遍历目录下的文件时要用lstat而不能用stat
#include <sys/stat.h> int stat(const char *path, struct stat *buf); int fstat(int fd, struct stat *buf); int lstat(const char *path, struct stat *buf);//丢弃跟随属性,只针对符号链接本身,不针对符号链接对应的文件 int fstatat(int fd,const char *path, struct stat *buf,int flag); //当flag被设置为AT_SYMLINK_NOFOLLOW时,fstatat不跟随符号链接,只返回符号链接本身的信息 //fd为AT_FDCWD时,fstatat会计算针对当前目录的path参数 //以上函数成功返回0失败返回-1 struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
文件类型
- 普通文件:包含任意数据,应用程序要区分文本文件he二进制文件,文本文件时只含有ASCII和Unicode字符的普通文件,二进制文件是所有其他文件,对内核而言,文本文件和二进制文件没有区别。Linux文本文件包含了一个文本行序列,每一行都是一个字符序列,以新行符 结束,新行符与ASCII换行符LF一样,数值为0x0a。一个文件包括两部分数据,一部分是元数据,如文件的类型、权限、大小、用户、组、各种时间戳等,存储在 i 节点中,另一部分是内容数据,存储在数据块中。
- 目录文件:包含一组链接,系统通过 i 节点唯一地标识一个文件的存在,目录就是用来建立文件名和 i 节点之间的映射的。目录的本质就是一个普通文件,与其它普通文件唯一的区别就是它仅仅存储文件名和 i 节点号的映射,每一个这样的映射,用目录中的一个条目表示,谓之硬链接。
- 块特殊文件:这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
- 字符特殊文件:这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
- FIFO:这种类型的文件用于进程间通信,有时也称为命名管道。
- 套接字:这种类型的文件用于进程间的网络通信。套接字也可用于一台宿主机上进程之间的非网络通信。
- 符号链接:这种类型的文件指向另一个文件。
//文本信息包含在st_mode中,以下宏确定st_mode成员类型 S_ISREG(m) is it a regular file (普通文件) S_ISDIR(m) directory (目录文件) S_ISCHR(m) character device (字符特殊文件) S_ISBLK(m) block device (块特殊文件) S_ISFIFO(m) FIFO (named pipe) (管道或 FIFO) S_ISLNK(m) symbolic link (符号链接)(Not in POSIX.1-1996.) S_ISSOCK(m) socket (套接字)(Not in POSIX.1-1996.)
也可从stat结构体中确定IPC对象类型,他们的参数并非st_mode,而是指向stat结构体指针
S_TYPEISMQ()//消息队列 S_TYPEISSEM()//信号量 S_TYPEISSHM()//共享存储对象
设置用户ID和设置组ID
与一个进程关联的ID有6个或更多,如下图所示:
实际用户ID 实际组ID |
我们实际是谁 |
有效用户ID 有效组ID 附加组ID |
用于文件访问权限检索 |
保存的设置用户ID 保存的设置组ID |
由exec函数保存 |
- 实际用户ID和实际组ID标识我们究竟是谁,这两个字段在登录时取自口令文件中的登录项。通常,在一个登录会话间这些值并不改变,但是超级用户进程有方法改变它们。
- 有效用户ID,有效组ID以及附加组ID决定了我们的文件访问权限。
- 保存的设置的用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本。
st_uid和st_gid
通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。
每个文件都有一个所有者和组所有者,所有者由stat结构中的st_uid成员表示,组所有者则由st_gid成员表示——只针对可执行文件有效
当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通常是实际组ID。但是可以在文件模式字中设置一个特殊标志,其含义是“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID(st_uid)”。与此类似,在文件模式字中可以设置另一位,它使得将执行此文件的进程的有效组ID设置为文件的组所有者(st_gid)。在文件模式字中的这两位被称为设置用户ID(set_user-ID)位和设置组ID(set-group-ID)位。
例如,若文件所有者是超级用户,而且设置了该文件的设置用户ID位,然后当该程序由一个进程执行时,则该进程具有超级用户特权。不管执行此文件的进程的实际用户ID是什么,都进行这样的处理。例如,UNIX程序password允许任一用户改变其口令,该程序是一个设置用户ID程序。因为该程序应能将用户的新口令写入口令文件中,而只有超级用户才具有对该文件的写权限,所以需要使用设置用户ID特征。因为运行设置用户ID程序的进程通常得到额外的权限,所以编写这样程序时要特别谨慎。
再返回到stat函数,设置用户ID位及设置组ID位都包含在st_mode值中。这两位可用常量S_ISUID和S_ISGID测试。
文件访问权限
stat 结构的st_mode 值中包含了针对文件的访问权限位。所有文件类型都具有访问权限。每个文件有 9 个访问权限位
对应的值:在st_mode中包含文件的类型(4位);set_uid,set_gid,黏着位(3位);访问权限(9位)
S_IFMT 0170000 文件类型的位遮罩 S_IFSOCK 0140000 socket S_IFLNK 0120000 符号链接(symbolic link) S_IFREG 0100000 一般文件 S_IFBLK 0060000 区块装置(block device) S_IFDIR 0040000 目录 S_IFCHR 0020000 字符装置(character device) S_IFIFO 0010000 先进先出(fifo) S_ISUID 0004000 文件的(set user-id on execution)位 S_ISGID 0002000 文件的(set group-id on execution)位 S_ISVTX 0001000 文件的sticky位 S_IRWXU 00700 文件所有者的遮罩值(即所有权限值) S_IRUSR 00400 文件所有者具可读取权限 S_IWUSR 00200 文件所有者具可写入权限 S_IXUSR 00100 文件所有者具可执行权限 S_IRWXG 00070 用户组的遮罩值(即所有权限值) S_IRGRP 00040 用户组具可读取权限 S_IWGRP 00020 用户组具可写入权限 S_IXGRP 00010 用户组具可执行权限 S_IRWXO 00007 其他用户的遮罩值(即所有权限值) S_IROTH 00004 其他用户具可读取权限 S_IWOTH 00002 其他用户具可写入权限 S_IXOTH 00001 其他用户具可执行权限
- 用名字打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限,这就是对目录的执行权限称为搜索位。例如,为了打开文件/usr/include/stdio.h,需要对目录/、/usr 和/usr/include具有执行权限。然后,需要具有对该文件本身的适当权限。如果当前工作目录是/usr/include,那么为了打开文件 stdio.h,则需要有对该工作目录的执行权限。注意:对于目录的读权限和执行权限的意义是不相同的。读权限允许我们读目录,获取在该目录中所有文件名的列表。当一个目录是我们要访问文件的路径名的一个组成部分时,对该目录的执行权限使我们可通过该目录(也就是搜索该目录,寻找一个特定的文件名。搜索该目录,寻找特定的文件名)
- 文件的读权限决定了我们是否能够打开该文件进行读操作。open一个文件O_TRUNC标志时,必须对该目录有写权限
- 文件的写权限决定了我们是否能够打开该文件进行写操作。
- 为了要在一个目录中创建一个新文件,必须对该目录具有写权限和执行权限。
- 为了删除一个现有的文件,必须对包含该文件的目录具有写权限和执行权限。对该文件本身则不需要有读、写权限。
- 如果用7个exec 函数中的任何一个执行某个文件,都必须对该文件具有执行权限。该文件还必须是一个普通文件。
测试文件访问权限位
进程每次打开、创建或删除一个文件时,内核就进行文件访问权限测试。这种测试可能涉及文件的所有者(st_uid 和st_gid)、进程的有效 ID(有效用户 ID 和有效组 ID)以及进程的附加组 ID。内核进行的测试按下面步骤依次进行:
- 若进程的有效用户 ID 为 0(即超级用户),则允许访问。
- 若进程的有效用户 ID 等于文件的所有者 ID,那么:若所有者适当的访问权限位被设置,则允许访问,否则拒绝访问。适当的访问权限位指的是:若进程为读而打开该文件,则用户读位应为 1;若进程为写而打开该文件爱你,则用户写位应为 1;若进程将执行该文件,则用户执行位应为 1.
- 若进程有效组 ID 或进程附加组 ID 之一等于文件的组 ID,那么,若组适当的访问权限位被设置,则允许访问,否则拒绝访问。
- 若其他用户适当的访问权限位被设置,则允许访问,否则拒绝访问。
顺序的执行以上四步,如果进程拥有此文件(2步)按用户访问权限位批准或拒绝该进程对文件的访问——不看组访问权限,如果进程不拥有此文件,单进程属于某个适当的组,按组访问权限位批准或拒绝该进程对文件的访问权限——不看其他用户的访问权限。
新文件的目录和所有权限
新文件的用户 ID 设置为进程的有效用户 ID。关于组 ID,POSIX.1 允许实现选择下列之一作为新文件的组 ID。
- 新文件的组 ID 可以是进程有效组 ID。
- 新文件的组 ID 可以是它所在目录的组 ID。
对于 Linux 2.4.22,新文件的组 ID 取决于它所在目录的设置组 ID 为是否被设置。如果该目录的这一位被设置,则新文件的组 ID 设置为目录的组 ID;否则,将新文件的组 ID 设置为进程的有效组 ID。
#include <unistd.h> int access(const char *pathname, int mode); int faccessat(int fd,const char *pathname, int mode,int flag); //以上函数成功返回0出错返回-1 /* Values for the second argument to access. These may be OR'd together. */ #define R_OK 4 /* Test for read permission. */ #define W_OK 2 /* Test for write permission. */ #define X_OK 1 /* Test for execute permission. */ #define F_OK 0 /* Test for existence. */
注意:
open打开文件时,内核以进程有效用户ID和有效组ID为基础测试文件访问权限位。次函数按实际用户ID和实际组ID进行文件访问权限位测试
#include <sys/stat.h> int chmod(const char *path, mode_t mode); int fchmod(int fd, mode_t mode); int fchmodat(int fd,const char *path,mode_t mode,int flag); //以上函数成功返回0失败返回-1
The new file permissions are specified in mode, which is a bit mask created by ORing together zero or more of the following: S_ISUID (04000) set-user-ID (set process effective user ID on execve(2)) S_ISGID (02000) set-group-ID (set process effective group ID on execve(2); mandatory locking, as described in fcntl(2); take a new file's group from parent directory, as described in chown(2) and mkdir(2)) S_ISVTX (01000) sticky bit (restricted deletion flag, as described in unlink(2)) S_IRUSR (00400) read by owner S_IWUSR (00200) write by owner S_IXUSR (00100) execute/search by owner ("search" applies for directories, and means that entries within the directory can beaccessed) S_IRGRP (00040) read by group S_IWGRP (00020) write by group S_IXGRP (00010) execute/search by group S_IROTH (00004) read by others S_IWOTH (00002) write by others S_IXOTH (00001) execute/search by others
注意:
- chomd只更新i结点最后一次访问时间
- 对普通文件赋予粘着位,有没有超级用户权限,则mode中的粘着位自动关闭,防止用户恶意设置粘着位,影响系统性能
- 新创建文件的组ID可能不是调用进程的所属的组。如果新文件的组ID不等于该进程有效组ID或进程复述数组ID其中之一,而且进程没有超级用户权限,那么设置组ID会被清除。防止用户创建了一个设置组ID文件,而该文件是由非该用户所属的组拥有的
粘着位
对于文件:
在以前旧的系统当中,如果一个程序文件一旦设置了粘着位,那么当该程序中止的时候他的所有指令段将被保存到系统的交换分区当中,再次运行时可以更快的调入系统.不过现在的操作系统已经不再使用这种功能了.但这并不表示这功能已经完全被废弃
对于目录:
当一个目录设置为粘着位时,它将发挥特殊的作用,即当一个目录被设置为"粘着位"(用chmod a+t),则该目录下的文件只能由
- 超级管理员删除
- 该目录的所有者删除
- 该文件的所有者删除
也就是说,即便该目录是任何人都可以写,但也只有文件的属主才可以删除文件
文件长度
stat结构成员st_size表示以字节为单位的文件长度,此字段只对普通文件、目录文件和符号链接有意义。对于普通文件,其文件长度可以是0,在读这种文件时,将得到文件结束(end-of-file)指示。对于目录,文件长度通常是一个数(例如16或者512)的倍数。对于符号链接,文件长度是文件名中实际字节数。
空洞文件
空洞是由所设置的偏移量超过文件尾端,并写了某些数据后造成的。对于没有写过的字节位置,read函数读到的字节是0.如果使用实用程序(例如cat(1))复制这种文件,那么所有这些空洞都会被填满,其中所有实际数据字节皆填写为0.
文件系统
我们可以把一个磁盘分成一个或多个分区。每个分区可以包含一个文件系统

- 在图中有两个目录项指向同一个i节点。每个i节点中都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少至0时,才可删除该文件(也就是可以释放该文件占用的数据块)。这就是为什么“删除对一个文件的链接”操作并不总是意味着“释放该文件占用的磁盘块”的原因。在stat结构中,链接计数包含在st_nlink成员中,其基本系统数据类型是nlink_t。这种链接类型称为硬链接,LINK_MAX指定了一个文件连接数的最大值。
- 另外一种链接类型称为符号链接。对于这种链接,该文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字。在上面的例子中:该目录项中的文件名是3字符的字符串lib,而在该文件中包含了7个数据字节usr/lib。gaii节点中的文件类型是S_IFLNK,于是系统知道这是一个符号链接。
- i节点包含了大多数与文件有关的信息:文件类型、文件访问权限位、文件长度和指向该文件所占用的数据块的指针等。stat结构的大多数信息都是取自i节点。只有两项数据存放在目录项中:文件名和i节点编号。i节点编号的数据类型是ino_t
-
每个文件系统各自对他们的i节点进行编号,因此目录项中的i节点编号数指向同一文件系统中的相应i节点,不能使一个目录项指向另一个文件系统的i节点。
-
当在不更换文件系统情况下为一个文件更名时,该文件的实际内容并未移动,只需构造一个指向现有i节点的心目录项,并解除与旧目录项的链接。这就是mv(1)命令的通常操作方式
硬链接
任何一个文件可以有多个目录项指向其i节点,创建一个指向现有文件的链接——link,也就是创建硬链接。
#include<unistd.h> int link(const char *existingpath, const char *newpath); int linkat(int efd,const char *existingpath,int nfd,const char *newpath,int flag); //返回值:若成功返回0,若出错返回-1
此函数创建一个新目录项newpath,它引用现有的文件existingpath。若果newpath已经存在,则返回出错。只创建newpath中的最后一个分量,路径中的其他部分应当已存在。
创建新目录项以及增加链接计数应当是个原子操作,大多数实现要求这两个路径名在同一个文件系统中。如果实现支持创建执行一个目录的硬链接,那么也是仅限于超级用户才可以这么做
为了删除一个现有的目录项,可以调用unlink函数
#include <unistd.h> int unlink(const char *pathname); int unlinkat(int fd,const char *pathname,int flag); //返回值:若成功返回0,若出错返回-1
此函数删除目录项,并将由pathname所引用文件的链接计数减1。如果还有指向该文件的其他链接,则仍然可以通过其他链接访问该文件的数据。如果出错,怎不对该文件做任何更改。
为了解除对文件的链接,必须对包含该目录项目录具有写和执行权限。如果对该目录设置了粘着位,则对该目录必须具有写权限,并且具备下面三个条件之一:
- 拥有该文件
- 拥有该目录
- 具有超级用户特权
只有链接计数达到0时,该文件的内容才可以被删除。只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程数。如果该数达到0,然后内核检查其连接数,如果这个数也是0,那么就删除该文件的内容。
unlink的这种性质经常被程序用来确保及时是在该程序奔溃时,他所创建的临时文件也不会遗留下来。进程用open或create创建一个文件,然后立即调用unlink。因为该文件仍旧是打开的,所以不会将其内容删除。只有当进程关闭该文件或终止时(在这种情况下,内核会关闭该进程打开的全部文件),该文件的内容才会被删除。
如果pathname是符号链接,那么unlink删除该符号链接,而不会删除由该链接所引起的文件。给出符号链接名情况下,没有一个函数能删除该链接所引用的文件。
符号链接
符号链接是指向一个文件的间接指针,它与硬链接有所不同,硬链接直接指向文件的i节点。引入符号链接的原因是为了避开硬链接的一些限制:
- 硬链接通常要求链接和文件位于同一文件系统中。
- 只有超级用户才能创建指向目录的硬链接。
对符号链接以及他指向各种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移到系统中的另一个位置。
当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。也就是该函数是否跟随符号链接到达他所连接的文件。如果该函数具有处理符号链接的功能,则其路径名参数引用由符号链接所指向的文件。否则,路径名参数将引用链接本身,而不是该链接指向的文件。
下表列出了本章所说明的各个函数是否处理符号链接,表中没有列出mkdrir、mkinfo、mknod、rmdir这些函数,其原因是,当路径名是符号链接时,他们都出错返回。以文件描述符作为参数的一些函数(如fstat、fchmod等)也未在该表中列出,其原因是,对于符号链接的处理是由返回文件描述符的函数(通常是open)进行的。chown是否跟随符号链接取决于实现。
引入符号链接可能在文件系统中引入循环。大多数查找路径名的函数在这种情况发生时都将返回值为ELOOP的errno
这里创建了一个目录foo,它包含了一个名为a的文件以及一个指向foo的符号链接。在下图显示了这种这种结果,以圆表示目录,正方形表示一个文件。如果我们编写一段程序,使用Solaris的标准函数ftw(3)以降序遍历文件结构,打印每个遇到的路径名:其结果输出是:
这样一个循环式很容易消除的。因为unlink并不跟随符号链接,所以可以unlink文件foo/testdir。但是如果创建了一个构成循环的硬链接,那么就很难消除它。这就是为什么link函数不允许构造指向目录的硬链接的原因(除非进程具有超级用户特权)。
当open打开文件时,如果传递给open函数的路径名指定了一个符号链接,那么open跟随此链接到达你所指定的文件。若此符号链接所指向的文件并不存在,则open返回出错,表示他不能打开该文件。
创建符号链接
#include <unistd.h> int symlink(const char *actualpath, const char *sympath); int symlinkat(const char *actualpath,int fd,const char *sympath); //返回值:若成功怎返回0,若出错则返回-1;
#include<unistd.h> ssize_t readlink(const char* restrict pathname, char *restrict buf,size_t bufsize); ssize_t readlinkat(int fd,const char* restrict pathname, char *restrict buf,size_t bufsize); //返回值:若成功则返回读到的字节数,若出错则返回-1;
此函数结合了open、read和close的所有操作。如果此函数成功执行,则他返回读入buf的字节数。在buf中返回的符号链接的内容不以null字符终止。
文件时间
注意修改时间(st_mtime)和更改状态时间呢(st_ctime)之间的区别。修改时间是文件内容最后一次被修改的时间。更改时间状态是该文件的i节点最后一次被修改的时间。有很多操作,他们影响到i节点,但没有更改文件的实际内容:文件的存取许可权、用户ID、连接数等等。因为i节点中的所有信息都是与文件的实际内容分开存放的,所以,除了文件夹数据修改时间以外,还需要更改状态时间。
注意,系统并不保存对一个i节点的最后一次存取时间,所以access和stat函数并不更改这三个时间中的任意一个。
系统管理员常常使用存取时间来删除在一定时间范围内没有存取过的文件。典型的例子是删除在过去一周内没有存取过的名为a.out或core的文件。find(1)命令常被用来进行这种操作。
修改时间和更改时间状态可被用来归档其内容已经被修改或者其i节点已经被更改的那些文件。
ls命令按这三个时间值中的一个进行排序显示。按系统默认,他按文件的修改时间的先后排序显示。-u选择项使其用存取时间排序,-c选择项则使其用刚改状态时间排序。
读目录
每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点(不以斜线开始的路径名为相对路径名)。当前工作目录是进程的一个属性,起始目录则是登录名的一个属性。
对某个目录具有访问权限的任一用户都可以读该目录,但是为了防止文件系统产生混乱,只有内核才可以写目录。一个目录的写权限位和执行权限位决定了该目录中能否创建新文件以及删除文件,他们并不表示能否写目录本身。
目录的实际格式依赖于UNIX系统实现和文件系统的设计。很多实现阻止应用程序使用read函数读取目录的内容,由此进一步将应用程序与目录格式中相关的细节隔离。
#include<dirent.h> DIR *opendir(const char *pathname); DIR *fdopendir(int fd); //两个函数返回值:若成功返回指针,若出错返回NULL struct dirent *readdir(DIR *dp); //返回值:若成功,返回指针;若在目录尾或者出错,返回NULL void rewinddir(DIR *dp); int closedir(DIR *dp); //返回值:若成功,返回0,若出错,返回-1 long telldir(DIR *dp); //返回值:与dp关联的目录中的当前位置 void seekdir(DIR *dp, long loc);
定义在头文件<dirent.h>中的dirent结构与实现相关。实现对此结构所做的定义至少包含下列两个成员:
ino_t d_ino; /*i节点编号*/ char d_name[]; /*以null结束的文件名*/
注意,d_name项的大小并没有指定,但必须保证他能包含至少NAME_MAX个字节(不包含终止null字节)。因为文件名是以null字节结束的,所以在头文件中如何定义数组d_name并无多大关系。数值大小并不表示文件名的长度。
DIR结构是一个内部结构,
上述的函数用这个内部结构保存当前正在被读的目录的有关信息。其作用类似于FILE结构。
由opendir和fdopendir返回的指向DIR结构的指针由另外5个函数使用。opendir执行初始化操作,使第一个readdir返回目录中的第一个目录项。DIR结构由fdopendir创建时,readdir返回的第一项取决于传给fdopendir函数的文件描述符相关联的文件偏移量。注意,目录中各目录项的顺序与实现有关。他么通常不按字母顺序排列。
下例子为一个遍历文件层次结构的程序

#include "apue.h" #include "pathalloc.h" #include <dirent.h> #include <limits.h> // function type that is called for each filename typedef int Myfunc(const char *, const struct stat *, int); static Myfunc myfunc; static int myftw(char *, Myfunc *); static int dopath(Myfunc *); static long nreg, ndir, nblk, nchr, nfifo, nslink, nsock, ntot; int main(int argc, char *argv[]) { int ret; if (argc != 2) { err_quit("usage: ftw <starting-pathname>"); } ret = myftw(argv[1], myfunc); // does it all ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock; if (ntot == 0) { ntot = 1; // avoid divide by 0; print 0 for all counts } printf("regular files = %7ld, %5.2f %% ", nreg, nreg * 100.0 / ntot); printf("directories = %7ld, %5.2f %% ", ndir, ndir * 100.0 / ntot); printf("block special = %7ld, %5.2f %% ", nblk, nblk * 100.0 / ntot); printf("char special = %7ld, %5.2f %% ", nchr, nchr * 100.0 / ntot); printf("FIFLs = %7ld, %5.2f %% ", nfifo, nfifo * 100.0 / ntot); printf("symbolic links = %7ld, %5.2f %% ", nslink, nslink * 100.0 / ntot); printf("sockets = %7ld, %5.2f %% ", nsock, nsock * 100.0 / ntot); exit(ret); } /* * Descend through the hierarchy starting at "pathname". * The caller's func() is called for every file. * */ #define FTW_F 1 // file other than directory #define FTW_D 2 // directory #define FTW_DNR 3 // directory thar can't be read #define FTW_NS 4 // file that we can't stat static char *fullpath; // contains full pathname for every file static size_t pathlen; static int myftw(char *pathname, Myfunc *func) // we return whatever func() returns { fullpath = path_alloc(&pathlen); // malloc PATH_MAX+1 bytes if (pathlen <= strlen(pathname)) { pathlen = strlen(pathname) * 2; if ((fullpath = realloc(fullpath, pathlen)) == NULL) { err_sys("realloc failed"); } } strcpy(fullpath, pathname); return (dopath(func)); } /* * Descend through the hierarchy, starting at "fullpath". * If "fullpath" is anything other than a directory, we lstat() it * call func(), and return. For a directory, we call ourself * recursively for each name in the directory. * */ static int dopath(Myfunc *func) // we return whatever func() returns { struct stat statbuf; struct dirent *dirp; DIR * dp; int ret, n; if (lstat(fullpath, &statbuf) < 0) { // stat error return (func(fullpath, &statbuf, FTW_NS)); } if (S_ISDIR(statbuf.st_mode) == 0) { // not a directory return (func(fullpath, &statbuf, FTW_F)); } /* * It's a directory. First call func() for the directory, * then process each filename in the directory. * */ if ((ret = func(fullpath, &statbuf, FTW_D)) != 0) { return ret; } n = strlen(fullpath); if (n + NAME_MAX +2 > pathlen) { // expand path buffer pathlen *= 2; if ((fullpath = realloc(fullpath, pathlen)) == NULL) { err_sys("realloc failed"); } } fullpath[n++] = '/'; fullpath[n] = 0; if ((dp = opendir(fullpath)) == NULL) { // can't read directory return func(fullpath, &statbuf, FTW_DNR); } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) { continue; // ignore dot and dot-dot } strcpy(&fullpath[n], dirp->d_name); // append name agter "/" if ((ret = dopath(func)) != 0) { // recursive break; // time to leave } } fullpath[n - 1] = 0; // erase everything from slash onward if (closedir(dp) < 0) { err_ret("can't close directory %s", fullpath); } return ret; } static int myfunc(const char *pathname, const struct stat *statptr, int type) { switch (type) { case FTW_F: switch (statptr->st_mode & S_IFMT) { case S_IFREG: ++nreg; break; case S_IFBLK: ++nblk; break; case S_IFCHR: ++nchr; break; case S_IFIFO: ++nfifo; break; case S_IFLNK: ++nslink; break; case S_IFSOCK: ++nsock; break; case S_IFDIR: // directories should have type = FTW_D err_dump("for S_IFDIR for %s", pathname); } break; case FTW_D: ++ndir; break; case FTW_DNR: err_ret("can't read directory %s", pathname); break; case FTW_NS: err_ret("stat error for %s", pathname); break; default: err_dump("unknow type %d for pathname %s", type, pathname); } return 0; }
下列例子为深度遍历文件层次结构

/************************************************************************* > File Name: ls.cpp > Author: Chen Tianzeng > Mail: 971859774@qq.com > Created Time: 2019年03月07日 星期四 11时16分12秒 ************************************************************************/ #include <iostream> #include <sys/stat.h> #include <dirent.h> #include <unistd.h> #include <cstring> #include <cstdio> #include <cstdlib> using namespace std; void ls_dirent(char *s) { DIR *dir; struct dirent *d; struct stat stats; if((dir=opendir(s))==NULL) { cout<<s<<endl; return; } chdir(s); while((d=readdir(dir))!=NULL) { lstat(d->d_name,&stats); if(S_ISDIR(stats.st_mode)) { if(strcmp(d->d_name,".")==0||strcmp(d->d_name,"..")==0) continue; else { char str[100]; memset(str,'',sizeof(str)); strcpy(str,s); strcat(str,"/"); strcat(str,d->d_name); ls_dirent(str); } } else { char dircwd[100]; memset(dircwd,'',100); strcpy(dircwd,s); strcat(dircwd,"/"); strcat(dircwd,d->d_name); cout<<dircwd<<endl; } } chdir(".."); closedir(dir); return ; } int main(int argc,char **argv) { char s[100]; cin>>s; ls_dirent(s); return 0; }
GitHub:https://github.com/tianzengBlog/test/tree/master/test/dir
特殊设备文件
st_dev和st_rdev这两个字段经常引起混淆,有关规则如下:
- 每个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t。主设备号标识设备驱动程序,有时编码为于其通信的外设扮;次设备号标识特定的子设备。
- 我们通常使用两个宏:major和minor来访问主、次设备号,大多数实现都定义这两个宏。这就意味着我们无需关心这两个数是如何存放在dev_t对象中的。
- 系统中与每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文件名以及与其对应的i节点。
- 只有字符特殊设备和块特殊设备才有st_rdev值。此指包含实际设备的设备号。
Linux将宏major和minor定义在头文件<sys/sysmacros.h>中,而该头文件又包括在<sys/type.h>中。
如下程序为每个命令行参数打印设备号,另外,若此参数引用的是字符特殊文件或块特殊文件,则还会打印该特殊文件的st_rdev值。
int i; struct stat buf; if(stat(filename, &buf) < 0) { err_ret("stat error"); continue; } printf("dev = %d/%d", major(buf.st_dev), minor(buf.st_dev)); if(S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode)) { printf(" (%s) rdev = %d/%d", (S_ISCHR(buf.st_mode)) ? "character" : "block", major(buf.st_rdev), minor(buf.st_rdev)); }