The C Programming Language(第 2 版) 笔记 / 8 UNIX 系统接口 / 8.6 实例(目录列表)

目录、参考文献


8.6 实例(目录列表)

我们常常还需要对文件系统执行另一种操作,以获得文件的有关信息,而不是读取文件的具体内容
目录列表程序便是其中的一个例子,比如 UNIX 命令 ls,它打印一个目录中的文件名以及其它一些可选信息,如文件长度、访问权限等等
MS-DOS 操作系统中的 dir 命令也有类似的功能

由于 UNIX 中的目录就是一种文件,因此,ls 只需要读此文件就可获得所有的文件名
但是,如果需要获取文件的其它信息,比如长度等,就需要使用系统调用
在其它一些系统中,甚至获取文件名也需要使用系统调用,例如在 MS-DOS 系统中即如此
无论实现方式是否同具体的系统有关,我们需要提供一种与系统无关的访问文件信息的途径

以下将通过程序 fsize 说明这一点
fsize 程序是 ls 命令的一个特殊形式,它打印命令行参数表中指定的所有文件的长度
如果其中一个文件是目录,则 fsize 程序将对此目录递归调用自身
如果命令行中没有任何参数,则 fsize 程序处理当前目录

我们首先回顾 UNIX 文件系统的结构
在 UNIX 系统中,目录就是一种文件,这种文件包含了一个文件名列表以及指示这些文件位置的信息
“ 位置 ” 是一个指向其它表(即 inode 表)的索引
文件的 inode 是存放除文件名以外的所有文件信息的地方
目录项(directory entry)通常仅包含两个条目:文件名和 inode 编号

在不同版本的系统中,目录的格式和确切的内容是不一样的
因此,为了分离出不可移植的部分,我们把任务分成两部分
外层定义了一个称为 Dirent 的结构和 3 个函数 opendirreaddirclosedir
它们提供与系统无关的对目录项中的名字和 inode 编号的访问
我们将利用此接口编写 fsize 程序,然后说明如何在与 Version 7 和 System V UNIX 系统的目录结构相同的系统上实现这些函数
其它情况留作练习

结构 Dirent 包含 inode 编号和文件名
文件名的最大长度由 NAME_MAX 设定,NAME_MAX 的值由系统决定
opendir 返回一个指向称为 DIR 的结构的指针,该结构与结构 FILE 类似,它将被 readdirclosedir 使用
所有这些信息存放在头文件 dirent.h

#define NAME_MAX 14 /* longest filename component; */
                            /* system-dependent */

typedef struct { /* portable directory entry */ 
    long ino; /* inode number */ 
    char name[NAME_MAX+1]; /* name + '\0' terminator */ 
} Dirent;

typedef struct { /* minimal DIR: no buffering, etc. */ 
    int fd; /* file descriptor for the directory */ 
    Dirent d; /* the directory entry */ 
} DIR;

DIR *opendir(char *dirname); 
Dirent *readdir(DIR *dfd); 
void closedir(DIR *dfd);

系统调用 stat 以文件名作为参数,返回文件的 inode 中的所有信息,若出错,则返回 -1

char *name; 
struct stat stbuf; 
int stat(char *, struct stat *); 
stat(name, &stbuf);

它将文件名为 name 的文件的 inode 信息填入结构 stbuf
描述 stat 返回值的结构定义,包含在头文件 <sys/stat.h>
该结构的一个典型形式如下所示:

struct stat /* inode information returned by stat */ 
{ 
    dev_t st_dev; /* device of inode */ 
    ino_t st_ino; /* inode number */ 
    short st_mode; /* mode bits */ 
    short st_nlink; /* number of links to file */ 
    short st_uid; /* owners user id */ 
    short st_gid; /* owners group id */ 
    dev_t st_rdev; /* for special files */ 
    off_t st_size; /* file size in characters */ 
    time_t st_atime; /* time last accessed */ 
    time_t st_mtime; /* time last modified */ 
    time_t st_ctime; /* time originally created */ 
};

dev_tino_t 等类型在头文件 <sys/types.h> 中定义,程序中必须包含此文件

st_mode 项包含了描述文件的一系列标志,这些标志在 <sys/stat.h> 中定义
我们只需要处理文件类型的有关部分:

#define S_IFMT 0160000 /* type of file: */ 
#define S_IFDIR 0040000 /* directory */ 
#define S_IFCHR 0020000 /* character special */
#define S_IFBLK 0060000 /* block special */ 
#define S_IFREG 0010000 /* regular */ 
/* ... */

下面我们来着手编写程序 fsize
如果由 stat 调用获得的模式说明某文件不是一个目录,就很容易获得该文件的长度,并直接输出
但是,如果文件是一个目录,则必须逐个处理目录中的文件
由于该目录可能包含子目录,因此该过程是递归的

主程序 main 处理命令行参数,并将每个参数传递给函数 fsize

#include <stdio.h> 
#include <string.h> 
#include "syscalls.h" 
#include <fcntl.h> /* flags for read and write */ 
#include <sys/types.h> /* typedefs */ 
#include <sys/stat.h> /* structure returned by stat */ 
#include "dirent.h"

void fsize(char *)

/* print file name */ 
main(int argc, char **argv) 
{ 
    if (argc == 1) /* default: current directory */ 
        fsize("."); 
    else 
        while (--argc > 0) 
            fsize(*++argv); 
    return 0; 
}

函数 fsize 打印文件的长度
但是,如果此文件是一个目录,则 fsize 首先调用 dirwalk 函数处理它所包含的所有文件
注意如何使用文件 <sys/stat.h> 中的标志名 S_IFMTS_IFDIR 来判定文件是不是一个目录
括号是必须的,因为 & 运算符的优先级低于 == 运算符的优先级

int stat(char *, struct stat *); 
void dirwalk(char *, void (*fcn)(char *));

/* fsize: print the name of file "name" */ 
void fsize(char *name) 
{ 
    struct stat stbuf;
    
    if (stat(name, &stbuf) == -1) { 
        fprintf(stderr, "fsize: can't access %s\n", name); 
        return; 
    } 
    if ((stbuf.st_mode & S_IFMT) == S_IFDIR) 
        dirwalk(name, fsize); 
    printf("%8ld %s\n", stbuf.st_size, name); 
}

函数 dirwalk 是一个通用的函数,它对目录中的每个文件都调用函数 fcn 一次
它首先打开目录,循环遍历其中的每个文件,并对每个文件调用该函数,然后关闭目录返回
因为 fsize 函数对每个目录都要调用 dirwalk 函数,所以这两个函数是相互递归调用的

#define MAX_PATH 1024

/* dirwalk: apply fcn to all files in dir */ 
void dirwalk(char *dir, void (*fcn)(char *)) 
{ 
    char name[MAX_PATH]; 
    Dirent *dp; 
    DIR *dfd;
    
    if ((dfd = opendir(dir)) == NULL) { 
        fprintf(stderr, "dirwalk: can't open %s\n", dir); 
        return; 
    } 
    while ((dp = readdir(dfd)) != NULL) { 
        if (strcmp(dp->name, ".") == 0 || strcmp(dp->name, "..")) 
            continue; /* skip self and parent */ 
        if (strlen(dir)+strlen(dp->name)+2 > sizeof(name)) 
            fprintf(stderr, "dirwalk: name %s %s too long\n", dir, dp->name); 
        else { 
            sprintf(name, "%s/%s", dir, dp->name); 
            (*fcn)(name); 
        } 
    } 
    closedir(dfd); 
}

每次调用 readdir 都将返回一个指针,它指向下一个文件的信息
如果目录中已没有待处理的文件,该函数将返回 NULL
每个目录都包含自身 . 和父目录 .. 的项目,在处理时必须跳过它们,否则将会导致无限循环

到现在这一步为止,代码与目录的格式无关
下一步要做的事情就是在某个具体的系统上提供一个 opendirreaddirclosedir 的最简单版本
以下的函数适用于 Version 7 和 System V UNIX 系统,它们使用了头文件 <sys/dir.h> 中的目录信息:

#ifndef DIRSIZ 
#define DIRSIZ 14 
#endif

struct direct { /* directory entry */ 
    ino_t d_ino; /* inode number */ 
    char d_name[DIRSIZ]; /* long name does not have '\0' */ 
};

某些版本的系统支持更长的文件名和更复杂的目录结构

类型 ino_t 是使用 typedef 定义的类型,它用于描述 inode 表的索引
在我们通常使用的系统中,此类型为 unsigned short,但是这种信息不应在程序中使用
因为不同的系统中该类型可能不同,所以使用 typedef 定义要好一些
所有的 “ 系统 ” 类型可以在文件 <sys/types.h> 中找到

opendir 函数首先打开目录,验证此文件是一个目录(调用系统调用 fstat,它与 stat 类似,但它以文件描述符作为参数)
然后分配一个目录结构,并保存信息:

int fstat(int fd, struct stat *);

/* opendir: open a directory for readdir calls */ 
DIR *opendir(char *dirname) 
{ 
    int fd; 
    struct stat stbuf; 
    DIR *dp;
    
    if ((fd = open(dirname, O_RDONLY, 0)) == -1 
        || fstat(fd, &stbuf) == -1 
        || (stbuf.st_mode & S_IFMT) != S_IFDIR 
        || (dp = (DIR *) malloc(sizeof(DIR))) == NULL) 
        return NULL; 
    dp->fd = fd; 
    return dp; 
}

closedir 函数用于关闭目录文件并释放内存空间:

/* closedir: close directory opened by opendir */ 
void closedir(DIR *dp) 
{ 
    if (dp) { 
        close(dp->fd); 
        free(dp); 
    } 
}

最后,函数 readdir 使用 read 系统调用读取每个目录项
如果某个目录位置当前没有使用(因为删除了一个文件),则它的 inode 编号为 0,并跳过该位置
否则,将 inode 编号和目录名放在一个 static 类型的结构中,并给用户返回一个指向此结构的指针
每次调用 readdir 函数将覆盖前一次调用获得的信息

#include <sys/dir.h> /* local directory structure */

/* readdir: read directory entries in sequence */ 
Dirent *readdir(DIR *dp) 
{ 
    struct direct dirbuf; /* local directory structure */ 
    static Dirent d; /* return: portable structure */
    
    while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf)) { 
        if (dirbuf.d_ino == 0) /* slot not in use */ 
            continue; 
        d.ino = dirbuf.d_ino; 
        strncpy(d.name, dirbuf.d_name, DIRSIZ); 
        d.name[DIRSIZ] = '\0'; /* ensure termination */ 
        return &d; 
    } 
    return NULL; 
}

尽管 fsize 程序非常特殊,但是它的确说明了一些重要的思想
首先,许多程序并不是 “ 系统程序 ”,它们仅仅使用由操作系统维护的信息
对于这样的程序,很重要的一点是,信息的表示仅出现在标准头文件中,使用它们的程序只需要在文件中包含这些头文件即可,而不需要包含相应的声明
其次,有可能为与系统相关的对象创建一个与系统无关的接口
标准库中的函数就是很好的例子


目录、参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值