文件和目录

  遍历目录下的文件时要用lstat而不能用statnode

#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 */
};

文件类型

  1. 普通文件:在 UNIX/Linux 系统中一般所见到的文件,一个文件包括两部分数据,一部分是元数据,如文件的类型、权限、大小、用户、组、各类时间戳等,存储在 i 节点中,另外一部分是内容数据,存储在数据块中。对于数据是文本数据仍是二进制数据无区别(例外:二进制可执行文件,为了执行程序,内核必须理解其格式,这种格式肯定程序文本和数据加载的位置)。
  2. 目录文件:系统经过 i 节点惟一地标识一个文件的存在,但人们更愿意使用有意义的文件名来访问文件,目录就是用来创建文件名和 i 节点之间的映射的。目录的本质就是一个普通文件,与其它普通文件惟一的区别就是它仅仅存储文件名和 i 节点号的映射,每个这样的映射,用目录中的一个条目表示,谓之硬连接
  3. 块特殊文件:这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
  4. 字符特殊文件:这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变。系统中的全部设备要么是字符特殊文件,要么是块特殊文件。
  5. FIFO:这种类型的文件用于进程间通讯,有时也称为命名管道。
  6. 套接字:这种类型的文件用于进程间的网络通讯。套接字也可用于一台宿主机上进程之间的非网络通讯。
  7. 符号连接:这种类型的文件指向另外一个文件。
//文本信息包含在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结构体指针ios

S_TYPEISMQ()//消息队列
S_TYPEISSEM()//信号量
S_TYPEISSHM()//共享存储对象

设置用户ID和设置组ID

与一个进程关联的ID有6个或更多,以下图所示:git

实际用户IDgithub

实际组ID数组

咱们实际是谁

有效用户ID网络

有效组IDapp

附加组IDsocket

用于文件访问权限检索

保存的设置用户IDide

保存的设置组ID函数

由exec函数保存
  1. 实际用户ID和实际组ID标识咱们到底是谁,这两个字段在登陆时取自口令文件中的登陆项。一般,在一个登陆会话间这些值并不改变,可是超级用户进程有方法改变它们。
  2. 有效用户ID,有效组ID以及附加组ID决定了咱们的文件访问权限。
  3. 保存的设置的用户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 屏蔽 意义
S_IRUSR 用户 -读
S_IWUSR 用户 -写
S_IXUSR 用户 -执行
S_IRGRP 组 -读
S_IWGRP 组 -写
S_IXGRP 组 -执行
S_IROTH 其余 -读
S_IWOTH 其余 -写
S_IXOTH 其余 -执行

  1. 用名字打开任一类型的文件时,对该名字中包含的每个目录,包括它可能隐含的当前工做目录都应具备执行权限,这就是对目录的执行权限称为搜索位。例如,为了打开文件/usr/include/stdio.h,须要对目录/、/usr 和/usr/include具备执行权限。而后,须要具备对该文件自己的适当权限。若是当前工做目录是/usr/include,那么为了打开文件 stdio.h,则须要有对该工做目录的执行权限。注意:对于目录的读权限和执行权限的意义是不相同的。读权限容许咱们读目录,获取在该目录中全部文件名的列表当一个目录是咱们要访问文件的路径名的一个组成部分时,对该目录的执行权限使咱们可经过该目录(也就是搜索该目录,寻找一个特定的文件名。)
  2. 文件的读权限决定了咱们是否可以打开该文件进行读操做。
  3. 文件的写权限决定了咱们是否可以打开该文件进行写操做。
  4. 为了要在一个目录中建立一个新文件,必须对该目录具备写权限和执行权限
  5. 为了删除一个现有的文件,必须对包含该文件的目录具备写权限和执行权限。对该文件自己则不须要有读、写权限
  6. 若是用7个exec 函数中的任何一个执行某个文件,都必须对该文件具备执行权限。该文件还必须是一个普通文件

测试文件访问权限位

  进程每次打开、建立或删除一个文件时,内核就进行文件访问权限测试。这种测试可能涉及文件的全部者(st_uid 和st_gid)、进程的有效 ID(有效用户 ID 和有效组 ID)以及进程的附加组 ID。内核进行的测试按下面步骤依次进行:

  1. 若进程的有效用户 ID 为 0(即超级用户),则容许访问。
  2. 若进程的有效用户 ID 等于文件的全部者 ID,那么:若全部者适当的访问权限位被设置,则容许访问,不然拒绝访问。适当的访问权限位指的是:若进程为读而打开该文件,则用户读位应为 1;若进程为写而打开该文件爱你,则用户写位应为 1;若进程将执行该文件,则用户执行位应为 1.
  3. 若进程有效组 ID 或进程附加组 ID 之一等于文件的组 ID,那么,若组适当的访问权限位被设置,则容许访问,不然拒绝访问。
  4. 若其余用户适当的访问权限位被设置,则容许访问,不然拒绝访问。

  顺序的执行以上四步,若是进程拥有此文件(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

注意:

  1. chomd只更新i结点最后一次访问时间
  2. 对普通文件赋予粘着位,有没有超级用户权限,则mode中的粘着位自动关闭,防止用户恶意设置粘着位,影响系统性能
  3. 新建立文件的组ID可能不是调用进程的所属的组。若是新文件的组ID不等于该进程有效组ID或进程复述数组ID其中之一,并且进程没有超级用户权限,那么设置组ID会被清除。防止用户建立了一个设置组ID文件,而该文件是由非该用户所属的组拥有的

粘着位

对于文件:

  在之前旧的系统当中,若是一个程序文件一旦设置了粘着位,那么当该程序停止的时候他的全部指令段将被保存到系统的交换分区当中,再次运行时能够更快的调入系统.不过如今的操做系统已经再也不使用这种功能了.但这并不表示这功能已经彻底被废弃

对于目录:

  当一个目录设置为粘着位时,它将发挥特殊的做用,即当一个目录被设置为"粘着位"(用chmod a+t),则该目录下的文件只能由

  1. 超级管理员删除
  2. 该目录的全部者删除
  3. 该文件的全部者删除

  也就是说,即使该目录是任何人均可以写,但也只有文件的属主才能够删除文件

文件长度

  stat结构成员st_size表示以字节为单位的文件长度,此字段只对普通文件、目录文件和符号连接有意义。对于普通文件,其文件长度能够是0,在读这种文件时,将获得文件结束(end-of-file)指示。对于目录,文件长度一般是一个数(例如16或者512)的倍数。对于符号连接,文件长度是文件名中实际字节数。

  其中,文件长度7就是路径名usr/lib的长度。现在,大多数UNIX系统提供字段st_blksize和st_blocks。其中,第一个是对文件I/O较为合适的块长度,第二个时所分配的实际512字节块数量。

空洞文件

  空洞是由所设置的偏移量超过文件尾端,并写了某些数据后形成的。对于没有写过的字节位置,read函数读到的字节是0.若是使用实用程序(例如cat(1))复制这种文件,那么全部这些空洞都会被填满,其中全部实际数据字节皆填写为0.

文件系统

  咱们能够把一个磁盘分红一个或多个分区。每一个分区能够包含一个文件系统

  i节点是固定长度的记录项,它包含有关文件的大部分信息。若是更仔细的观察一个柱面组的i节点和数据块部分,则能够看到 以下图所示的状况
注意:
  1. 在图中有两个目录项指向同一个i节点。每一个i节点中都有一个连接计数,其值是指向该i节点的目录项数。只有当连接计数减小至0时,才可删除该文件(也就是能够释放该文件占用的数据块)。这就是为何“删除对一个文件的连接”操做并不老是意味着“释放该文件占用的磁盘块”的缘由。在stat结构中,连接计数包含在st_nlink成员中,其基本系统数据类型是nlink_t。这种连接类型称为硬连接,LINK_MAX指定了一个文件链接数的最大值。
  2. 另一种连接类型称为符号连接。对于这种连接,该文件的实际内容(在数据块中)包含了该符号连接所指向的文件的名字。在上面的例子中:该目录项中的文件名是3字符的字符串lib,而在该文件中包含了7个数据字节usr/lib。gaii节点中的文件类型是S_IFLNK,因而系统知道这是一个符号连接。
  3. i节点包含了大多数与文件有关的信息:文件类型、文件访问权限位、文件长度和指向该文件所占用的数据块的指针等。stat结构的大多数信息都是取自i节点。只有两项数据存放在目录项中:文件名和i节点编号。i节点编号的数据类型是ino_t
  4. 每一个文件系统各自对他们的i节点进行编号,所以目录项中的i节点编号数指向同一文件系统中的相应i节点,不能使一个目录项指向另外一个文件系统的i节点。

  5. 当在不更换文件系统状况下为一个文件改名时,该文件的实际内容并未移动,只需构造一个指向现有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。若是还有指向该文件的其余连接,则仍然能够经过其余连接访问该文件的数据。若是出错,怎不对该文件作任何更改。
  为了解除对文件的连接,必须对包含该目录项目录具备写和执行权限。若是对该目录设置了粘着位,则对该目录必须具备写权限,而且具有下面三个条件之一:

  1. 拥有该文件
  2. 拥有该目录
  3. 具备超级用户特权

  只有连接计数达到0时,该文件的内容才能够被删除。只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程数。若是该数达到0,而后内核检查其链接数,若是这个数也是0,那么就删除该文件的内容。

  unlink的这种性质常常被程序用来确保及时是在该程序奔溃时,他所建立的临时文件也不会遗留下来。进程用open或create建立一个文件,而后当即调用unlink。由于该文件仍旧是打开的,因此不会将其内容删除。只有当进程关闭该文件或终止时(在这种状况下,内核会关闭该进程打开的所有文件),该文件的内容才会被删除。
  若是pathname是符号连接,那么unlink删除该符号连接,而不会删除由该连接所引发的文件。给出符号连接名状况下,没有一个函数能删除该连接所引用的文件。

符号连接

  符号连接是指向一个文件的间接指针,它与硬连接有所不一样,硬连接直接指向文件的i节点。引入符号连接的缘由是为了避开硬连接的一些限制:

  1. 硬连接一般要求连接和文件位于同一文件系统中。
  2. 只有超级用户才能建立指向目录的硬连接。

  对符号连接以及他指向各类对象并没有任何文件系统限制,任何用户均可以建立指向目录的符号连接。符号连接通常用于将一个文件或整个目录结构移到系统中的另外一个位置。
  当使用以名字引用文件的函数时,应当了解该函数是否处理符号连接。也就是该函数是否跟随符号连接到达他所链接的文件。若是该函数具备处理符号连接的功能,则其路径名参数引用由符号连接所指向的文件。不然,路径名参数将引用连接自己,而不是该连接指向的文件。 

  下表列出了本章所说明的各个函数是否处理符号连接,表中没有列出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;
  在建立此符号连接时,并不要求actualpath已经存在。而且,actualpath和sympath并不须要位于同一文件系统中。由于open函数跟随符号连接,因此须要有一种方法打开连接自己,并读该连接中的名字。readlink函数提供了这种功能。
#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 %%\n", nreg, nreg * 100.0 / ntot);
    printf("directories    = %7ld, %5.2f %%\n", ndir, ndir * 100.0 / ntot);
    printf("block special  = %7ld, %5.2f %%\n", nblk, nblk * 100.0 / ntot);
    printf("char special   = %7ld, %5.2f %%\n", nchr, nchr * 100.0 / ntot);
    printf("FIFLs          = %7ld, %5.2f %%\n", nfifo, nfifo * 100.0 / ntot);
    printf("symbolic links = %7ld, %5.2f %%\n", nslink, nslink * 100.0 / ntot);
    printf("sockets        = %7ld, %5.2f %%\n", 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;
}
View Code

  下列例子为深度遍历文件层次结构

/*************************************************************************
    > 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,'\0',sizeof(str));
                strcpy(str,s);
                strcat(str,"/");
                strcat(str,d->d_name);
                ls_dirent(str);
            }
        }
        else
        {
            char dircwd[100];
            memset(dircwd,'\0',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;
}
View Code

GitHub:https://github.com/tianzengBlog/test/tree/master/test/dir

特殊设备文件

  st_dev和st_rdev这两个字段常常引发混淆,有关规则以下:

  1. 每一个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t。主设备号标识设备驱动程序,有时编码为于其通讯的外设扮;次设备号标识特定的子设备。
  2. 咱们一般使用两个宏:major和minor来访问主、次设备号,大多数实现都定义这两个宏。这就意味着咱们无需关心这两个数是如何存放在dev_t对象中的。
  3. 系统中与每一个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文件名以及与其对应的i节点。
  4. 只有字符特殊设备和块特殊设备才有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));
    }

文件访问权限位小结