UNIX环境高级编程 第四章 文件和目录

4.1 函数stat、fstat、fstatat、lstat

函数定义:

#include <sys/stat.h>

int stat(const char *restrict pathname, struct stst *restrict buf);//restrict是一个限定符,主要用来修饰指针指向的内存不能被别的指针引用。

int fstat(int fd, struct stst *buf);

int lstat(const char *restrict pathname, struct stat *restrict buf);

int fstatat(int fd, const char *restrict pathneme, struct stat *restrict buf, int flag);
//四个函数的返回值:若成功,返回0;若出错,返回-1

参数:
pathneme

  • stat函数将返回与此命名文件有关的信息结构。
  • fstat函数将返回已在描述符fd上打开文件的有关信息。
  • lstat函数类似于stat,但是当命名文件是一个链接时,lstat返回该链接的有关信息,而不是链接引用的文件的信息。
  • fstatat为一个相对于当前打开目录(由fd参数指向)的路径名返回文件统计信息。flag参数控制着是否跟随一个符号链接。

buf
参数buf是一个指针,它必须指向一个struct stat结构体对象,用来承接返回值。

4.2 文件类型

  • 普通文件:最常见的文件,对这个文件的解释由处理该文件的应用程序进行。
  • 目录文件:包含了其他文件的名字以及指向这些文件有关信息的指针。对目录文件具有读权限的任何一个进程都可以读该目录的内容,但是只有内核可以直接写目录文件。
  • 块特殊文件:该类型的文件提供对设备带缓冲的访问,每次以固定长度为单位进行。
  • 字符特殊文件:该类型文件提供对设备不带缓冲的访问,每次访问长度可变。(系统中的所有设备,要么是字符特殊文件,要么是块特殊文件)。
  • FIFO:用于进程间通信,与哦时也称文命名管道。
  • 套接字:用于进程间的网络通信,也可用于宿主机上进程的非网络通信。
  • 符号链接:这种类型的文件相当于指针,指向另一个文件。

程序读取命令行参数,然后针对每一个命令行参数打印文件类型:

#include <apue.h>

int main(int argc, char *argv[]) {
    struct stat buf;
    char *ptr;
    for(int i = 1; i < argc; i++){
        printf("%s: ", argv[i]);
        if(lstat(argv[i], &buf) < 0){
            err_ret("lstat error\n");
            continue;
        }
        if(S_ISREG(buf.st_mode)){
            ptr = "普通文件";
        }
        else if(S_ISDIR(buf.st_mode)){
            ptr = "目录文件";
        }
        else if(S_ISCHR(buf.st_mode)){
            ptr = "文字特殊文件";
        }
        else if(S_ISBLK(buf.st_mode)){
            ptr = "块特殊文件";
        }
        else if(S_ISFIFO(buf.st_mode)){
            ptr = "FIFO(管道)";
        }
        else if(S_ISLNK(buf.st_mode)){
            ptr = "符号链接";
        }
        else if(S_ISSOCK(buf.st_mode)){
            ptr = "套接字";
        }
        else{
            ptr = "知识盲区";
        }
        printf("%s\n", ptr);
    }
    exit(0);
}

在这里插入图片描述

4.3 设置用户ID和设置组ID

与一个进程相关的ID有6个或者更多

  • 实际用户ID和实际组ID标识我们究竟是谁
  • 有效用户ID、有效组ID和附属组ID决定了我们的文件访问权限
  • 保存的设置用户ID和保存的设置组ID在执行一个程序是包含了有效用户ID和有效组ID的副本

通常有效用户ID等于实际用户ID,有效组ID等于实际组ID。

在文件模式字中有两个特殊的位
设置用户ID位(set_user_ID):当执行此文件时,将执行此文件的进程的有效用户ID设置为文件所有者的用户ID。
设置组ID位(set_group_ID):当执行此文件文件时,将执行此文件的进程的有效组ID设置为文件的组所有者ID。

4.4 文件的访问权限

每个文件存在9个访问权限位,分为三组分别为用户、组和非用户与组成员的其他用户的权限,其中每一组中都有一个读、写和执行的权限

重点在于目录的访问权限,如果没有执行权限,则无法进入到目录中,如果没有读权限,无法读取目录中的信息,没有写权限,则无法创建或删除目录中的文件或目录。
创建或删除一个新文件需要同时对文件所在目录具有执行权限与写权限。

4.5 新文件和目录的所有权

  • 新文件的用户ID为进程的有效用户ID
  • 新文件的组ID有两种选择,分别为进程的有效组ID或者它所在目录的组ID

4.6 函数access和faccessat

access和faccessat按照实际用户ID和实际组ID进行访问权限测试

#include <unistd.h>

int access(const char *pathname, int mode);

int faccessat(int fd, const *pathname, int mode, in tflag);
//两个函数的返回值:若成功,返回0;若失败,返回-1

access函数的mode标志

mode说明
R_OK测试读权限
W_OK测试写权限
X_OK测试执行权限

faccessat在pathname参数为绝对路径fd参数取值为AT_FDCWD而pathname为相对路径时与access函数相同,否则faccessat计算相对于fd参数指向的打开目录的pathname
flag参数可以用于改变faccessat函数的行为,如果法拉格设置为AT_EACCESS,访问检查用的是调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID。

access函数使用方法:

#include <iostream>
#include <apue.h>
#include <fcntl.h>

using namespace std;

int main(int argc, char *argv[]) {
    if(argc != 2){
        cerr << "usage: a.out <pathname>";
    }
    if(access(argv[1], R_OK) < 0){
        err_ret("access error for %s", argv[1]);
    }else{
        cout << "read access OK\n";
    }

    if(open(argv[1], O_RDONLY) < 0){
        err_ret("open error for %s", argv[1]);
    }else{
        cout << "open for reading OK\n";
    }
    return 0;
}

在这里插入图片描述

函数umask

文件模式创建屏蔽字决定文件创建时的默认权限。
每个进程都有一个单独的跟进程相关联的文件模式创建屏蔽字(file creation mask). 每个进程的文件模式创建屏蔽字跟该进程的父进程没有关系,不继承于父进程的文件模式创建屏蔽字,本进程文件模式创建屏蔽字变更对父进程的文件模式进程也不会产生任何影响。

在进程创建一个新文件时,会通过文件模式创建屏蔽字和创建新文件时指定的文件访问权限位一起决定新文件最终的文件访问权限。对于任何在文件模式创建屏蔽字中为 1 的位,在文件 mode 中的相对应位则一定被关闭。

umask函数为进程设置文件模式创建屏蔽字,并返回之前的值(无出错返回值)

#include <sys/stat.h>

mode_t umask(mode_t cmask);
//返回值:之前的文件模式创建屏蔽字

参数cmask是9个范文权限常量中的若干为“或”构成的。

umask函数实例:

#include <apue.h>
#include <fcntl.h>

#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)

int main(void) {
    umask(0);
    if(creat("foo", RWRWRW) < 0){
        err_sys("create error for foo");
    }
    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    if(creat("bar", RWRWRW) < 0){
        err_sys("creat error for bar");
    }
    return 0;
}

在这里插入图片描述
更改进程的文件模式创建屏蔽字不影响父进程的屏蔽字

在shell中更改屏蔽字:
在这里插入图片描述

4.8 函数chmod、fchmod和fchmodat

上述三个函数可以帮我们修改现有文件的访问权限

#include <sys/stat.h>

int chomd(const char *pathname, mode_t mode);

int fchmod(int fd, mode_t mode);

int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
//3个函数的返回值:若成功,返回0;若失败,返回-1
  • chmod在指定文件上进行修改访问权限操作。
  • fchmod在fd所指的打开的文件上进行修改访问权限操作。
  • fchmodat在pathname为绝对路径或者fd取值为AT_FDCWD且pathname为相对路径时与chmod相同。其它条件下,计算相对于打开目录fd的pathname。flag参数限制fchmodat是否跟随符号链接。

参数mode是下表所示常量的按位或

mode说明
S_ISUID执行时设置用户ID
S_ISGID执行时设置组ID
S_ISVTX保存正文(黏着位)
S_IRWXU用户(所有者)读、写和执行
S_IRUSR用户(所有者)读
S_IWUSR用户(所有者)写
S_IXUSR用户(所有者)执行
S_IRWXG组(所有者)读、写和执行
S_IRGRP组(所有者)读
S_IWGRP组(所有者)写
S_IXGRP组(所有者)执行
S_IRWXO其他(所有者)读、写和执行
S_IROTH其他(所有者)读
S_IWOTH其他(所有者)写
S_IXOTH其他(所有者)执行

chmod函数实例:

#include <apue.h>

int main() {
    struct stat statbuf;
    //打开set_group_ID并且关闭group-execute
    if(stat("foo", &statbuf) < 0){
        err_sys("对于文件foo函数stat出错");
    }
    if(chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0){
        err_sys("对于文件foo函数chmod出错");
    }

    //设置mode为"rw-r--r--"
    if(chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0){
        err_sys("对于文件bar函数chmod出错");
    }
    return 0;
}

在这里插入图片描述

4.9 黏着位

当一个目录设置了黏着位,只有对目录具有写权限的用户并且为文件或目录的拥有着或者为root用户,才可以删除或重命名该目录下的文件。

4.10 函数chown、fchown、fchownat和lchown

四个chown函数可用于更改文件中的用户ID和组ID。如果两个参数owner或geoup任意一个为-1,则对应的ID不变。

#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int fchownat(int fd, const char *pathname, uid_t owner,gid_t group, int flag);
int lchown(const char *pathname, uid_t owner, gid_t group);
//4个函数的返回值:若成功,返回0;若出错,返回-1

4.11 文件长度

stat结构成员st_size表示以字节为单位的文件的长度。次子段支队普通文集爱你、目录文件和符号链接有意义。

  • 普通文件:长度可以为0,在读这种文件时,将得到文集爱你结束指示。
  • 目录:长度通常为一个数的整倍数。
  • 符号链接:文件长度是在文件名中的实际字节数。(由于符号链接文件长度总是由st_size指示,所以不包含C语言用作名字结尾的null字节)
文件中的空洞

空洞是由所设置的偏移量超过文件尾端,并写入了某些数据后造成的。
从原来的文件结尾到新写入数据间的这段空间被成为文件空洞。

4.12 文件截断

从文件尾端处截去一些数据以缩短文件。将文件的长度截断为0是一个特例。
调用函数truncate和ftruncate截断文件。

#include <unistd.h>

int truncate(const char *pathname, off_t length);

int ftruncate(int fd, off_t length);
//两个函数的返回值:若成功,返回iu0;若出错,返回-1;

这两个函数将现有的文件长度截断为length。如果该文件以前的长度大于length,则超过length以外的数据将不再能访问。如果以前的长度小于length,文件长度将增加,以前的文件尾端与新的尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞)。

文件系统

把磁一个磁盘分成一个或多个分区,每一个分区可以包含一个文件系统。
i节点(inode)是固定长度的记录项,它包含了有关文件的大部分信息。

在这里插入图片描述
由上图可知,每一个磁盘可以分为一个或多个分区,每一个分区都可以有自己的文件系统。
每个文件系统由自举块、超级块和多个柱面组组成。

  • 自举块:也称为引导块,分区中自身引导程序存放的位置。
  • 超级块:位于文件系统的根上,用来描述和维护文件系统的状态

在这里插入图片描述

  • 每一个i节点都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少至0时,才可以删除该文件。例:若有一个目录A,目录本身的“. 目录块”与目录中的字目录的“ … 目录块同时指向该目录的i节点”。(使用i节点的链接称为硬链接,可以将重要文件设置多个硬链接,防止误删)
  • 符号链接文件的内容包含了该文件指向的文件的名字,被称作软链接。
  • i节点包含了与文件有关的所有信息:文集爱你类型,文件访问权限位,文件长度和指向文件数据块的指针等。
  • 在不更换文件系统的情况下,为一个文件重命名时,只是构造了一个指向现有i节点的目录项,并删除原目录项,并没有移动文件的实际内容,剪切粘贴也是相同的道理。

在这里插入图片描述

上图为创建了目录testdir后的文件系统状态。
2549为testdir目录。

4.14 函数link、linkat、unlink、unlinkat和remove

link、linkat

任何一个文件可以有多个目录指向i节点。创建一个指向现有文件的链接的方法是使用link或linkat函数。

#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

两个函数都是引用现有文件existingpath来创建新的目录项,如果newpath已经存在了,那么返回-1。创建的过程中,只创建newpath的最后一个分量,其他部分应当已经存在,依旧是说,不能创建多级目录。

创建新目录和增加链计数,应当是一个原子操作。

unlink、unlinkat

为删除一个现有的目录项,可以调用unlink函数。

#include <uniste.h>

int unlink(const char *pathname);

int unlinkat(int fd, const char *pathname, int flag);
//两个函数的返回值:若成功,返回0;若出错,返回-1

只有当链接计数达到0时,该文件的内容才可以被删除。若有进程打开了该文件,其内容也不会删除。

删除一个文件时,内核首先检查打开文件的进程个数,如果这个计数达到0,内核再去检查链接计数,如果计数也是0,才会删除该文件的内容。

打开一个文件,然后解除它的链接,执行该程序的进程然后沉睡15秒:

#include <iostream>
#include <apue.h>
#include <fcntl.h>

using namespace std;

int main() {
    if(open("tempfile", O_RDWR) < 0){
        err_sys("open error");
    }
    if(unlink("tempfile") < 0){
        err_sys("unlink error");
    }
    cout << "file unlinked\n";
    sleep(15);
    cout << "down\n";
    return 0;
}

在这里插入图片描述

进程用open或create创建一个临时文件,然后立即调用unlink,因为该文件依旧是打开的,所以不会将内容删除,只有将进程关闭该文件或进程终止时,该文件的内容才会被删除。

4.15 remove

解除对一个文件或目录的链接。对于文件remove功能与unlink相同;对于目录,remove功能与rmdir相同。

#include <stdio.h>

int remove(const char *pathname);
//返回值:若成功,返回0;若出错,返回-1

函数rename和renameat

文件或目录可以使用rename函数或者renameat函数进行重命名

#include <stdio.h>

int rename(const char * oldname, const char *new name);

int renameat(int oldfd, const char *oldname, int newfd, const char *newname);
//两个函数的返回值:若成功,返回0;若出错返回-1
  • 如果oldname指的时一个文件而不是目录,那么为该文件或符号链接重命名。在这个情况下,如果newname已经存在,并且它不是一个目录的话,首先将该目录项删除,然后oldname重命名位newname。(通俗讲就是,如果将a改名为b,这里的名字即为path,若b已经存在,首先将b删除,然后对a进行重命名)对包含oldname以及newname的目录,调用进程必须具有写权限,因为要同时更改两个目录。

  • 如若oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,而且该目录应当是空目录(空目录指的是该目录中只有.和…项)。如果 newname存在(而且是一个空目录),则先将其删除,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀。例如,不能将/usr/foo重命名为/usr/foo/testdir,因为旧名字(/usr/foo)新名字的路径前缀,因而不能将其删除

  • 如若oldname或newname引用符号链接,则处理的是符号链接本身,而不是它所引用的文件。

  • 不能对.和…重命名。更确切地说,.和…都不能出现在oldname和newname的最后部分。

  • 作为一个特例,如果oldname和newname引用同一文件,则函数不做任何更改而成功返回。

如若newname已经存在,则调用进程对它需要有写权限(如同删除情况一样)。另外,调用进程将删除oldname目录项,并可能要创建newname目录项,所以它需要对包含oldname及包含newname的目录具有写和执行权限。

除了当oldname与newname指向相对路径名时,其他情况下,renameat和rename功能相同。如果oldname参数指定了相对路径,相对于oldfd所指的目录进行计算。同理,newname针对newfd进行计算。(两个fd参数若为AT_FDCWD表示当前目录)

4.16 符号链接

符号链接时对一个文件的间接指针,它是不同于硬链接的软链接,引入符号链接的原因是为了避开硬链接的一些限制。

  • 硬链接通常要求链接和文件位于同一文件系统中。
  • 只有超级用户才能创建指向目录的硬链接。

任何用户都可以创建指向目录的符号链接。
符号链接通常用于将一个文件或整个目录结构移动到系统中的另一位置。

使用符号链接可能在文件系统中引入循环。

4.17 创建和读取符号链接

可以用symlink或symlinkat函数创建一个符号链接

#include <unistd.h>

int symlink(const char *actualpath, const char *sympath);

int symlinkat(const char *actualpath, int fd, const char *sympath);
//两个函数的返回值:若成功,返回0;若出错,返回-1

函数创建了一个指向actualpath的新目录sumpath。创建此符号链接时,并不要求actualpath已经存在。而且两个path并不需要位于同一个文件系统中。
symlinkat计算相对路径的情况。

因为open函数跟随符号链接,所以需要一种方法打开链接本身并读取该链接中的名字,readlink和readlinkat函数提供了这种功能。

#include <unistd.h>

ssize_t readlink(const char *restrict pathname, char *rrestrict buf, size_t bufsize);

ssize_t readlinkat(int fd, const char * restrick pathname, char *restrict buf,  size_t bufsize);
//两个函数的返回值:若成功,返回读取的字节数;若出错,返回-1

两个函数组合了open、read和close的所有操作。如果函数成功执行,则返回读入buf的字节数,在buf中返回的符号链接的内容不以null字节终止。

4.18 文件的时间

对每个文件维护三个时间字段:

字段说明ls(1)选项
st_atim文件数据的最后访问时间-u
st_mtim文件数据的最后修改时间默认
st_ctimi节点状态的最后更改时间-c

系统不维护对一个i节点的最后一次访问时间,所以access和stat函数并不会更改三个时间中的任何一个。

4.19 函数futimens、utimensat和utimes

一个文件的访问和修改时间可以用以下几个函数修改。futimens和utimensat函数可以指定纳秒级精度的时间戳。用到的数据结构是与stat函数族相同的timespec结构

#include <sys/stat.h>

int futimens(int fd, const struct timespece time[2]);

itn utimenast(int fd, const char *path, const struct timespece time[2], int flag);
//两个函数的返回值:若成功,返回0;若出错,返回-1

两个函数的timestimes叔祖参数的第一个元素包含访问时间,第二个元素包含修改时间。这两个时间值是日历时间。
时间戳可以按照下列四种方式之一进行指定。

  • 如果time参数是一个空指针,则访问时间和修改时间两者都设置为当前时间。
  • 如果time参数指向两个timespece结构的数组,任一数组元素的tv_nsec字段的值为UTIME_NOW,相应的时间戳设置为当前时间,忽略相应的tv_sec字段。
  • 如果time参数指向两个timespece结构的数组,任一数组元素的tv_nsec字段的值为UTIME_OMIT,相应的时间戳保持不变,忽略相应的tv_sec字段。
  • 如果time参数指向两个timespece结构的数组,任一数组元素的tv_nsec字段的值既不是UTIME_NOW也不是UTIME_OMIT,这种情况下相应的时间戳就设置为相应的tv_sec和tv_nsec的值。

utimes对路径名进行操作

#include <sys/time.h>

int utimes(const char *pathname, const struct timeval times[2]);
//返回值:若成功,返回0;若出错,返回-1

使用带O_TRUNC选项的open函数将文件长度截断为0,但并不更改其访问时间和修改和修改时间。为了做到这一点,首先用stat函数得到这些时间,然后截断文件,最后futimens函数重置这两个时间

#include <iostream>
#include <apue.h>
#include <fcntl.h>

using namespace std;

int main(int argc, char *argv[]) {
    int fd;
    struct stat statbuf;
    struct timespec times[2];
    for(int i = 1; i < argc; i++){
        if(stat(argv[i], &statbuf) < 0){
            err_ret("%s: stat error", argv[i]);
            continue;
        }
        if((fd = open(argv[i], O_RDWR | O_TRUNC)) < 0){
            err_ret("%s: stat error", argv[i]);
        }

        times[0] = statbuf.st_atim;
        times[1] = statbuf.st_mtim;
        if(futimens(fd, times) < 0){
            err_ret("%s: futimens error", argv[i]);
        }
        close(fd);
    }
    return 0;
}

在这里插入图片描述

最后修改时间和最后访问时间未改变,但是状态更改时间改为程序运行时的时间。

4.20函数mkdir,mkdirat和rmdir

用mkdir和mkdirat创建目录

#include <sys/stat.h>

int mkdir(const char *pathname, mode_t mode);

int mkdirat(int fd, const char *pathname, mode)t mode);

这两个函数创建一个新目录,其中. 和. .目录是自动创建的。所指定的文件访问权限mode由进程的文件模式创建屏蔽字。

rmdir可以删除一个空目录,空目录是值包含 . 和 . .这两项的目录。

#include <qunistd.h>

int rmdir(const char *pathname);
//返回值:若成功,返回0;若出错,反回-1

4.21 读目录

权限:
对某个目录具有访问权限的任一用户都可以读该目录,不过,为了防止文件系统产生混乱,只有内核才可以写目录。

#include <dirent.h>

DIR *opendir(const char *pathname);

DIR *fdopendir(int fd);
//两个函数返回值:若成功,返回指针;若出错,返回NULL

struct dirent *readdir(DIR *dp);
//函数返回值:若成功,返回指针;若在目录尾或出错,返回NULL

viod rewinddir(DIR *dp);

int closedir(DIR *dp);
//返回值:若成功,返回0;若出错,返回-1

long telldir(DIR *dp);
//返回值:与目录关联的目录中的当前位置

void seekdir(DIR *dp, long loc);
  • 由opendir和fdopendir返回的指向DIR结构的指针,供另外5个函数调用。
  • opendir执行初始化操作,使第一个readdir返回目录中的第一个目录项。DIR结构由fdopendir创建时,readdir返回的第一项取决于传给fdopendir函数的文件描述符相关联的文件偏移量。

递归降序遍历目录层次结构,并按照文件类型计数:

#include <iostream>
#include <apue.h>
#include <dirent.h>
#include <limits.h>
#include <stdlib.h>

using namespace std;

char* path_alloc(size_t* size)
{
    char *p = NULL;
    if(!size) return NULL;
    p = (char *)malloc(256);
    if(p)
        *size = 256;
    else
        *size = 0;
    return p;
}

/*
 *functon type that is callerd 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);
    ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock;
    if (ntot == 0){
        ntot = 1;
    }
    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("FIFOs          = %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);
    return 0;
}

/*
 * Desend throw the hierarchy, starting at pathname
 * The caller's func() is called for every file
 * 从路径名开始层次遍历,每一个文件都会调用函数func()
 */

const int FTW_F = 1;//是文件而不是目录
const int FTW_D = 2;//是目录
const int FTW_DNR = 3;//是目录但是无法进行读操作
const int FTW_NS = 4;//未知文件

static char *fullpath;
static size_t pathlen;

static int myftw(char *pathname, Myfunc *func){
    fullpath = path_alloc(&pathlen);//malloc PAATH_MAX+1 bytes
    if(pathlen <= strlen(pathname)){
        pathlen = strlen(pathname)*2;
        if((fullpath = (char *)realloc(fullpath, pathlen)) == NULL) {
            err_sys("realloc filed");
        }
    }
    strcpy(fullpath, pathname);
    return (dopath(func));
}

/*
 * 从fullpath开始进行层次下降
 * 如果fullpath是目录以外的东西,我们调用lstat()
 * 调用func()然后返回,对于目录,调用自身
 * 递归目录中的每个名称
 */

static int dopath(Myfunc* func){
    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));
    }
    /*
     * 如果一个目录,首先为这个目录调用func()
     * 然后遍历目录中的所有文件名
     */
    if((ret = func(fullpath, &statbuf, FTW_D)) != 0){
        return 0;
    }
    n = strlen(fullpath);
    if(n+NAME_MAX+2 > pathlen){
        pathlen *= 2;
        if((fullpath = (char *)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;
        strcpy(&fullpath[n], dirp->d_name);//append name after "/"
        if((ret = dopath(func)) != 0) {
            break;
        }
    }
    fullpath[n-1] = 0;//erase everything fromr 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:
                    err_dump("for S_IFDIR for %s", pathname);
                    break;
            }
            break;
        case FTW_D:
            ndir++;
            break;
        case FTW_DNR:
            err_ret("Can't read directory %s", pathname);
        case FTW_NS:
            err_ret("stat error for %s", pathname);
            break;
        default:
            err_dump("unknown type %d for pathname %s", type, pathname);
    }
    return 0;
}

在这里插入图片描述

4.22 函数chardir、fchdir和getcwd

每一个进程都有一个当前工作目录,此目录是搜索所有相对路径的起点。
进程调用chdir或fchdir函数可以更改当前工作目录。

#include <unistd.h>

int chdir(const char *pathname);

int chdir(fd);
//两个函数的返回值:若成功,返回0;若出错,返回-1

在这两个函数中,分别用pathname和打开文件描述符来指定新的当前工作目录。

chdir函数实例:

#include <iostream>
#include <apue.h>

using namespace std;

int main() {
    if(chdir("/tmp") < 0)
        err_sys("chdir failed");
    cout << "chdir to /tmp succeeded\n";
    return 0;
}

当前工作目录是进程的一个属性,所以只会影响chdir的进程本身(shell的子进程),不会影响shell
在这里插入图片描述

函数getcwd,获取当前工作目录
它从当前工作目录 . 开始,用 . . 找到其上一级目录,然后读其目录项,直到该目录项中i节点与工作目录i节点编号相同,这样就找到了对应的文件名,按照这种方法逐层上移,直到遇到根

#include <unistd.h>

char *getcwd(char *buf, size_t size);
//返回值:若成功,返回buf;若出错,返回NULL

buf必须具有足够的长度,以容纳绝对路径名加上终止null字节

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值