ファイル操作とファイル・システム
Menu Menu
目的
この実験では、Unixのシステム・コールを通じて、ファイル・システムの利用について学ぶ。具体的には、ファイルの作成、書き込み、読み込み、開く閉じる操作および、ディレクトリの読み込み操作について実習を行う。同時に、標準入出力の概念を学ぶ。
関連科目
情報204 オペレーティングシステム 必修、2単位
C言語
Cのプログラムは、ANSI-C で統一すること。
int
main( int argc, char *argv[] )
{
....
}
BSD/OSでもSolaris でも gcc を使うことによりコンパイラの違いを気にすることなくプログラムを作成することができる。しかし、どのような ccでもコンパイルできるよう可搬性(portablity)の高い記述が望ましい。しかし、OSの異なる場合も含めてportabilityを確保することは一般的には難しい。以下の例題でも、Solaris および BSD/OS の両方で動作するように工夫されていることに注意して欲しい。
報告書
それぞれの実験に付いて、
作成したプログラムへのポインタ (file server naha (/net/home/y99/j99xxxx )
にコピーして、その場所を書く)、
その説明(説明に必要なソースのコピーは最小限にすること)
(Makefileについても説明すること)、
および、
その実行結果を付けなさい。
この実験では、プログラムの説明では、フローチャートを付加する必要はない。開発環境と実行環境 (計算機、オペレーティング・システムのバージョン、コンパイラ)を載せなさい。それぞれの実験について、プログラム作成に要した時間を書きなさい。
一般的な注意
報告書は、日本語または英語で記述すること。プログラム、表、図、数式の羅列は、報告書とは認めない。図は必ず本文から参照すること。数学における証明のように、示すべき結論、用いる仮定と前提、推論の詳細について論理的に記述しなさい。
source の場所
ソースファイルの作成と読み書き
file-copy.c と stdio-thru.c は、いずれもファイルのコピーを行うプログラムである。それぞれの使い方を、以下に示す。
% make file-copy % ./file-cpoy 既存のファイル 新しいファイル % % make stdio-thru % ./stdio-thru < 既存のファイル > 新しいファイル %このように、file-copy.c は、引数で指定されたファイルを開き、その内容をコピーするプログラムである。stdio-thru.c は標準入力で指定されたファイルの内容を標準出力で指定されたファイルへコピーするプログラムである。
以下に file-copy.c のプログラムを示す。
/*
file-copy.c -- ファイルをコピーする簡単なプログラム
$Header: /home/h1/yas/slab-info1-os/3-file/RCS/file-copy.c,v 1.2 1995/03/08 13:57:05 yas Exp $
Start: 1995/03/04 16:40:24
*/
#include <stdio.h> /* stderr */
#include <fcntl.h> /* open(2) */
fprintf()関数、stderr変数を使うためには、stdio.h というヘッダ・ファイルを読み込まなければならない。open()システム・コールを使うためには、fcntl.h を読み込まなければならない。必要なヘッダ・ファイルは、マニュアルを引けば出ている。
main( argc,argv )
int argc ;
char *argv[] ;
{
if( argc != 3 )
{
fprintf( stderr,"Usage: %s from to\n", argv[0] );
exit( 1 );
}
file_copy( argv[1],argv[2] );
}
main() は引数のチェックをしてfile_copy()という関数を呼ぶ。
#define BUFFSIZE 1024
あとで変更することを考えて、バッファの大きさをマクロを使って定義する。これは、stdio.h に定義されている BUFSIZ というマクロを使った方が良い。
file_copy( from_name,to_name )
char *from_name, *to_name ;
{
int from_fd,to_fd ;
char buff[BUFFSIZE] ;
int rcount ;
/*
* O_RDONLY: 読み込み専用でファイルを開く
*/
from_fd = open( from_name,O_RDONLY );
ここでは、入力用のファイルを開いている。open()は、フィアルを開くシステム・コールである。UNIXではファイルを読み書きするためには、まずファイルを開かなければならない。O_RDONLYとは読み込み専用でファイルを開くことを意味している。open()システム・コールは、結果としてファイル記述子を返す。ファイル記述子は、負でない小さい整数(1-127の範囲)である。ファイル記述子は、read()システム・コールやwrite()システム・コールで実際にファイルを読み書きする時に使われる。
if( from_fd == -1 ) { /* エラーが起きたとき */
perror( from_name );/* ファイル名とエラーメッセージを表示し、 */
exit( 1 ); /* プロセスを終了する。 */
}
エラーが起きた時には、open()システム・コールは、-1 を返す。この時、エラーの番号がerrnoという変数に格納される。perror()は、errno変数を解析して、より詳しいエラー・メッセージを表示する関数である。(Solaris では日本語、BSD/OSでは英語で表示される) perror()の引数は、エラーメッセージともに表示される文字列である。
/* O_WRONLY: 書き込み専用でファイルを開く。
* O_CREAT: ファイルが存在しなければ作られる。
* O_TRUNC: ファイルが存在している時には、その大きさを0にする。
* 0666: ファイルを作る時のモード。実際には、umask の分落とされる。
*/
to_fd = open( to_name,O_WRONLY|O_CREAT|O_TRUNC,0666 );
if( to_fd == -1 ) {
perror( to_name );
exit( 1 );
}
ここでは出力用のファイルを開いている。O_WRONLYは、書き込み専用でファイルを開くことを意味している。O_CREATは、ファイルが存在しなければ作るように指示するものである。ファイルが存在する場合、上書きされる。O_TRUNCは、ファイルが存在している時には、その大きさを0にすることを指示するものである。0666 (C言語で0から始まる数は、8進数)は、ファイルを作る時のモードである。この数値にしたがって、ファイルのモード(ls -l でrwxrwxrwx と表示される部分)が決定される。作成されるファイルのモードは、ここで指定されたモードから現在のumaskが落された値となる。
ファイル記述子は、小さな整数である。次のように、printf() の %d 書式で画面に表示される。次のように表示されるであろう。
from_fd == 3, to_fd == 4
これより、以下のプログラムでは read(), write() の引数として直接 3, 4 と記述しても同じ結果になる。ただし、open(), close() を繰り返し行うと、この値が異なってくる。よって普通は、このように変数で指定しなければならない。
printf("from_fd == %d, to_fd == %d\n", from_fd, to_fd );
read() システム・コールは、第1引数で指定されたファイル記述子のファイルを読み込み、それを第2引数の番地へ保存する。読み込むバイト数は、第3引数で与えられる。結果として読み込んだバイト数を返す。通常は、BUFFSIZE が返される。read() システム・コールの結果、ファイル上の読み書きする位置が、実際に読み込んだバイト数だけずれる。ファイルの末尾近くや、ファイルが端末の時、BUFFSIZE 以下の値が返される。ファイルの末尾に行き着くと 0 が返される。エラーが起きると、-1 が返される。
write() システム・コールは、第1引数で指定されたファイル記述子のファイルへデータを書き込む。書き込まれるデータは、第2引数で与えられた番地から、第3引数で与えらた大きさである。write() システム・コールは、結果として書き込んだバイト数を返す。ファイル上の読み書きする位置が、実際に読み込んだバイト数だけずれる。通常は、BUFFSIZE が返される。空き容量不足などで書き込みが失敗した時には、-1 を返す。エラーが起きると、-1 が返される。
while( (rcount=read(from_fd,buff,BUFFSIZE)) > 0 )
{
if( write(to_fd,buff,rcount)== -1 )
{
perror( to_name );
exit( 1 );
}
}
close( from_fd ); /* ファイルを閉じる。*/
close( to_fd ); /* ファイルを閉じる。*/
}
close() システム・コールを実行してしまうと、もうそのファイル記述子は無効である。ここで、read() や write() を行うと、エラーが返される。
標準入出力を使ったコピー
stdio-thru.c は、標準入力で指定されたファイルの内容を標準出力で指定されたファイルへコピーするプログラムである。
/*
stdio-thru.c -- 標準入力から標準出力へのコピー
$Header: /home/h1/yas/slab-info1-os/3-file/RCS/stdio-thru.c,v 1.2 1995/03/08 13:58:59 yas Exp $
Start: 1995/03/04 16:40:24
*/
#include <stdio.h> /* stderr */
main( argc,argv )
int argc ;
char *argv[] ;
{
if( argc != 1 )
{
fprintf( stderr,"Usage: %s\n", argv[0] );
exit( 1 );
}
stdio_thru();
}
このプログラムは、引数を取らない。"<"や">"は、シェルにより解釈され、このプログラムには、既に開いたファイルのファイル記述子(0番と1番)として渡される。
#define BUFFSIZE 1024
stdio_thru()
{
char buff[BUFFSIZE] ;
int rcount ;
file_copy() とは違って、stdio_thru() では、ファイルを開く操作(open())を行うことなく、入出力(read(),write)を行っている。このような事が可能な理由は、UNIXでは、ファイル記述子 0, 1, 2 は、シェルにより開かれているからである。
while( (rcount=read(0,buff,BUFFSIZE)) > 0 )
{
if( write(1,buff,rcount)== -1 )
{
perror("stdout");
exit( 1 );
}
}
close( 0 ); /* ファイルを閉じる。*/
close( 1 ); /* ファイルを閉じる。*/
}
次のように file-copy と stdio-thru をコンパイルし、実行してみなさい。
% make file-copy
% ./file-copy 既存のファイル 新しいファイル
%
% make stdio-thru
% ./stdio-thru < 既存のファイル > 新しいファイル
stdio-thru を使ってファイルをコピーするためには、上で示したようにシェルの標準入出力切り替え機能を使わなければならない。
課題1 tee プログラム
file-copy.c , stdio-thru.c の二つのプログラムを参考にして、UNIXの tee プログラムと同じ機能を持つプログラムを作りなさい。プログラムの名前はteeではなくmytee としなさい。その骨組みを mytee.c に示す。
/*
mytee.c -- tee コマンドと似た動きをするコマンド
$Header: /home/h1/yas/slab-info1-os/4-file/RCS/mytee.c,v 1.2 1995/05/31 11:43:32 yas Exp $
Start: 1995/03/08 22:54:31
*/
#include <stdio.h> /* stderr */
#include <fcntl.h> /* open(2) */
main( argc,argv )
int argc ;
char *argv[] ;
{
if( argc != 2 ) {
fprintf( stderr,"Usage: %s filename\n", argv[0] );
exit( 1 );
}
mytee( argv[1] );
}
mytee( filename )
char *filename ;
{
/*
課題 1 は、この部分を完成させることである。file-copy.c の
file_copy() と stdio-thru.c の stdio_thru() を参考にしなさい。
*/
}
mytee は、次のようにして、標準入力を標準出力にコピーしならがら、同時にファイルにも保存するものである。
% grep pattern file1 | ./mytee rresult
この結果として、画面には、次のように grep コマンドを実行した時と同じ結果が表示される。
% grep pattern file1
同時に、ファイル result には、画面に表示された結果とまったく同じものが保存される。
tee コマンドの名前は、アルファベットの「T」に由来する。図形的に考えれば動きを理解することができる。tee コマンドは、左(パイプラインで標準入力) から入ったデータをした(引数で与えられたファイル)に保存しながら右に(標準出力)にも出力する。
入力 ---------- 出力
|
|
ファイル
tee コマンドは、時間のかかる処理の結果や、後で参照したい中間結果をファイルに保存するために利用される。次の例は、makeコマンドの実行けっkをmake.out に保存すると同時に、それをuserにメールで送るものである。
% make |& tee make.out | Mail -s 'make result' user
これにより、userは、メールが届いたことで、make コマンドの実行終了を知ることができる。同時に、作業していたディレクトリでその結果を参照することができる。
もう一つの方法として、ファイルを経由してtailを使って出力を二つに分けることができる。例えば、
% make >& make.out &
% tail -f make.out
とすると、make.out を書き込みながら同時にmake.outの内容を標準出力に書き出すことができる。tail の -f オプションは、定期的にファイルの最後をチェックし、最後が拡張されていたら、その部分を書き出す。BSD/OSDで、
% tail -f /var/log/maillog
としながら、そこでメールを送って見よう。
この二つの方法の利点と欠点について考察して見よ。
課題 2 cat コマンド
UNIXの cat コマンドと同様の働きをするプログラムを作りなさい。このプログラムの名前をmycatとする。引数として、何も与えられなかった時には、標準入力を標準出力にコピーしなさい。また、引数として - が与えられた時には、その位置で標準入力をコピーしなさい。例えば、次のような場合、file1 と file2 の内容の間に、標準入力の内容をコピーしなさい。tail -f に相当する機能は、fseek やusleepを使って作ることができるが、この時点ではかなり難しい。
ファイルの属性
UNIXでは、stat() (あるいは、lstat(),fstat() )システム・コールを用いてファイルの属性(ファイルの管理情報)を調べることができる。ファイルの属性を調べ、画面に出力するプログラムを stat.c に示す。
/*
stat.c -- stat システム・コールのシェル・インタフェース
$Header: /home/h1/yas/slab-info1-os/4-file/RCS/stat.c,v 1.2 1995/05/31 11:43:32 yas Exp $
Start: 1995/03/07 20:59:12
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
main( argc,argv )
int argc ;
char *argv[] ;
{
if( argc != 2 ) {
fprintf( stderr,"Usage:%% %s filename \n",argv[0] );
exit( 1 );
}
stat_print( argv[1] ); /* 引数はファイル */
}
stat_print( path )
char *path ;
{
struct stat buf ;
if( stat( path,&buf ) == -1 ) {
perror( path );
exit( 1 );
}
printf("path: %s\n",path );
printf("dev: %d,%d\n",major(buf.st_dev),minor(buf.st_dev) );
printf("ino: %d\n",buf.st_ino );
printf("mode: 0%o\n",buf.st_mode );
printf("nlink: %d\n",buf.st_nlink );
printf("uid: %d\n",buf.st_uid );
printf("gid: %d\n",buf.st_gid );
printf("rdev: %d,%d\n",major(buf.st_rdev),minor(buf.st_rdev) );
printf("size: %d\n",buf.st_size );
printf("atime: %s",ctime(&buf.st_atime) );
printf("mtime: %s",ctime(&buf.st_mtime) );
printf("ctime: %s",ctime(&buf.st_ctime) );
printf("blksize: %d\n",buf.st_blksize );
printf("blocks: %d\n",buf.st_blocks );
}
次のようにコンパイルして、実行して見なさい。
% make stat
gcc -O2 -o stat stat.c
% ./stat stat.c
path: stat.c
dev: 3,6
ino: 198491
mode: 0100644
nlink: 1
uid: 5020
gid: 0
rdev: 0,807163
size: 1171
atime: Thu Apr 30 23:16:56 1998
mtime: Sun Mar 22 20:13:03 1998
ctime: Sun Mar 22 20:21:52 1998
blksize: 8192
blocks: 4
%
この結果から次のようなことがわかる。
(1) ファイル名は stat.c である。
(2) ファイルが存在するデバイスは、メジャー番号3,マイナー番号6である。
(ls -l /dev/wd0h としてみよう)
(3) iノード番号は 198491 である。
(4) モードは 0100644 である。
(5) リンク数は 1 である。
(6) ファイルの所有者のuidは 5020である。
(7) ファイルのグループidのは 0である。
(8) デバイスの識別子は、0,807163 である。( ここではあまり意味がない)
(9) ファイルの大きさは 1171 バイトである。
(10) 最終アクセス時刻は、Thu Apr 30 23:16:56 1998 である。
(11) 最終更新時刻は、Sun Mar 22 20:13:03 1998 である。
(12) ファイルの作成時刻は、Sun Mar 22 20:21:52 1998 である。
(13) 入出力に適したブロックサイズは、8192である。
(14) 実際に使われているブロックサイズは、4である。1ブロックは512バイト
(ファイルの存在するファイル・システムによる)
ここで、モードが8進数で0100644であることから、ファイルの型 (普通のファイルかディレクトリかという情報)を調べることができる。0100644の上位4bit、つまり0170000とのAND(論理積、Cでは&演算子)を取った結果は0100000となる。この値は、普通のファイルを意味する。ディレクトリの場合、0040000となる。これらの数は、<sys/stat.h>(/usr/include/sys/stat.h など)で定義されていりう。例えば、BSD/OSの場合、/usr/include/sys/stat.h で以下のように定義されている。
#ifndef _POSIX_SOURCE
#define S_IFMT 0170000 /* type of file mask */
#define S_IFIFO 0010000 /* named pipe (fifo) */
#define S_IFCHR 0020000 /* character special */
#define S_IFDIR 0040000 /* directory */
#define S_IFBLK 0060000 /* block special */
#define S_IFREG 0100000 /* regular */
#define S_IFLNK 0120000 /* symbolic link */
#define S_IFSOCK 0140000 /* socket */
#define S_IFWHT 0160000 /* whiteout */
#define S_ISVTX 0001000 /* save swapped text even after use */
#endif
#define S_ISDIR(m) ((m & 0170000) == 0040000) /* directory */
#define S_ISCHR(m) ((m & 0170000) == 0020000) /* char special */
#define S_ISBLK(m) ((m & 0170000) == 0060000) /* block special */
#define S_ISREG(m) ((m & 0170000) == 0100000) /* regular file */
#define S_ISFIFO(m) ((m & 0170000) == 0010000 || \
(m & 0170000) == 0140000) /* fifo or socket */
したがってプログラム中では、次のようにしてファイルの型をチェックすることができる。
struct stat buf ;
switch ( buf.st_mode & _IFMT ) {
case S_IFREG:
printf("普通のファイルです。\n");
braek;
case S_IFDIR:
printf("ディレクトリです。\n");
braek;
default:
printf("ファイルでもディレクトリでもありません。\n");
braek;
}
あるいは、<sys/stat.h>に含まれている S_ISREG(), S_ISDIR()というマクロを用いて、次のように記述する方法もある。
if ( S_ISREG(buf.st_mode) {
printf("普通のファイルです。\n");
} else if ( S_ISDIR(buf.st_mode) {
printf("ディレクトリです。\n");
} else {
printf("ファイルでもディレクトリでもありません。\n");
}
モードの下位9bit(上の例では、8進数で644)は、許可されたアクセス方法を表している。その9bitは、3bitづつに区切られており、上位から所有者(user)、グループ(group)、その他(others)に許可されているアクセス方法を示している。所有者はonwerとも呼ばれる。
各3bitは次の様なアクセス方法が許可されていることを意味する。
ls -l 3bit アクセス権
r 4 読み込み可
w 2 書き込み可
x 1 実行可または、ディレクトリのアクセス可
課題 3 ls -l コマンドの仕組み
stat()システム・コールを用いて、ls -l filename と似たような結果を出力するプログラムを作りなさい。このプログラムの骨格をmyls-l.c に示す。
/*
myls-l.c -- ls -l filename とにた動きをするプログラム
$Header: /home/h1/yas/slab-info1-os/4-file/RCS/myls-l.c,v 1.2 1995/06/08 08:23:45 yas Exp $
Start: 1995/03/07 20:59:12
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <pwd.h>
main( argc,argv )
int argc ;
char *argv[] ;
{
if( argc != 2 ) {
fprintf( stderr,"Usage:%% %s filename \n",argv[0] );
exit( 1 );
}
ls_l( argv[1] ); /* 引数はファイル */
}
ls_l( path )
char *path ;
{
/*
実験4.2の課題は、この関数を完成させることである。下請け関数として、以
下で定義されている uid_print() を使ってもよい。stat.c を参考にして、ファ
イルの属性を取りだし、そのうちのいくつかを表示しなさい。
*/
}
/*
struct stat の st_uid フィールドには、uid が入っている。これを数値とし
てではなく、ログイン名として表示するためには、次の関数 uid_print() を
使うとよい。引数は、uid_t 型(unsigned short)である。
*/
uid_print( uid )
uid_t uid ; /* unsigned short, in <sys/types.h > */
{
struct passwd *pwd ;
pwd = getpwuid( uid );
if( pwd == NULL ) {
printf("%d ",uid );
} else {
printf("%s ", pwd->pw_name );
}
}
ls -l では、3つの時刻のうち、どの時刻が表示されているかを調べなさい。また、他の二つの時刻を表示させる方法を調べなさない。また、その時刻を変更する方法について考察し実際に変更して見なさい。
myls-l の表示形式は、ls -l と完全に一致しなくてもよい。例えば、時刻の表示は、上のstat.c と同じでも良い。localtime(), strftime() ライブラリ関数を利用すると、時刻の表示をより簡単に、ls -l の表示に近づけることができる。
プログラムの引数となるファイルの数は一つとする。複数のファイルについてls -l と同様の表示をするように拡張しても良い。
引数とsてディレクトリの名前が与えられた場合にも、ディレクトリの内容ではなくディレクトリ自身の属性を表示する。シンボリックリンクには対応しなくて良い。(これは、ls -ldL の動作と似ている)
課題4 シンボリック・リンクの内容の表示
課題3で、lstat() と readlink() の2つのシステム・コールを用いて、ls -l と同じようにシンボリック・リンクの内容を表示しなさい。
課題5 ディレクトリの内容
課題3で、opendir(), scandir() ライブラリ関数を利用したり、あるいは、次の実験のプログラム dir-list.c と組み合わせて、より ls -l の表示に近づけなさい。余裕があれば qsort() も使って見ること。
ディレクトリの検索
dir-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()のように、一つのディレクトリの内容を検索する仕組みが必要になる。