当前位置:网站首页>The C Programming Language(第 2 版) 笔记 / 8 UNIX 系统接口 / 8.6 实例(目录列表)
The C Programming Language(第 2 版) 笔记 / 8 UNIX 系统接口 / 8.6 实例(目录列表)
2022-06-12 16:15:00 【M菜鸟M】
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 /* 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_t 和 ino_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_IFMT 和 S_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
每个目录都包含自身 . 和父目录 .. 的项目,在处理时必须跳过它们,否则将会导致无限循环
到现在这一步为止,代码与目录的格式无关
下一步要做的事情就是在某个具体的系统上提供一个 opendir、readdir 和 closedir 的最简单版本
以下的函数适用于 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 程序非常特殊,但是它的确说明了一些重要的思想
首先,许多程序并不是 “ 系统程序 ”,它们仅仅使用由操作系统维护的信息
对于这样的程序,很重要的一点是,信息的表示仅出现在标准头文件中,使用它们的程序只需要在文件中包含这些头文件即可,而不需要包含相应的声明
其次,有可能为与系统相关的对象创建一个与系统无关的接口
标准库中的函数就是很好的例子
边栏推荐
- IDEA中文棱形乱码错误解决方法--控制台中文输出棱形乱码
- acwing 797 差分
- < 山东大学软件学院项目实训 > 渲染引擎系统——点云处理(十)
- 批量--03---CmdUtil
- MySQL blob and text types
- RTOS rt-thread裸机系统与多线程系统
- Solution to idea Chinese prism garbled code error -- console Chinese output prism garbled code
- Step by step steps to create an ABAP program with a custom screen
- [tool recommendation] personal local markdown knowledge map software
- 为什么阿里巴巴不建议MySQL使用Text类型?
猜你喜欢

Explore the Apache shardingsphere SQL parse format function

从斐波那契数列求和想到的俗手、本手和妙手

Homology? Cross domain? How to solve cross domain problems?

Read MHD and raw images, slice, normalize and save them

当编程纳入到高考。。。

Project training of Software College of Shandong University rendering engine system point cloud processing (10)

RTOS rt-thread裸机系统与多线程系统

超详细干货!Docker+PXC+Haproxy搭建高可用强一致性的MySQL集群

Project training of Software College of Shandong University rendering engine system basic renderer (III)

读取mhd、raw图像并切片、归一化、保存
随机推荐
鼻孔插灯,智商上升,风靡硅谷,3万就成
Match single character
Go Net Library (to be continued)
联通网管协议框图
What is JUC in high concurrency programming
Development status of China's pig breeding industry in 2021 and comparative analysis of key enterprises: 671million pigs were sold [figure]
一步步创建包含自定义 Screen 的 ABAP 程序的详细步骤试读版
< 山东大学软件学院项目实训 > 渲染引擎系统——基础渲染器(四)
【周赛复盘】LeetCode第80场双周赛
d的sha6转大整
Global and Chinese market of vascular prostheses 2022-2028: Research Report on technology, participants, trends, market size and share
Escape rules and examples of go
Multimix: small amount of supervision from medical images, interpretable multi task learning
C packing and unpacking
Explore the Apache shardingsphere SQL parse format function
acwing 798二维差分(差分矩阵)
Great God cracked the AMD k6-2+ processor 22 years ago and opened the hidden 128KB L2 cache
从斐波那契数列求和想到的俗手、本手和妙手
acwing796 子矩阵的和
为什么阿里巴巴不建议MySQL使用Text类型?