~分析apue程序清單4-7 遞歸降序遍歷目錄層次結構,並按文件類型計數 。

爲了便於程序的分析,我把原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);

line 18-19,判斷有沒有輸入一個參數;line 21調用函數myftw(char *, myfunc *)。

3、

42 #define FTW_F 1
43 #define FTW_D 2
44 #define FTW_DNR 3
45 #define FTW_NS 4
46 
47 static char *fullpath;
48 
49 static int 
50 myftw(char *pathname, Myfunc *func)
51 {
52     int len;
53     fullpath = path_alloc(&len);
54                                 
55     strncpy(fullpath, pathname, len);
56     fullpath[len-1] = 0;
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)
77         return(func(fullpath, &statbuf, FTW_NS));
78     
if (S_ISDIR(statbuf.st_mode) == 0)
79     
    return(func(fullpath, &statbuf, FTW_F));
80 
81
85 
    if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)
86     
    return(ret);
87 
88 
    ptr = fullpath + strlen(fullpath);
89 
    *ptr++ = '/';
90 
    *ptr = 0;
91 
92 
    if((dp = opendir(fullpath)) == NULL)
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;
99 
100     
    strcpy(ptr, dirp->d_name);
101 
        if ((ret = dopath(func)) != 0)
102     
        break;
103 
    }
104     ptr[-1] = 0;
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);
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 操作如下圖:


typedef <wbr>int <wbr>Myfunc(const <wbr>char*, <wbr>const <wbr>struct <wbr>stat <wbr>*, <wbr>int)問題的思考

注*:執行了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"。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章