APUE中遍历目录程序的思考

此文转载于linyang博客,链接地址为http://www.cnblogs.com/linyang/archive/2010/05/12/1733702.html

分析apue2e上的程序清单4-7 递归降序遍历目录层次结构,并按文件类型计数 。分析环境:FreeBSD 6.2-RELEASE,i386。

程序清单4-7,英文版即121页的Figure 4.22. Recursively descend a directory hierarchy, counting file type

为了便于程序的分析,我把原ftw4.c中用到的程序清单2-3(Figure 2.15)直接放到ftw4.c后面,形成新的ftw4.c源程序。为了配合该程序,建立了目录:/home/joe/music/rock,其结构如下:(其中目录MJ为空) ,(假设声称的可执行程序为ftw4,则正确执行的命令为./ftw4 /home/joe/music/rock)

/home/joe/music/rock

MJ/

./

../

MJ/

linkin.park.numb.mp3

./

../

下面开始分析程序:(每行程序代码前的数字为该行在ftw4.c中的行数)

1

6 typedef int Myfunc(const char *, const struct stat*, int);
7 static Myfunc myfunc;
8 static int myftw(char *, Myfunc *);
9 static int dopath( Myfunc *);
10 char *path_alloc(int *);
11
12 static long nreg, ndir, nchr, nblk, nfifo, nslink, nsock, ntot;

line 6为一个声明,声明了一个新的函数类型Myfunc,这种函数类型带3个参数,返回值是int类型的。第7行中就是声明了函数myfunc的原形,其类型为Myfunc,完整的就是static int myfunc(const char *, const struct stat*, int);。第8行声明函数myftw的原形,其参数两个,其一为一指针,其二为一函数名。

myfunc函数主要是遍历到符合条件的文件类型进行计数(分别放在line 12的各全局变量中,全局变量ntot为总文件数;宏FTW_F(在函数dopath中)及FTW_D标志文件类型正确或者可识别。),以及对stat函数出错(比如程序的参数为一个不存在目录或文件)、不能读目录(opendir函数出错)和不能识别的文件类型等异常状态作出相应的处理。(后面会详细指出。)

path_alloc函数主要是为路径(完整路径)分配内存空间。其返回两个参数:一为分配的内存空间的起始地址,即指针ptr;二为分配的内存空间的大小*sizep。

dopath函数主要是递归获取路径,并判断是目录还是文件,从而转向myfunc函数进一步判断并计数。

myftw函数是从path_alloc获取存放完整路径的内存空间起始地址和大小(分别存放在fullpath和len中),并将起始地址(即ftw4程序的参数)复制到该内存空间中,之后调用函数dopathz。

各函数之间的调用关系图:     

                    ↓ ̄|*1

main()<--myftw()<--dopath()<==myfunc()

           ↑               *2

       path_alloc()

                              *1: dopath()函数里有递归,

                                  *2: <==表示dopath()函数多次调用myfunc()函数。

2、

14 int main(int argc, char *argv[])
15 {
16 int ret;
17
18 if (argc != 2)
19    err_quit("usage: ftw4 <starting-pathname>");
20
21 ret = myftw(argv[1], myfunc); /* does it all */

line 18-19,判断有没有输入一个参数;line 21调用函数myftw(char *, myfunc *)。

3、

42 #define FTW_F 1 /* file other than directory */
43 #define FTW_D 2 /* directory */
44 #define FTW_DNR 3 /* directory that can't be read */
45 #define FTW_NS 4 /* file that we can't stat */
46
47 static char *fullpath; /* contains full pathname for every file */
48
49 static int
50 myftw(char *pathname, Myfunc *func)
51 {
52     int len;
53     fullpath = path_alloc(&len); /* malloc's for PATH_MAX+1 bytes */
54                                  /* (Figure 2.15 */
55     strncpy(fullpath, pathname, len); /* protect against */
56     fullpath[len-1] = 0; /* buffer overrun */
57
58     return(dopath(func));
59 }

line 47定义一个静态变量,作用域为line48至EOF,理解这一点比较重要,用GDB调试时,可以从myftw函数开始跟踪fullpath所指向的字符串。

line 53调用函数path_alloc,分配存储路径的空间,把已分配空间的地址赋给fullpath,并且空间长度为len(从后path_alloc的定义中可知其为1024),返回的字符串fullpath值全部为null character: '\0'

line 55 pathname是指向输入路径名称的字符串(本例中字符串为/home/joe/music/rock),函数strncpy(fullpath,pathname,len)将字符串pathname复制给fullpath,若strlen(pathname)<len(一般均是这种情况,至于strlen(pathname)>len则会把pathname的前len长度复制给fullpath),则把pathname全部拷贝给fullpath,并且fullpath剩下的空间全部为null character(即'\0'),因此,实际对我们有用的fullpath为"/home/joe/music/rock"。

line 56 fullpath[len-1]=0 是为了当出现strlen(pathname)>len时,防止内存溢出而作的处理,保证fullpath有一个结束。但这时得到的最终结果就不是你所期望的结果了。

line 58 return(dopath(func));调用dopath()函数。这个函数是本程序的核心之所在。

4、函数dopath会多次调用myfunc,因此我这里把这两个函数放在一起讨论。

67 static int
68 dopath(Myfunc* func)
69 {
70     struct stat statbuf;
71     struct dirent *dirp;
72     DIR *dp;
73     int ret;
74     char *ptr;
75
76     if (lstat(fullpath, &statbuf) < 0) /* stat error */
77         return(func(fullpath, &statbuf, FTW_NS));
78    
if (S_ISDIR(statbuf.st_mode) == 0) /* not a directory */
79    
    return(func(fullpath, &statbuf, FTW_F));
80
81 /*
82 * It's a directory. First call func() for the directory,
83 * then process each filename in the directory
84 */
85
    if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)
86    
    return(ret);
87
88
    ptr = fullpath + strlen(fullpath); /* point to end of fullpath */
89
    *ptr++ = '/';
90
    *ptr = 0;
91
92
    if((dp = opendir(fullpath)) == NULL) /* can't read directory */
93    
    return(func(fullpath, &statbuf, FTW_DNR));
94
95
    while ((dirp = readdir(dp)) != NULL)
96
    {
97    
    if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
98       
     continue; /* ignore dot and dot-dot */
99
100    
    strcpy(ptr, dirp->d_name); /* append name after slash */
101
        if ((ret = dopath(func)) != 0) /* recursive */
102    
        break; /* time to leave */
103
    }
104     ptr[-1] = 0; /* erase everything from slash onwards */
105
106     if (closedir(dp) < 0)
107         err_ret("can't close directory %s", fullpath);
108
109
    return(ret);
110 }
111
112 static int
113 myfunc(const char *pathname, const struct stat *statptr, int type)
114 {
115
    switch (type){
116
    case FTW_F:
117
        switch (statptr->st_mode & S_IFMT){
118
        case S_IFREG: nreg++; break;
119
        case S_IFBLK: nblk++; break;
120
        case S_IFCHR: nchr++; break;
121
        case S_IFIFO: nfifo++; break;
122
        case S_IFLNK: nslink++; break;
123
        case S_IFSOCK: nsock++; break;
124
        case S_IFDIR:
125
            err_dump("for S_IFDIR for %s", pathname); /* directories should have type = FTW_D */
126
        }
127
        break;
128
    case FTW_D:
129
        ndir++;
130
        break;
131
    case FTW_DNR:
132
        err_ret("can't read directory %s", pathname);
133
        break;
134     case FTW_NS:
135
        err_ret("stat error for %s", pathname);
136
        break;
137
    default:
138
        err_dump("unknown type %d for pathname %s", type, pathname);
139
    }
140
    return(0);
141 }

line 76用lstat函数得到所输入的路径的信息。若输入的是一个不存在的路径,则出现错误,line77转至myfunc函数处理出错类型,并且返回0值,因此此时dopath执行return(0),退出dopath()函数返回myftw()函数,因此return(dopath(func))也为return(0),再返回main()函数,最后exit(0)结束程序;若lstat无出错,则继续判断(line78)fullpath是否为目录,若不是目录,则为一个文件,line79转向myfunc()判断文件类型(regular file, block special file, character special file, FIFO(or pipe), symbolic link, socket),并且为相应的计数器增1(计数器:nreg, nblk, nchr, nfifo, nslink, nsock),同时返回0,此时dopath也是执行return(0),退出dopath()函数返回myftw()函数,因此return(dopath(func))也为return(0),再返回main()函数,最后exit(0)结束程序。这两种情况都很好理解,若路径不存在,当然显示出错信息并退出程序;若是路径是一个文件,很明显就用不到dopath的递归了(因为没有目录),给相应计数器赋值为1,退出程序。

line 85-100是我们讨论的重点,这里是处理fullpath为目录时的情况,有递归的算法出现。

line 85就是为输入的目录(即fullpath)相应的计数器ndir增1,若出错直接退出程序。

line 88-90 操作如下图:



注*:执行了line100之后,ptr为字符串dir->d_name,即/home/joe/music/rock下的子目录(第一次),line100之后,为/home/joe/music/rock加上了/和字符串结束符,为下面加上的子目录做好准备。

line 92-93,打开一个目录,若出错则显示出错信息并退出程序;否则返回DIR结构(The GNU C Library里描述:The DIR data type represents a directory stream,描述一个目录流。)供readdir函数使用。

line95-103读目录并获取目录信息,返回dirent结构,并忽略目录./和../,本例中首先返回的dirp->d_name为文件linkin.park.numb.mp3,line 100把文件名linkin.park.numb.mp3拷贝到/home/joe/music/rock/之后,这个时候形成的fullpath为/home/joe/music/rock/linkin.park.numb.mp3。line 101递归调用dopath函数,又返回line76,因为现在代表的是一个文件,而非目录,因此执行line79,判断文件的类型,为相应的计数器增1(本例中nreg增1),并退出当前的dopath,返回上一层dopath,重新执行line95,因此时由readdir(dp)的得到的dirp->d_name为一个目录(本例中为目录MJ/),ptr为MJ,fulpath为/home/joe/music/rock/MJ,在递归调用dopath,计算MJ/下的文件和目录数量,本例中,由于MJ/下为空,因此ndir增1后,退出while循环。关闭目录流dp(为/home/joe/music/rock/MJ),返回上一层dopath函数,再关闭目录流dp(为/home/joe/music/rock),再依次返回到myftw函数和main函数的line 22,再在main中继续后面的操作,计算/home/joe/music/rock下目录和各种文件的数量和百分比。最后退出程序。至此,程序正常结束。

在分析该程序时,要充分理解typedef的用法,typedef int Myfunc(const char *, const struct stat*, int),类型Myfunc的函数myfunc贯穿整个程序。有关typedef的详细信息可以参考"Expert C Programming: Deep Secrets"。

以上分析为自己在学习APUE.2e过程中的个人理解,欢迎各位讨论。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值