4.3 ディレクトリの検索
Menu Menudir-list.c は、ディレクトリの内容を表示させる プログラムである。
/*
dir-list.c -- ディレクトリの内容を表示するプログラム
$Header: /home/h1/yas/slab-info1-os/4-file/RCS/dir-list.c,v 1.2 1995/05/31 11:07:04 yas Exp $
Start: 1995/03/07 21:44:51
*/
#include <stdio.h> /* stderr */
#include <fcntl.h> /* open(2) */
#include <sys/types.h> /* getdirentries(2) */
#include <sys/dirent.h> /* getdirentries(2) */
open(), getdirentries()システム・コールを使うためのヘッダ・ファイルを読み込む。
extern char *malloc();
main( argc,argv )
int argc ;
char *argv[] ;
{
if( argc != 2 )
{
fprintf( stderr,"Usage:%% %s dirname \n",argv[0] );
exit( 1 );
}
dir_list( argv[1] );
}
このプログラムは、引数として、ディレクトリ名一つを取る。エラーメッセージできるだけ丁寧に出すようにしよう。それがデバッグを用意にすることが多い。
#define BUFFSIZE BUFSIZ
dir_list( dirname )
char *dirname ;
{
int fd ;
struct dirent *p ;
char *buff ;
int rcount ;
long pointer;
* O_RDONLY: 読み込み専用でディレクトリ・ファイルを開く
fd = open( dirname,O_RDONLY );
if( fd == -1 ) /* エラーが起きたとき */
{
perror( dirname ); /* ファイル名とエラーメッセージを表示し、 */
exit( 1 ); /* プロセスを終了する。 */
}
ここでは、malloc() を用いて BUFFSIZE 分のメモリを確保する。
buff = malloc( BUFFSIZE );
if( buff == 0 )
{
perror("memory");
exit( 1 );
}
getdirentries() システム・コールは、ファイルに対する read() システム・コールとよく似ている。第1引数で指定されたファイル記述子のディレクトリ・ファイルの内容を読み込み、それを第2引数の番地へ保存する。読み込むバイト数は、第3引数で与えられる。getdirentries() は、結果として読み込んだバイト数を返す。通常は、BUFFSIZE が返される。ファイルの末尾やファイルが端末の時、BUFFSIZE 以下の値が返される。ファイルの末尾に行き着くと 0 が返される。最後の引数は、次のentryのfile pointerを書き込む場所である。
getdirentries(int fd, char *buf, int nbytes, long *basep);
while((rcount=getdirentries(fd, buff, BUFFSIZE, &pointer))>0)
{
for( p = (struct dirent *)buff ; (char *)p < &buff[rcount] ;
p=(struct dirent *) ((int)p+(p->d_reclen)) )
{
printf("p:%d,\t", (char *)p - buff );
/* printf("off:%d, ", p->d_off ); */
printf("fileno:%d,\t", p->d_fileno );
printf("reclen:%d,\t", p->d_reclen );
printf("namlen:%d,\t", p->d_namlen );
printf("name:%s\n", p->d_name );
}
printf("base:%ld\n", pointer);
}
ここでは、次のような内容を画面に表示している。
p: ポインタ p がバッファの先頭からどれだけずれているか
filneno: i-ノード番号。stat()システム・コールのinoと同じ。
reclen: レコード長。このディレクトリ・エントリの長さ
次のディレクトリ・エントリがどこにあるかを計算する時に
使われる。
namelen: 次のnameに含まれているファイル名の長さ
name: ファイル名の本体。文字列として最後に'\0'がある。
最後に、ディレクトリ・ファイルを閉じ、確保したメモリを解放している。
close( fd ); /* ファイルを閉じる。*/
free( buff );
}
次のように dir-list をコンパイルし、実行して見なさい。余裕があれば、BSD/OS
とSolaris の両方でプログラムを動かして見よ、
% make dir-list
% ./dir-list /usr
%
name: の後に、指定されたディレクトリ(上の例では/usr)に登録されているファイル名が現れていることがわかる。それ以外の意味は、上で説明したようなディレクトリファイルの内容が表示されていることがわかる。
ここでdirentは、/usr/include/sys/dirent.h に次のように定義されている構造体である。
struct dirent {
u_int32_t d_fileno; /* file number of entry */
u_int16_t d_reclen; /* length of this record */
u_int8_t d_type; /* file type, see below */
u_int8_t d_namlen; /* length of string in d_name */
#ifdef _POSIX_SOURCE
char d_name[255 + 1]; /* name must be no longer than this */
#else
#define MAXNAMLEN 255
char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */
#endif
};
d_name[]の大きさが255+1であることにより、ファイル名の長さが255文字であることがわかる。+1 バイトは、文字列の終端を表す'\0'のためのものである。しかしながら、実際のファイル名は短いものが多い。よって、すべてのディレクトリエントリについて255文字分の領域を割り当てることは無駄が多い。よって実際のディレクトリでは、d_name[]として255文字分の領域が使われているのではなく、必要最小限の領域しか割り当てられない。d_name[]は、次のようなものが並んでいる。
名前の文字列、d_namelen バイト
文字列の終端を表す1バイト
4バイト境界に合わせるための埋め文字(padding), 0-3バイト
埋め文字、または、終端を表す'\0'の次には、次のディレクトリ・エントリが続いている。よって、p++ ではなく、プログラムにあるように、
p=(struct dirent *) ((int)p+(p->d_reclen))とすることで、次のディレクトリ・エントリを得ることができる。
課題6 ディレクトリの内容の検索
与えられたディレクトリの中から与えられた名前のファイルを検索し、そのiノード番号を表示するプログラムを作りなさい。そのプログラムの名前をdir-lookup とする。dir-lookup の実行例を示す。
% ./dir-lookup /usr local
49614 local
ここで 49614 はiノード番号である。
dir-lookup コマンドの骨格部分を dir-lookup.c に含める。
/*
dir-lookup.c -- ディレクトリの内容を検索するプログラム
$Header: /home/h1/yas/slab-info1-os/4-file/RCS/dir-lookup.c,v 1.2 1995/05/31 11:43:32 yas Exp $
Start: 1995/03/08 22:02:00
*/
#include <stdio.h> /* stderr */
#include <fcntl.h> /* open(2) */
#include <sys/types.h> /* getdents(2) */
#include <sys/dirent.h> /* getdents(2) */
extern char *malloc();
main( argc,argv )
int argc ;
char *argv[] ;
{
if( argc != 3 )
{
fprintf( stderr,"Usage:%% %s dirname filename\n",argv[0] );
exit( 1 );
}
dir_lookup( argv[1], argv[2] );
}
dir_lookup( dirname, filename )
char *dirname ;
char *filename ;
{
/* この関数を完成させなさい。*/
}
この中の関数 dir_lookup(0 を完成させなさい。dir_list() の大部分が利用可能である。ループの中で、filename で与えられた文字列と p->d_name
を比較し、見つかったらiノード番号とファイル名を表示して終了すれば良い。ファイルが見つからなかった場合は、その旨をメッセージで表示すること。文字列を比較するためには strcmp() ライブラリ関数を使うか、自分でループなどを用いて一文字ずつ比較するかしなさい。
作成したプログラムが正しく動いていることを、以下のように"ls -i dirname/filename"の結果と比較して示しなさい。
% ./dir-lookup . dir-lookup.c
57003 dir-lookup.c
% ls -i ./dir-lokup.c
57003 ./dir-lookup.c
課題 7 複数のディレクトリに渡るディレクトリ内容の検索
chdir()システム・コール、 getdirentries()システム・コールを使って、与えられたファイル名から、そのiノード番号を調べるプログラムを作りなさい。例えば、与えられたファイル名が"/usr/open/doc/tcsh" の場合、次のようにシステム・コールを発行していく。
chdir("/");
dir_lookup("usr");
chdir("usr");
dir_lookup("open");
chdir("open");
dir_lookup("doc");
chdir("doc");
dir_lookup("tcsh");
このように、ルート・ディレクトリから一つ一つiノード番号を調べながら、chdir()で手繰っていく。最後に dir_lookup() により iノード番号を調べる。
このプログラムでは、中間段階のdir_lookup は不要である。実際のUNIXカーネルのファイルのオープンでは、このプログラムと同じように中間のiノードを次々と調べながら処理が進められている。chdir()やopen()を実現するためにも dir_lookup()のように、一つのディレクトリの内容を検索する仕組みが必要になる。