FreeBSDで、下のような驚くべき現象を目の当たりにした。(まれにしか再現しない)
1 2 3 4 5 6 7 8 9 |
knu@archon[2]% sh $ ls a b a b $ touch -r a b $ [ a -nt b ] && echo 'ガーソ!' ガーソ! $ touch -r a a $ [ a -nt b ] || echo 'なんで?' なんで? |
原因を探ったところ、以下の事実がわかった。
- ファイルシステム上ではファイルの更新時刻がナノ秒単位で格納されており、タイミングによってマイクロ秒よりも細かい精度の値が記録される。
- stat(2)の結果から、st_mtimespec.tv_nsecを見ればナノ秒の値が取れる。
- sh(1)のビルトインtestコマンドの実装はtest(1)を実体としている。
- test(1)は-ntなどでの時刻の比較において、秒単位(tv_sec)まで一致したときはナノ秒の値(tv_nsec)同士を比較している。
- 一方、時刻を設定・変更するインターフェースにはutime(3)とutimes(2)があるが、それぞれ秒、マイクロ秒の精度でしか時刻を設定できない。
- touch(1)はutimes(2)を用いて、マイクロ秒単位でファイルの時刻を設定する。
- 従って、touch(1)の-rで更新時刻をコピーしても、参照元のファイルにマイクロ秒以下の端数がある場合、設定先のファイルに設定される更新時刻では端数が抜け落ちているため、参照元のファイルの方が「新しい」ということになってしまう。
これは困ったぞ。ナノ秒の精度で取得はできるが設定はできない、という非対称なことになっているのだ。ふつうタイムスタンプをコピーしたら同じ時刻になると思うじゃん!
解決策としては、まっとうな考え方をすれば、ナノ秒単位で設定するインターフェースを用意するのが筋かもしれない。ただし、システムコールの追加または変更なので慎重な議論に基づいた判断が必要な上、互換性を考えると問題の解決が相当先になってしまう。
とりあえずは、test(1)がそんな細かい単位は無視するようにするのが手っ取り早そう。実際、bashやzshでは、プラットフォームにもよって秒あるいはマイクロ秒以下の端数は無視するようだ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Index: test.c =================================================================== RCS file: /mirror/freebsd/ncvs/root/src/src/bin/test/test.c,v retrieving revision 1.52 diff -u -r1.52 test.c --- test.c 19 Aug 2002 09:19:31 -0000 1.52 +++ test.c 7 May 2004 17:06:54 -0000 @@ -527,7 +527,7 @@ if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec) return 0; - return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec); + return (b1.st_mtimespec.tv_nsec / 1000 > b2.st_mtimespec.tv_nsec / 1000); } static int |
さて、これを説得するのはめんどうだなあ…。
current@FreeBSD.orgに投げたところ、Bruce Evans先生からリプライがあり、そもそも vfs.timestamp_precision=0 (TSP_SEC; デフォルト)であればマイクロ秒以下の値は記録されないはずだそうだ。
あと、もちろんカーネル内には nanotime(9) という関数があるが、上記の vfs.timestamp_precision の値によって適切に値を丸めたりする処理が一部未実装だそうで、今すぐユーザランドにさらすわけにもいかない。
と、そのあたりの今後の顛末は後日フォロー予定。