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内容。
FAT文件系统数据存取则没有 inode节点,它将数文件数据依次序写入 block 号码中,每次读取一个 block号码内容。这样就导致了如果同一个文件数据写入的 block 分散的太过厉害时,我们的磁头将无法在磁盘转动一圈就读取到所有的数据,需要磁盘多转动几圈才能完整读取整个文件的内容。
FAT文件系统的存储方式会导致一个问题,那就是文件写入的 block 太过松散,此时会导致文件读取的性能过差。这个时候可以通过磁盘重组将一个文件所属的 blocks 汇整在一起,这样数据的读取会比较容易。而 Ext2文件系统属于索引式文件系统,基本不需要进行磁盘重组。
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内的文件名数据,再一层一层往下读到正确的文件名。
每个文件系统都有独立的 inode/block/superblock 等信息,这个文件系统要能够链接到目录树才能被我们使用。将文件系统与目录树结合的动作我们称为挂载。挂载点一定是目录,只有挂载到目录树的某个目录后,才能使用该文件系统。
3.4 实体链接与符号链接
在 Linux下有两种链接文件:硬链接(Hard Link)与软链接(symblic Link),在创建链接时,底层文件系统的操作有区别。
- 硬链接:
前面我们得知创建一个目录时,文件名只与目录有关,但是文件内容与inode有关。那么可能出现多个文件名对应到一个inode号码的情况。所以说硬链接只是在某个目录下新增一笔文件名链接到某inode号码的关联记录。
硬链接也有需要注意的地方,(1) 硬链接仅能在单一文件系统中进行,不能够跨文件系统;(2) 不能对目录创建硬链接,因为如果对目录创建硬链接,那么链接的数据需要连同被链接目录下面的所有数据都创建链接。举例来说,如果你要将 /etc 使用硬链接创建一个 /etc_hd 目录时,那么在 /etc_hd 下面的所有文件名同时都要与 /etc 下的文件名创建硬链接,而不是仅仅链接到 /etc_hd 与 /etc而已。并且如果未来需要在 /etc_hd 下面创建新文件时,连带地需要去 /etc 下面创建一次硬链接,会对环境造成相当大的复杂度,所以不支持。
- 符号链接:
符号链接(软链接),创建一个独立的文件,而这个文件会让数据的读取指向他 link 的那个文件的文件名。所以只是利用文件来作为指向动作,当源文件被删除后,符号链接就会打开不了。
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;
}

//输出计算结果
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");
}