掲示板利用宣言

 次のフォームをすべてチェックしてからご利用ください。

 私は

 題名と投稿者名は具体的に書きます。
 課題の丸投げはしません。
 ソースの添付は「HTML変換ツール」で字下げします。
 返信の引用は最小限にします。
 環境(OSとコンパイラ)や症状は具体的に詳しく書きます。
 返信の付いた投稿は削除しません。
 マルチポスト(多重投稿)はしません。

掲示板2

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧

No.24813

ディレクトリの再帰処理
投稿者---zero(2005/12/15 01:16:43)


現在Linux(gcc)環境でディレクトリの再帰処理を行なう関数を作っています。

関数プロトタイプは以下のようになっています。
--------------------------------------------------------------------
bool search_dir(const char *topDir);
引数:
topDir 再帰処理を始めるルートディレクトリ名
返り値:
bool 再帰処理が成功したか。成功ならsuccess:1 失敗なら:false-1を返す--------------------------------------------------------------------

再帰処理の内容ですが、引数で与えられたディレクトリから再帰的にディレクトリを検索していき、
そのディレクトリの中に含まれているファイル名を表示するというものです。
ディレクトリが、'.'と'..'の場合は無視し、またディレクトリ名は表示せず、
ファイル名のみを表示していくというものです。以下に作ったソースを載せます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <unistd.h>

#define success  1
#define fail    -1

bool search_dir(const char *topDir) {
    DIR *dir_p;
    struct dirent *dir;
    struct stat stat_b;
    int ret;
    char *path;

    if (topDir == NULL) return fail;

    dir_p = opendir(topDir);
    if ( (dir_p == NULL) && (errno == EACCES)) {
        return fail;
    }
    while ( (dir = readdir(dir_p)) != NULL) {
        path = calloc(strlen(topDir)+strlen(dir->d_name)+2, sizeof(char));
        sprintf(path, "%s/%s", topDir, dir->d_name);

        if ( (strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0)) {
            continue;
        }

        lstat(path, &stat_b);
        if (S_ISDIR(stat_b.st_mode)) {
            search_dir(path);
        }
        else {
            printf("%s\n", dir->d_name);
        }
        free(path);
    }
    return fail;
}

int main(int argc, char *argv[]) {

    (void)search_dir(argv[1]);

    return 0;
}

------------------------------ EOF ---------------------------------

一応上記のソースを作ってみたのですが、以下のように実行すると、
セグメンテーションフォルトで実行が中止されてしまいます。

$ ./a.out /usr

(ファイル名が表示されていく)
セグメンテーション違反です
$



以下のコマンドで何個ファイルが表示されたのか数えてみました。
$ ./a.out /usr | wc -l
27784
$



またgdbでのデバッグの内容です。(デバッグと言えるほどのものではないですが、、、)
$gdb a.out
(gdb) break main
(gdb) run /usrStarting program: /home/zero/c/a.out /usr
Reading symbols from shared object read from target memory...done.
Loaded system supplied DSO at 0xb7fd4000

Breakpoint 1, main (argc=2, argv=0xbfdd20b4) at demo.c:5
5 (void)search_dir(argv[1]);
(gdb) c

(ファイル名が表示されていく)

Program received signal SIGSEGV, Segmentation fault.
0x00886fa6 in readdir () from /lib/tls/libc.so.6
(gdb) backtrace
#0 0x00886fa6 in readdir () from /lib/tls/libc.so.6
#1 0x080486a2 in search_dir (topDir=0x90f57c8 "/usr/share/gimp-print/doc/html") at lib.c:16
#2 0x0804876a in search_dir (topDir=0x90f57f8 "/usr/share/gimp-print/doc") at lib.c:26
#3 0x0804876a in search_dir (topDir=0x90eb290 "/usr/share/gimp-print") at lib.c:26
#4 0x0804876a in search_dir (topDir=0x8ce3028 "/usr/share") at lib.c:26
#5 0x0804876a in search_dir (topDir=0xbfdd3acc "/usr") at lib.c:26
#6 0x080488a8 in main (argc=2, argv=0xbfdd20b4) at demo.c:5



上記のようにセグメンテーションフォルトとなる原因はなんでしょうか?
俗に再帰処理でよく起こりうるスタックオーバーフローなのでしょうか?
また opendir() によってディレクトリをオープンしていきますが、
/usr/include/linux/limits.h の中でdefineされている

#define OPEN_MAX 256 /* # open files a process may have */

で256個までしかオープンできないみたいですが、これに対処するよい方法があればお教え下さい。

ご教授のほどよろしくお願いします。


この投稿にコメントする

削除パスワード

発言に関する情報 題名 投稿番号 投稿者名 投稿日時
<子記事> Re:ディレクトリの再帰処理 24814 ぽこ 2005/12/15 02:52:56


No.24814

Re:ディレクトリの再帰処理
投稿者---ぽこ(2005/12/15 02:52:56)


現在、実行環境を持っていない状況なので真因は分かりませんが、
以下のようなソースだと、opendir()がNULLを返しても状況によっては
readdir()の処理に入りませんか?
(readdir()の引数にNULLを渡しているから落ちているのでは?)

    dir_p = opendir(topDir);
    if ( (dir_p == NULL) && (errno == EACCES)) {
        return fail;
    }
    while ( (dir = readdir(dir_p)) != NULL) {




この投稿にコメントする

削除パスワード

No.24822

Re:ディレクトリの再帰処理
投稿者---べた(2005/12/15 10:22:12)


環境は、Solaris8iですが、
ファイル数が256以上、1000以上あるディレクトリで
試してみましたが、発生しませんでした。

UNIX-C、gccでコンパイルしています。


この投稿にコメントする

削除パスワード

No.24823

Re:ディレクトリの再帰処理
投稿者---zero(2005/12/15 11:00:06)


>以下のようなソースだと、opendir()がNULLを返しても状況によっては
>readdir()の処理に入りませんか?
>(readdir()の引数にNULLを渡しているから落ちているのでは?)
>
>
    dir_p = opendir(topDir);
    if ( (dir_p == NULL) && (errno == EACCES)) {
        return fail;
    }
    while ( (dir = readdir(dir_p)) != NULL) {


返信ありがとうございます。
なるほど!!そうですね。(dir_p==NULL)で (errno != EACCES) の時は
確かにreaddir()にNULL引数を与えてしまいますね。うっかりしてました。
これが原因の可能性大ですね。

今、実行環境のある所から離れていまして、今夜にでも結果を報告したいと
思います。


この投稿にコメントする

削除パスワード

No.24832

Re:ディレクトリの再帰処理(エラー原因発見!)
投稿者---zero(2005/12/15 20:44:38)


エラーの原因は

EMFILE プロセスで使われているファイル・ディスクリプターが多すぎる。

というものでした。
opendir()でディレクトリをオープンしてclosedir()を呼び出すのを忘れていました。
以下に修正した、search_dir関数を載せます。

-------------------------------------------------------------------
bool search_dir(const char *topDir) {
    DIR *dir_p;
    struct dirent *dir;
    struct stat stat_b;
    char *path;

    dir_p = opendir(topDir);
    if (dir_p == NULL)
        return fail;

    while ( (dir = readdir(dir_p)) != NULL) {
        path = calloc(strlen(topDir)+strlen(dir->d_name)+2, sizeof(char));
        sprintf(path, "%s/%s", topDir, dir->d_name);

        if ( (strcmp(dir->d_name, ".") == 0) || (strcmp(dir->d_name, "..") == 0))
            continue;

        Lstat(path, &stat_b);

        if (S_ISDIR(stat_b.st_mode))
            search_dir(path);
        else
            printf("%s\n", dir->d_name);
  
        free(path);
    }
    Closedir(dir_p);
    return success;
}

--------------------------------------------------------------------
ちなみにLstat()、Closedir()に関してはエラー処理をほどこした自作関数
で、別に作ってあります。

上記のソースに潜在的なバグはあるでしょうか?
ヒープ、スタックオーバーフローや、今回問題となった、プロセスのlimit
制限に引っかかるプロセスエラー関連等、お気付きになりましたら、
お教え下さると勉強になります。

よろしくお願いします。


この投稿にコメントする

削除パスワード

No.24833

Re:ディレクトリの再帰処理(再び疑問)
投稿者---zero(2005/12/15 21:17:42)


今、readdir(3)を読んでいて以下の記述が気になりました。

readdir() によって返されるデータは、以降の同じストリームに対する
read-dir() の呼び出しによって上書きされる可能性がある。

可能性があるって、、、。


という事は、readdir()によって返されたポインタの指す先の構造体データを
安全な場所にコピーしておく必要があるってことでしょうか?(安全対策のために)


この投稿にコメントする

削除パスワード

管理者用メニュー    ツリーに戻る    携帯用URL    ホームページ    ログ    タグ一覧