Unix/Linuxプログラミング理論と実践 第2章(プログラミング課題) つづき
備忘録です.
環境:FreeBSD 8.2-STABLE
http://www.amazon.co.jp/gp/product/4048700219
manを読んでみる
コピー元とコピー先の両方が同一ファイルだとコピーに失敗する.
$ man cp ... If cp detects an attempt to copy a file to itself, the copy will fail.
cpを実行してみる
同一ファイルに対して実行すると下記のとおり,警告が出力される.
$ cp file1 file1 cp: file1 and file1 are identical (not copied).
ソースコード調査
"are identical (not copied)."をコード中で探すと,cp.cのl.321以降で登場する.
if (to_stat.st_dev == curr->fts_statp->st_dev && to_stat.st_ino == curr->fts_statp->st_ino) { warnx("%s and %s are identical (not copied).", to.p_path, curr->fts_path);
to_statとcurr->fts_statpを見つけて,そのメンバを見れば何をやっているのかが分かりそう.順に探す.
to_statとは?
to_statは以下のとおり定義され,データを格納されている.
stat()は,第1引数のパスが指すファイルの情報を第2引数に格納する.詳細は,$ man 2 stat で.
struct statを読めば上で何を比較しているのかすぐに分かるけれど,ここはあえて置いておく.
struct stat to_stat; if (stat(to.p_path, &to_stat) == -1) dne = 1;
stat()の第1引数で渡しているtoの定義はつぎのとおり.
static char emptystring[] = ""; PATH_T to = { to.p_path, emptystring, "" };
ここで出てくるPATH_Tは,cp.cと同じディレクトリのextern.hに定義あり.
typedef struct { char *p_end; /* pointer to NULL at end of path */ char *target_end; /* pointer to end of target base */ char p_path[PATH_MAX]; /* pointer to the start of a path */ } PATH_T;
それで,実際にto.pathに何が格納されているのかというと,コピー先のパスで初期化されている.
target = argv[--argc]; if (strlcpy(to.p_path, target, sizeof(to.p_path)) >= sizeof(to.p_path)) errx(1, "%s: name too long", target);
ということで,to_statにはコピー先ファイルの情報が格納されていることが分かった.
curr->fts_statpとは?
currは,FTSENT型へのポインタで,fts_read()の結果が格納されている.
FTSENT *curr; for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0) {
ftspは,FTS型へのポインタで,fts_opne()の結果が格納されている.
FTS *ftsp; if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL)
ここで出てきたFTSとFTSENTは,FreeBSDが提供するftsライブラリ(UNIXのファイル階層を再帰的に処理するときに役立つ)の構造体.
FTS構造体は,ファイル階層を表現して,FTSENT構造体はファイル階層のファイルを表現するとのこと.
詳細は,$ man fts 参照.ftsは,よく分かっていない.
fts_open()が実行される段階で,argvを渡している.
このargvは何を指しているのか.
argcとargvに格納された値は,プログラム中で変更されている.
- オプションのチェックが終わった段階で,argvは{"cp", "file1, "file2"}の"file1"を指し,argcには2が格納されている状態.
その後,下の処理が行なわれるため,fts_opne()実行時に,argvは{"cp", "file1", NULL}の"file1"を指している.
/* Set end of argument list for fts(3). */ argv[argc] = NULL;
argvは"cp"の次の"file1"を指しており,コピー先ファイルを示す最後のエントリは消されている(NULL).
それで,fts_open()にはコピー元ファイル名が渡される.
fts_open()で,渡したファイル群に対するFTS構造体を受け取り,fts_read()で順にファイルのFTSENT構造体を受け取る.
したがって,currにはコピー元ファイルの情報が入ることになる.
結果,コピー元ファイルとコピー先ファイルが同一であると判断する基準は,以下の2つを満足するときであると分かる.
- コピー元ファイルとコピー先ファイルのあるデバイスIDが等しい
- コピー元ファイルとコピー先ファイルのinodeが等しい
どう実装するのか
2章で作ったcpコマンドは,コピー元が1つしか無い前提.
そのため,コピー元もコピー先と同じように,stat()を利用してstat構造体を取得するのが分かりやすそう.