Unix/Linuxプログラミング理論と実践 第2章(プログラミング課題) つづき

備忘録です.
環境:FreeBSD 8.2-STABLE
http://www.amazon.co.jp/gp/product/4048700219

2.11の話.FreeBSD版のソースコードから引用.

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構造体を取得するのが分かりやすそう.