目录、参考文献
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 个函数 opendir、readdir 和 closedir
它们提供与系统无关的对目录项中的名字和 inode 编号的访问
我们将利用此接口编写 fsize 程序,然后说明如何在与 Version 7 和 System V UNIX 系统的目录结构相同的系统上实现这些函数
其它情况留作练习
结构 Dirent 包含 inode 编号和文件名
文件名的最大长度由 NAME_MAX 设定,NAME_MAX 的值由系统决定
opendir 返回一个指向称为 DIR 的结构的指针,该结构与结构 FILE 类似,它将被 readdir 和 closedir 使用
所有这些信息存放在头文件 dirent.h 中
#define NAME_MAX 14
typedef struct {
long ino;
char name[NAME_MAX+1];
} Dirent;
typedef struct {
int fd;
Dirent d;
} 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 返回值的结构定义,包含在头文件
该结构的一个典型形式如下所示:
struct stat
{
dev_t st_dev;
ino_t st_ino;
short st_mode;
short st_nlink;
short st_uid;
short st_gid;
dev_t st_rdev;
off_t st_size;
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
};
dev_t 和 ino_t 等类型在头文件
st_mode 项包含了描述文件的一系列标志,这些标志在
我们只需要处理文件类型的有关部分:
#define S_IFMT 0160000 #define S_IFDIR 0040000 #define S_IFCHR 0020000 #define S_IFBLK 0060000 #define S_IFREG 0010000
下面我们来着手编写程序 fsize
如果由 stat 调用获得的模式说明某文件不是一个目录,就很容易获得该文件的长度,并直接输出
但是,如果文件是一个目录,则必须逐个处理目录中的文件
由于该目录可能包含子目录,因此该过程是递归的
主程序 main 处理命令行参数,并将每个参数传递给函数 fsize
#include#include #include "syscalls.h" #include #include #include #include "dirent.h" void fsize(char *) main(int argc, char **argv) { if (argc == 1) fsize("."); else while (--argc > 0) fsize(*++argv); return 0; }
函数 fsize 打印文件的长度
但是,如果此文件是一个目录,则 fsize 首先调用 dirwalk 函数处理它所包含的所有文件
注意如何使用文件
括号是必须的,因为 & 运算符的优先级低于 == 运算符的优先级
int stat(char *, struct stat *);
void dirwalk(char *, void (*fcn)(char *));
void fsize(char *name)
{
struct stat stbuf;
if (stat(name, &stbuf) == -1) {
fprintf(stderr, "fsize: can't access %sn", name);
return;
}
if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
dirwalk(name, fsize);
printf("%8ld %sn", stbuf.st_size, name);
}
函数 dirwalk 是一个通用的函数,它对目录中的每个文件都调用函数 fcn 一次
它首先打开目录,循环遍历其中的每个文件,并对每个文件调用该函数,然后关闭目录返回
因为 fsize 函数对每个目录都要调用 dirwalk 函数,所以这两个函数是相互递归调用的
#define MAX_PATH 1024
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 %sn", dir);
return;
}
while ((dp = readdir(dfd)) != NULL) {
if (strcmp(dp->name, ".") == 0 || strcmp(dp->name, ".."))
continue;
if (strlen(dir)+strlen(dp->name)+2 > sizeof(name))
fprintf(stderr, "dirwalk: name %s %s too longn", dir, dp->name);
else {
sprintf(name, "%s/%s", dir, dp->name);
(*fcn)(name);
}
}
closedir(dfd);
}
每次调用 readdir 都将返回一个指针,它指向下一个文件的信息
如果目录中已没有待处理的文件,该函数将返回 NULL
每个目录都包含自身 . 和父目录 .. 的项目,在处理时必须跳过它们,否则将会导致无限循环
到现在这一步为止,代码与目录的格式无关
下一步要做的事情就是在某个具体的系统上提供一个 opendir、readdir 和 closedir 的最简单版本
以下的函数适用于 Version 7 和 System V UNIX 系统,它们使用了头文件
#ifndef DIRSIZ
#define DIRSIZ 14
#endif
struct direct {
ino_t d_ino;
char d_name[DIRSIZ];
};
某些版本的系统支持更长的文件名和更复杂的目录结构
类型 ino_t 是使用 typedef 定义的类型,它用于描述 inode 表的索引
在我们通常使用的系统中,此类型为 unsigned short,但是这种信息不应在程序中使用
因为不同的系统中该类型可能不同,所以使用 typedef 定义要好一些
所有的 “ 系统 ” 类型可以在文件
opendir 函数首先打开目录,验证此文件是一个目录(调用系统调用 fstat,它与 stat 类似,但它以文件描述符作为参数)
然后分配一个目录结构,并保存信息:
int fstat(int fd, struct stat *);
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 函数用于关闭目录文件并释放内存空间:
void closedir(DIR *dp)
{
if (dp) {
close(dp->fd);
free(dp);
}
}
最后,函数 readdir 使用 read 系统调用读取每个目录项
如果某个目录位置当前没有使用(因为删除了一个文件),则它的 inode 编号为 0,并跳过该位置
否则,将 inode 编号和目录名放在一个 static 类型的结构中,并给用户返回一个指向此结构的指针
每次调用 readdir 函数将覆盖前一次调用获得的信息
#includeDirent *readdir(DIR *dp) { struct direct dirbuf; static Dirent d; while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf)) { if (dirbuf.d_ino == 0) continue; d.ino = dirbuf.d_ino; strncpy(d.name, dirbuf.d_name, DIRSIZ); d.name[DIRSIZ] = ' '; return &d; } return NULL; }
尽管 fsize 程序非常特殊,但是它的确说明了一些重要的思想
首先,许多程序并不是 “ 系统程序 ”,它们仅仅使用由操作系统维护的信息
对于这样的程序,很重要的一点是,信息的表示仅出现在标准头文件中,使用它们的程序只需要在文件中包含这些头文件即可,而不需要包含相应的声明
其次,有可能为与系统相关的对象创建一个与系统无关的接口
标准库中的函数就是很好的例子
目录、参考文献



