apue(一)、文件和目录

129 阅读11分钟

1、文件/目录信息 stat

#include <sys/stat.h>
int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, strcut stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);

stat 函数返回与此命名文件有关的信息结构;fstat函数获得已在描述符fd上打开文件的有关信息;lstat函数类似于stat,但是当命名文件是一个符号链接时,lstat返回该符号链接的有关信息。

stat 结构体组成如下,其中 st_mode 需要重点关注。它包含了文件类型(普通文件\目录\字符特殊文件\管道或FIFO\符号链接\套接字\消息队列\信号量\共享存储),还包含了文件访问权限(读写执行)。

strcut stat {
    mode_t  st_mode;
    ino_t   st_ino;
    dev_t   st_dev;
    nlink_t st_nlink;
    uid_t   st_uid;
    gid_t   st_gid;
    off_t   st_size;
    struct timespec  st_atime;
    struct timespec  st_mtime;
    struct timespec  st_ctime;
    blksize_t        st_blksize;
    blkcnt_t         st_blocks;
}

文件类型信息可以通过宏确定文件类型,比如:

文件类型
S_ISREG()普通文件
S_ISDIR()目录文件
S_ISCHR()字符特殊文件
S_ISBLK()块特殊文件
S_ISFIFO()管道或FIFO
S_ISLNK()符号链接
S_ISSOCK()套接字
S_TYPEISMQ()消息队列
S_TYPEISSEM()信号量
S_TYPEISSHM()共享存储对象

文件访问权限也可以通过宏确定:

st_mode屏蔽含义
S_IRUSR用户读
S_IWUSR用户写
S_IXUSR用户执行
S_IRGRP组读
S_IWGRP组写
S_IXGRP组执行
S_IROTH其它读
S_IWOTH其它写
S_IXOTH其它执行

2、设置用户ID和设置组ID

一个进程关联6个或者更多的ID,包括:实际用户ID、实际组ID;有效用户ID、有效组ID、附属组ID;保存的设置用户ID、保存的设置组ID。

  • 实际用户ID/实际组ID:标识我们究竟是谁。这两个字段在登陆时取自口令文件中的登录项;
  • 有效用户ID/有效组ID/附属组ID:决定了文件的访问权限;
  • 保存的设置用户ID/保存的设置组ID:在执行一个程序时包含了有效用户ID和有效组ID的副本。

通常当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通常是实际组ID。但是可以在文件模式字(st_mode)中设置一个特殊标志,其含义是 "当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID",同理还有另一位将有效组ID设置为文件的组所有者ID。在文件模式中的这两位被称为设置用户ID位和设置组ID位。

例如,若文件所有者是超级用户,而且设置了该文件的设置用户ID位,那么当该程序文件由一个进程执行时,该进程具有超级用户权限。不管执行此文件的进程的实际用户ID是什么,都会是这样。例如UNIX系统程序 passwd 允许,该程序是一个设置用户ID程序。

进程每次打开、创建、删除一个文件时,内核就进行文件访问权限测试,测试涉及文件的所有者(st_uid、st_gid)、进程的有效ID(有效用户ID和有效组ID)以及进程的附属组ID。两个所有者ID是文件的性质,而两个有效ID和附属组ID则是进程的性质。内核进行的具体测试如下:

  • 若进程的有效用户ID是0,则允许访问。这给予了超级用户对整个文件系统进行处理的最充分自由;
  • 若进程的有效用户ID等于文件的所有者ID,那么如果所有者适当的访问权限被设置,则允许访问;否则拒绝访问。
  • 若进程的有效组ID或进程的附属组ID之一等于文件的组ID,那么如果组适当的访问权限被设置,则允许访问,否则拒绝访问。
  • 若其他用户适当的访问权限被设置,则允许访问;否则拒绝访问。

3、文件系统

3.1 常见的文件系统

Linux采用 Ext2 文件系统,windows 98采用 FAT 文件系统,windows 2000以后的版本采用 NTFS 文件系统。Linux文件系统在运行时,将文件权限(rws)与文件属性(拥有者、群组、时间参数等)放到 inode 中,而将实际数据放到 data block中,另外还有一个超级区块(superblock)记录整个文件系统的整体信息,包括 inode与 block的总量、使用量、剩余量等信息。

  • superblock:记录文件系统的整体信息,包括 inode/block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
  • inode:记录文件的属性,一个文件占用一个 inode,同时记录此文件的数据所在的 block 号码;
  • block:记录文件的实际内容,如果文件太大,会占用多个 block。

Ext2 文件系统中,文件的属性和权限放到 inode4号,而这个 inode记录了文件数据的实际放置点为 2,7,13,15 这四个block号码,通过读取 inode4号能一次性得知所有 block内容。

image.png

FAT文件系统数据存取则没有 inode节点,它将数文件数据依次序写入 block 号码中,每次读取一个 block号码内容。这样就导致了如果同一个文件数据写入的 block 分散的太过厉害时,我们的磁头将无法在磁盘转动一圈就读取到所有的数据,需要磁盘多转动几圈才能完整读取整个文件的内容。

FAT文件系统的存储方式会导致一个问题,那就是文件写入的 block 太过松散,此时会导致文件读取的性能过差。这个时候可以通过磁盘重组将一个文件所属的 blocks 汇整在一起,这样数据的读取会比较容易。而 Ext2文件系统属于索引式文件系统,基本不需要进行磁盘重组。

image.png

3.2 Ext2文件系统

Ext2 文件系统在格式化的时候基本上是区分为多个区块群组,每个区块群组都有独立的 inode/block/superblock 系统。

  • datablock(数据区块):

存放数据内容,有1K、2K、4K三种大小,在格式化时就固定好 block 大小了,不能再改变除非重新格式化。其次每个 block 内最多只能放置一个文件的数据;如果这个文件大于 block的大小,则一个文件会占用多个 block 数量;若文件小于一个 block大小,剩余的block容量也不能用了,会造成浪费。

  • inode(inode表格):

inode 记录很多信息,具体包括:(1) 该文件的存取模式(rwx);(2) 该文件的拥有者与群组;(3) 该文件的容量;(4) 该文件创建或状态改变的时间(ctime);(5) 最近一次的读取时间(atime);(6) 最近的修改时间(mtime);(7) 该文件真正内容的指向。

inode 的数量和大小在格式化时就固定了,每个 inode大小固定为 128Bytes(新的Ext4可以设置到256Bytes);每个文件仅会占用一个inode;系统在读取文件时先找到 inode,并分析inode 所记录的权限与使用者是否符合,若符合才能够开始实际读取 block的内容。

假如一个文件有 400MB 且每个 block大小为 4K时,inode仅仅为 128Bytes,那么至少需要十万多个block号码需要记录,此时单个 inode肯定无法记录;为此将 inode 记录block号码的区域定义为 12个直接、1个间接、1个双间接、1个三间接记录区。这样子 inode 能够记录更多的 block: 121K + 2561K + 2562561K + 256256256K = 16GB。

  • superblock(超级区块):

superblock 是记录整个文件系统相关信息的地方,主要记录包括:(1) inode与 block的总量;(2) 未使用与已使用的 inode/block 数量;(3) block 与inode的大小(block为 1,2,4K,inode为 128Bytes或 256Bytes);(4) 文件系统的挂载时间、最近一次写入数据的时间、最近一次检验磁盘的时间与文件系统的相关信息。

每个 block group 都可能含有 superblock,但是一个文件系统应该仅有一个 superblock,除了第一个 block group 含有 superblock,后续的 block group不一定含有 superblock,而含有 superblock 则该 superblock 主要是第一个 block group 内 superblock的备份。

superblock 在保留各个区段信息时通过记录以下信息:(1) FileSystem Description(文件系统描述说明),说明每个区段superblock、bitmap、inodemap、data block分别介于哪一个 block号码之间;(2) block bitmap(区块对照表),新增和删除文件时从 block bitmap中得知哪些 block是空的,并对 block bitmap对应 block号码的标志进行修改为未使用;(3) inode bitmap(inode 对照表),记录使用与未使用的 inode号码。

3.3 Ext2 文件系统与目录树

在 Linux 系统下,每个文件都会占用一个 inode,目录同样也是文件。当在 linux 下的文件系统创建目录时,文件系统会分配一个 inode与至少一块 block给该目录。其中 inode记录该目录的相关权限与属性,并可记录分配到的那块 block号码;而 block 则是记录这个目录下的文件名与该文件名占用的 inode号码数据

由于目录树是由根目录开始读起的,因此系统通过挂载的信息可以找到挂载点的 inode号码,此时就能够得到根目录的 inode内容,并依据该 inode读取根目录的 block内的文件名数据,再一层一层往下读到正确的文件名。

image.png

每个文件系统都有独立的 inode/block/superblock 等信息,这个文件系统要能够链接到目录树才能被我们使用。将文件系统与目录树结合的动作我们称为挂载。挂载点一定是目录,只有挂载到目录树的某个目录后,才能使用该文件系统。

3.4 实体链接与符号链接

在 Linux下有两种链接文件:硬链接(Hard Link)与软链接(symblic Link),在创建链接时,底层文件系统的操作有区别。

  • 硬链接

前面我们得知创建一个目录时,文件名只与目录有关,但是文件内容与inode有关。那么可能出现多个文件名对应到一个inode号码的情况。所以说硬链接只是在某个目录下新增一笔文件名链接到某inode号码的关联记录

image.png

image.png

硬链接也有需要注意的地方,(1) 硬链接仅能在单一文件系统中进行,不能够跨文件系统;(2) 不能对目录创建硬链接,因为如果对目录创建硬链接,那么链接的数据需要连同被链接目录下面的所有数据都创建链接。举例来说,如果你要将 /etc 使用硬链接创建一个 /etc_hd 目录时,那么在 /etc_hd 下面的所有文件名同时都要与 /etc 下的文件名创建硬链接,而不是仅仅链接到 /etc_hd 与 /etc而已。并且如果未来需要在 /etc_hd 下面创建新文件时,连带地需要去 /etc 下面创建一次硬链接,会对环境造成相当大的复杂度,所以不支持。

  • 符号链接

符号链接(软链接),创建一个独立的文件,而这个文件会让数据的读取指向他 link 的那个文件的文件名。所以只是利用文件来作为指向动作,当源文件被删除后,符号链接就会打开不了。

image.png

4、递归地统计某给定路径下的文件信息

要求:递归遍历一个目录下的所有文件和文件夹,统计各个类型文件所占的百分比

#include <sys/stat.h>
#include "../../include/err.h"
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>

struct FileStat
{
    char name[100];    //文件类型
    int count;         //文件数目
    float percent;     //占比
    FileStat(const char* name)
    {
        strcpy(this->name, name);
        this->count = 0;
        this->percent = 0;
    }
};

struct FileStat *fs[8] = {
    new FileStat("普通文件(REGULAR)              "),
    new FileStat("目录文件(DIRECTORY)            "),
    new FileStat("字符特殊文件(CHARACTER_SPECIAL)"),
    new FileStat("块特殊文件(BLOCK_SPECIAL)      "),
    new FileStat("管道或FIFO(FIFO)               "),
    new FileStat("符号链接(SYMBOLIC_LINK)        "),
    new FileStat("套接字(SOCKET)                 "),
    new FileStat("其他未知类型(UNKNOWN)          ")
};

struct stat buf;

void checkDir(const char* dir, int depth)
{
    DIR *dp;
    struct dirent *entry;
    struct stat statbuf;
    if ((dp = opendir(dir)) == NULL)
    {
        err_ret("can't open Directory: %s\n", dir);
    }
    chdir(dir);
    while ((entry = readdir(dp)) != NULL) 
    {
        lstat(entry->d_name, &statbuf);
        if (S_ISDIR(statbuf.st_mode))
        {
            if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0) {
                continue;
            }
            fs[1]->count++;
            checkDir(entry->d_name, depth+4);
        } else {
            if (lstat(entry->d_name, &buf) < 0) {
                err_ret("lstat error, pathname: %s", entry->d_name);
                continue;
            }
            if (S_ISREG(buf.st_mode))
                fs[0]->count++;
            else if (S_ISCHR(buf.st_mode))
                fs[2]->count++;
            else if (S_ISBLK(buf.st_mode))
                fs[3]->count++;
            else if (S_ISFIFO(buf.st_mode))
                fs[4]->count++;
            else if (S_ISLNK(buf.st_mode))
                fs[5]->count++;
            else if (S_ISSOCK(buf.st_mode))
                fs[6]->count++;
            else
                fs[7]->count++;
        }
    }  
    chdir("..");
    closedir(dp);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        err_sys("require 2 params");
    }
    checkDir(argv[1], 0);
    int i, total;
    for(i = 0; i < 8; i++)
    {
        total += fs[i] -> count;
    }
    for(i = 0; i < 8; i++)
    {
        fs[i] -> percent = 1.0 * fs[i] -> count / total;
    }

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8dbd2a48e05646078ce367d8413b7130~tplv-k3u1fbpfcp-watermark.image?)
    //输出计算结果
    printf("                    STAT                    \n");
    printf("============================================\n");
    for(i = 0; i < 8; i++)
    {
        printf("%s | %7.3f%%\n", fs[i] -> name, 100.0 * fs[i] -> count / total);
    }
    for (int i = 0; i < 8; i++) {
        delete fs[i];
        fs[i] = NULL;
    }
    printf("Done.\n");
}

image.png