深くlinuxの下でディレクトリツリーを巡る方法の総括分析


先日はカタログツリー全体を巡回することを実現して、関連資料を調べました。発見された元の方法は、readdir()とlstat()関数を使用して再帰的な遍歴を達成することであり、その後、linuxは、このような最も一般的な動作をディレクトリに対して遍歴していることがわかった。この二つの方法について具体的に説明します。1、手動で再帰的に1.1 stat()関数族stat関数を実現することは、stat、fstat及びlstat関数を含み、いずれもユーザにファイルの属性情報(メタデータ)を返すことである。

view plaincopy to clipboardprint?
#include <sys/stat.h>  
       int stat(const char*pathname,struct stat*buf);  
       int fstat(int filedes,struct stat*buf);  
       int lstat(const char *pathname,struct stat*buf); 
 #include <sys/stat.h>
        int stat(const char*pathname,struct stat*buf);
        int fstat(int filedes,struct stat*buf);
        int lstat(const char *pathname,struct stat*buf);
の3つの関数の戻り:成功すれば0になり、エラーは−1です。一つのpathnameに対して、stat関数はこの名前付きファイルに関する情報構造を返します。fstat関数は記述子filedesで開かれたファイルに関する情報を得ます。lstat関数はstatと似ていますが、名前が付けられたファイルがシンボル接続である場合、lstatはシンボル接続で参照されたファイルの情報ではなく、シンボル接続に関する情報を返します。第二のパラメータはポインタであり、我々が提供すべき構造を指す。これらの関数はbufが指す構造を記入します。この構造の実際の定義は、実施されるかもしれないが、基本的な形式は、

      struct stat{
        mode st_mode; /* ( )*/
        ino st_ino;/* i- ( )*/
        dev st_dev;/* ( )*/
        dev st_rdev;/* */
        nlink st_nlink;/* */
        uid st_uid;/* ID*/
        gid st_gid;/* ID*/
        off st_size;/* */
        time st_atime;/* */
        time st_mtime;/* */
        time st_ctime;/* */
        long st_blksize;/* I/O */
        long st_blocks;/* 512
        };
以下の簡単なテスト

view plaincopy to clipboardprint?
#include<unistd.h>  
#include<sys/stat.h>  
#include<stdio.h>  
int 
main(int argc, char **argv){  
  struct stat buf;  
  if(stat(argv[1],&buf)) {  
    printf("[stat]:error!/n");  
    return -1;  
  }  
  printf("st_dev:%d/n",buf.st_dev);  
  printf("st_ino:%d/n",buf.st_ino);  
  printf("st_mode:%d S_ISDIR:%d/n",buf.st_mode,S_ISDIR(buf.st_mode));  
  printf("st_nlink:%d/n",buf.st_nlink);  
  printf("st_uid:%d/n",buf.st_uid);  
  printf("st_gid:%d/n",buf.st_gid);  
  printf("st_rdev:%d/n",buf.st_rdev);  
  printf("st_size:%d/n",buf.st_size);  
  printf("st_blksize:%lu/n",buf.st_blksize);  
  printf("st_blocks:%lu/n",buf.st_blocks);  
  printf("st_atime:%ld/n",buf.st_atime);  
  printf("st_mtime:%ld/n",buf.st_mtime);  
  printf("st_ctime:%ld/n",buf.st_ctime);  
  return 0;  

#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>
int
main(int argc, char **argv){
  struct stat buf;
  if(stat(argv[1],&buf)) {
    printf("[stat]:error!/n");
    return -1;
  }
  printf("st_dev:%d/n",buf.st_dev);
  printf("st_ino:%d/n",buf.st_ino);
  printf("st_mode:%d S_ISDIR:%d/n",buf.st_mode,S_ISDIR(buf.st_mode));
  printf("st_nlink:%d/n",buf.st_nlink);
  printf("st_uid:%d/n",buf.st_uid);
  printf("st_gid:%d/n",buf.st_gid);
  printf("st_rdev:%d/n",buf.st_rdev);
  printf("st_size:%d/n",buf.st_size);
  printf("st_blksize:%lu/n",buf.st_blksize);
  printf("st_blocks:%lu/n",buf.st_blocks);
  printf("st_atime:%ld/n",buf.st_atime);
  printf("st_mtime:%ld/n",buf.st_mtime);
  printf("st_ctime:%ld/n",buf.st_ctime);
  return 0;
}
であり、ここで、linuxにおけるファイルの基本的なタイプを説明するために追加される。1.一般ファイル(Reglar file)。これは最も一般的なファイルタイプで、このファイルにはある形式のデータが含まれています。このようなデータはテキストですか?それともバイナリデータですか?通常のファイル内容の解釈は、このファイルを処理するアプリケーションによって行います。2.ディレクトリファイル(Directory file)。このファイルには他のファイルの名前とこれらのファイルに関する情報を指すポインタが含まれています。一つのディレクトリファイルに対して読み取り許可数を持つプロセスはいずれもディレクトリの内容を読むことができますが、システムコアのみがディレクトリファイルを書くことができます。3.文字特殊ファイル(Chrocter special file)。このようなファイルはシステム内のデバイスのいくつかのタイプに使用されます。4.ブロック特殊ファイル(Block special file)。このようなファイルは典型的にディスクデバイスに使用されます。システム内のすべてのデバイスまたは特殊文字ファイル、またはブロック特殊ファイルです。5.FIFO。このファイルはプロセス間の通信に使用されます。時には配管と呼ばれることもあります。6.インターフェースをセットします。このファイルはプロセス間のネットワーク通信に使用されます。セットインターフェースは、シンクホスト上のプロセス間の非ネットワーク通信にも使用され得る。7.符号接続(Symbolilink)。このファイルは他のファイルを指します。ファイルタイプについては、定義されたマクロをS_のように利用することができます。ISDIR()などのテストst_mode、ファイルタイプを判断します。マクロはS_がありますISREG、S_ISDIR、S_ISCHR、S_ISBLK、S_ISFIFO、S_ISLNK、S_ISSOCK1.2ディレクトリの例を参照して他の人の例を参照して、多くのファイル処理関数をまとめて使用します。プログラムは指定されたディレクトリのファイルを巡回しながら、下位サブディレクトリに進みます。この点はサブディレクトリを再びopendirに転送することです。これはサブディレクトリの入れ子が深すぎると、プログラムは失敗します。開くことができるサブディレクトリのストリーム数に上限があるからです。

view plaincopy to clipboardprint?
/*  We start with the appropriate headers and then a function, printdir, 
    which prints out the current directory. 
    It will recurse for subdirectories, using the depth parameter is used for indentation.  */ 
#include <unistd.h>  
#include <stdio.h>  
#include <dirent.h>  
#include <string.h>  
#include <sys/stat.h>  
void printdir(char *dir, int depth)  
{  
    DIR *dp;  
    struct dirent *entry;  
    struct stat statbuf;  
    if((dp = opendir(dir)) == NULL) {  
        fprintf(stderr,"cannot open directory: %s/n", dir);  
        return;  
    }  
    chdir(dir);  
    while((entry = readdir(dp)) != NULL) {  
        lstat(entry->d_name,&statbuf);  
        if(S_ISDIR(statbuf.st_mode)) {  
            /**//* Found a directory, but ignore . and .. */ 
            if(strcmp(".",entry->d_name) == 0 ||   
                strcmp("..",entry->d_name) == 0)  
                continue;  
            printf("%*s%s//n",depth,"",entry->d_name);  
            /**//* Recurse at a new indent level */ 
            printdir(entry->d_name,depth+4);  
        }  
        else printf("%*s%s/n",depth,"",entry->d_name);  
    }  
    chdir("..");  
    closedir(dp);  
}  
/**//*  Now we move onto the main function.  */ 
int main(int argc, char* argv[])  
{  
    char *topdir, pwd[2]=".";  
    if (argc != 2)  
        topdir=pwd;  
    else 
        topdir=argv[1];  
    printf("Directory scan of %s/n",topdir);  
    printdir(topdir,0);  
    printf("done./n");  
    exit(0);  

/*  We start with the appropriate headers and then a function, printdir,
    which prints out the current directory.
    It will recurse for subdirectories, using the depth parameter is used for indentation.  */
#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
void printdir(char *dir, int depth)
{
    DIR *dp;
    struct dirent *entry;
    struct stat statbuf;
    if((dp = opendir(dir)) == NULL) {
        fprintf(stderr,"cannot open directory: %s/n", dir);
        return;
    }
    chdir(dir);
    while((entry = readdir(dp)) != NULL) {
        lstat(entry->d_name,&statbuf);
        if(S_ISDIR(statbuf.st_mode)) {
            /**//* Found a directory, but ignore . and .. */
            if(strcmp(".",entry->d_name) == 0 ||
                strcmp("..",entry->d_name) == 0)
                continue;
            printf("%*s%s//n",depth,"",entry->d_name);
            /**//* Recurse at a new indent level */
            printdir(entry->d_name,depth+4);
        }
        else printf("%*s%s/n",depth,"",entry->d_name);
    }
    chdir("..");
    closedir(dp);
}
/**//*  Now we move onto the main function.  */
int main(int argc, char* argv[])
{
    char *topdir, pwd[2]=".";
    if (argc != 2)
        topdir=pwd;
    else
        topdir=argv[1];
    printf("Directory scan of %s/n",topdir);
    printdir(topdir,0);
    printf("done./n");
    exit(0);
}
2、ftwを使ってディレクトリを巡回する2.1 ftw関数族を呼び出して、readdir関数などを使って再帰的なエルゴードを達成する方法は元のものと比較して、glibc 2.1はftwなどの関数を収録して、ディレクトリツリーの遍歴を実現することができます。

view plaincopy to clipboardprint?
#include <ftw.h>  
int ftw(const char *dirpath,  
        int (*fn) (const char *fpath, const struct stat *sb,int typeflag),  
        int nopenfd);  
#define _XOPEN_SOURCE 500  
#include <ftw.h>  
int nftw(const char *dirpath,  
        int (*fn) (const char *fpath, const struct stat *sb,int typeflag, struct FTW *ftwbuf),  
        int nopenfd, int flags); 
#include <ftw.h>
int ftw(const char *dirpath,
        int (*fn) (const char *fpath, const struct stat *sb,int typeflag),
        int nopenfd);
#define _XOPEN_SOURCE 500
#include <ftw.h>
int nftw(const char *dirpath,
        int (*fn) (const char *fpath, const struct stat *sb,int typeflag, struct FTW *ftwbuf),
        int nopenfd, int flags);
具体的な英語解釈は文章「ftw,ftw-file tree walk」を参照することができます。ftw()関数の説明:ftw()はパラメータdirpathで指定されたディレクトリから始まり、次のステップへと再帰的にサブディレクトリを巡回します。ftw()は三つのパラメータをfn()に伝えます。最初のパラメータ*fpathは当時のディレクトリパスを指します。二つ目のパラメータは**sbで、stat構造ポインタで、三つ目のパラメータはflagsです。次のような可能性があります。F        一般ファイルFTW_D       カタログFTW_DNR    読み取り不可のディレクトリは、このディレクトリ以下はFTW_を巡回しません。SL。       シンボル接続FTW_NS       stat構造データを取得できませんでした。権限問題の最後のパラメータdepth代表ftw()がディレクトリを巡回している間に開いているファイルの数かもしれません。ftw()各階層のディレクトリを通過するには少なくとも1つのファイル記述ワードが必要であり、もし巡回している間にdepthによって与えられた制限数を使い切ったら、全体が巡回してファイルを閉じたり、ファイルを開いたりすることによって動作が遅くなります。実際にテストをした時に発見されませんでした。ftw()の遍歴を終了するにはfn()は0以外の値を返すだけでいいです。この値はftw()の戻り値です。そうしないとftw()は全てのディレクトリを歩いて0に戻ります。  値:巡回中断はfn()関数の戻り値を返します。全部遍歴すれば0に戻ります。エラーが発生したら-1の追加説明に戻ります。ftw()はメモリを動的に構成しますので、正常方式(fn関数は非ゼロ値)を使ってエルゴードを中断してください。fn関数でlongjmp()nftw()関数説明を使わないでください。nftw()いずれもパラメータdirpathで指定されたディレクトリから始まり、サブディレクトリを巡回します。ディレクトリに入るたびにパラメータ*fn定義の関数を呼び出して処理します。nftw()は、fn()に4つのパラメータを伝えます。最初のパラメータ*fpathは、当時のディレクトリパスを指します。2番目のパラメータは**sbで、stat構造ポインタ(構造定義はstat()を参照してください。3番目のパラメータはtypeflagsです。次のような可能性があります。FTW_F                         一般ファイルFTW_D                         カタログFTW_DNR                      読み込めないディレクトリです。このディレクトリ以下はFTW_を巡回しません。SL。                         シンボル接続FTW_NS                        stat構造データを取得できませんでした。権限問題かもしれません。DP                        カタログ、そしてサブディレクトリはFTWを巡回されました。SLN                       シンボル接続ですが、存在しないファイルfn()を接続する第4のパラメータはFTW構造で、

struct  FTW
{
     int  base;
     int  level; //level
}
nftw()第3のパラメータdepthはnftw()を表します。ftw()各階層のディレクトリを通過するには少なくとも1つのファイル記述ワードが必要であり、もし巡回するとdepthによって与えられた制限数が使い切れば、全体が巡回してファイルをオフにしたり、ファイルを開いたりすることによって徐々に見えるnftw()の最後のパラメータflagsが使用され、以下の操作またはOR組合せFTW_を指定できます。CHDIR                 カタログを読む前にchdir()を使ってこのカタログFTW_を移動します。DEPTH                深さ優先検索を実行します。このディレクトリを巡回する前に、すべてのサブディレクトリをFTW_に巡回しました。MOUNT               巡回中に他のファイルシステムに乗り越さないでください。PHYS                  シンボル接続されたディレクトリを巡回しないでください。プロビジョニングは、シンボル接続ディレクトリを巡回します。nftw()の巡回を終了するには、fn()は0以外の値を返しても良いです。この値は同時にnftw()の戻り値です。さもないとnftw()はすべてのディレクトリを巡回してから0に戻ります。戻り値:巡回中断したらfn()関数の戻り値に戻ります。全部遍歴したら0に戻ります。エラーが発生したら-1に戻ります。ftwは各ファイルに対してstat関数を呼び出します。これはプログラムがシンボルリンクに従うことになります。これは、いくつかのディレクトリを繰り返したり、循環的にいくつかのディレクトリファイルを統計したりすることを引き起こす可能性があります。nftwはlstat関数を呼び出しますので、シンボルリンクに従う問題はありません。注意:nftw関数を使用する場合は、鏣define_を定義しなければなりません。XOPEN_SOURCE 500は未定義などのエラーが発生します。一つはっきりしていない問題があります。FTWを使っています。DEPTHがディレクトリツリー全体を巡回する時、procディレクトリの下に異常があります。FTW_を指定する必要があるかもしれません。PHYSは符号リンクディレクトリを遍歴しないようにします。これは暇があれば調べてください。2、遍歴した例は自分で書いたテストの小さい例です。指定されたディレクトリを巡回して、ファイルのメタデータや巡回の深さなどの情報を出力します。

view plaincopy to clipboardprint?
#define _XOPEN_SOURCE 500   
#include<ftw.h>  
#include<sys/stat.h>  
#include<unistd.h>  
#include<stdio.h>  
#include<string.h>   
#define FILEOPEN 1024   
int gb_filecount;  
int getMetadata(const char *dirpath, const struct stat *sb,int typeflag, struct FTW *ftwbuf);  
int main(int argc, char ** argv){  

  int ret = 0;  
  struct stat pathbuf;  
  if(argc > 2){  
    printf("-nfwt_t:invalid arguments /n ");  
    return -1;  
  }  
  if(stat(argv[1],&pathbuf)){  
    printf("-nfwt_t:invalid dirpath:%s/n",argv[1]);  
    return -1;  
  }else{  
    if(0 == S_ISDIR(pathbuf.st_mode)){  
      printf("-nfwt_t:/"%s/" is not dirpath/n",argv[1]);  
      return -1;  
    }  
  }  
  gb_filecount=0;  
  ret = nftw(argv[1],getMetadata,FILEOPEN,FTW_PHYS);  
    if(ret<0){  
    printf("-nftw:[wrong:%d]ntfw search %d files/n",ret,gb_filecount);  
    return -1;  
  }else{  
    printf("-nftw:[done:%d]trasvers in %s search %d files/n",ret,argv[1],gb_filecount);  
    return 0;  
  }  
}  
int   
getMetadata(const char *dirpath, const struct stat *sb,int typeflag, struct FTW *ftwbuf){  
  printf("num:%d path:%s ",++gb_filecount,dirpath);  
  printf("st_dev:%d ",(*sb).st_dev);  
  printf("st_ino:%d ",(*sb).st_ino);  
  printf("st_mode:%d S_ISDIR:%d ",(*sb).st_mode,S_ISDIR((*sb).st_mode));  
  printf("st_nlink:%d ",(*sb).st_nlink);  
  printf("st_uid:%d ",(*sb).st_uid);  
  printf("st_gid:%d ",(*sb).st_gid);  
  printf("st_rdev:%d ",(*sb).st_rdev);  
  printf("st_size:%d ",(*sb).st_size);  
  printf("st_blksize:%lu ",(*sb).st_blksize);  
  printf("st_blocks:%lu ",(*sb).st_blocks);  
  printf("st_atime:%ld ",(*sb).st_atime);  
  printf("st_mtime:%ld ",(*sb).st_mtime);  
  printf("st_ctime:%ld ",(*sb).st_ctime);  
  printf("typeflag:%d ",typeflag);  
  printf("FTW_base:%d FTW_level:%d /n",(*ftwbuf).base,(*ftwbuf).level);  
  return 0;