Edited at

macOSの$TMPDIR以下のファイルが部分的に削除された理由

More than 1 year has passed since last update.

手元のMacOSX El Capitan(10.11)でのトラブルです。

某OSSのtar ballを$TMPDIR以下に展開してビルドしていたのですが、なぜか展開後のファイルのうち0バイトのファイルだけ1日後に消える事件が発生しました。

消される0バイトのファイルのうちinstall-shconfigureで必要なファイルなので、昨日作業したディレクトリで今日も./configureしようと思ったら次のようにエラーで怒られてしまいます。

$ ./configure

checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for a sed that does not truncate output... /usr/local/bin/gsed
configure: error: cannot find install-sh, install.sh, or shtool in "." "./.." "./../.."

手元の環境の$TMPDIRは次のようなものでした。

$ echo $TMPDIR

/var/folders/_0/lkp460qj0j1ftylt6w_3mjxr0000gn/T/


犯人はperiodicか?

調べてみると近い話題で苦しんでいる人が見つかります。

macOS上でJenkinsのファイルが消えてしまうトラブルがあり、periodicで起動される日次バッチが悪さをしているのでは?という内容です。

しかし、日次のファイル消去スクリプト /etc/periodic/daily/110.clean-tmps/tmp 以下が対象で、今回話題にしている /var/folders 以下を消すようには見えません。また、このスクリプトが消したファイルが/var/log/daily.outに出力されるはずですが、該当ファイルを消した記録は見当たりませんでした。

というわけでperiodicが犯人ではないようです。


毎晩3時35分にdirhelperがファイルを消している

dtraceを有効にしてfbt::VNOP_REMOVE:entryを監視してみたところ、毎日3時35分に$TMPDIR以下のファイルを消しているdirhelperというプロセスを見つけました。

1414517           0  17049 dirhelper    VNOP_REMOVE  tests/empty.html

1414519 0 17049 dirhelper VNOP_REMOVE fileinfo/EXPERIMENTAL
1414530 0 17049 dirhelper VNOP_REMOVE pdo_oci/EXPERIMENTAL
1414531 0 17049 dirhelper VNOP_REMOVE bug53872/second.txt
1414535 0 17049 dirhelper VNOP_REMOVE skeleton/EXPERIMENTAL
1414546 0 17049 dirhelper VNOP_REMOVE image/blank_file.bmp
1414551 0 17049 dirhelper VNOP_REMOVE 5.6.31/install-sh
1414552 0 17049 dirhelper VNOP_REMOVE 5.6.31/missing
1414552 0 17049 dirhelper VNOP_REMOVE 5.6.31/mkinstalldirs
1414552 0 17049 dirhelper VNOP_REMOVE cgi/php.sym
1414553 0 17049 dirhelper VNOP_REMOVE pi3web/php.sym
1414554 0 17049 dirhelper VNOP_REMOVE thttpd/stub.c
1414556 0 17049 dirhelper VNOP_REMOVE build/default.manifest

今回問題になったinstall-shも含め、多くのファイルが消されていることがわかります。

このdirhelperとは何者でしょうか。man dirhelperによれば、/System/Library/LaunchDaemons/com.apple.bsd.dirhelper.plistの設定で起動されている管理用コマンドのようです。このplist中に次の内容が見つかります。

    <dict>

<key>CLEAN_FILES_OLDER_THAN_DAYS</key>
<string>3</string>
</dict>

3日前より古いファイルを消す設定、ということのようです。またdirhelperのソースコードを見ると、/var/folders以下の掃除をしているらしいこと、ファイルのbirthtimeとatimeの両方が指定日数より古かったらファイルを消していることがわかります。

下記URLのコメントも参考になりました。


なぜ0バイトのファイルだけ消えたのか

macOSでは$TMPDIR以下のファイルは寿命が3日しかないということなら設定通りであり何の問題もありませんが、なぜ0バイトのファイルだけが1日で消えてしまったのでしょうか。

これはmacOS標準のbsdtarの挙動が関係しています。bsdtarでファイルを展開するとbirthtimeはアーカイブの時刻と同じ時刻(大抵は3日前より古い)になります。一方、atimeは原則ファイルを展開した時刻になるのですが、0バイトのファイルだけはunix epoch(1970-01-01 0時)になるようです。

つまり、tar ballをbsdtarで展開した場合、0バイトのファイルは展開直後からdirhelperの削除対象になるのです。夜中の3時30分にtar ballを展開したのが3時40分に勝手に壊れているようだと悲惨ですね。

ちなみにGNU tarを使えば0バイトのファイルのatimeも変更してくれますので、展開後3日間は消されることはありません。


こんないつファイルが消されるかわからないディレクトリに居られるか!俺は出て行くぞ!(死亡フラグ)

どうやらmacOSの$TMPDIR以下でOSSのビルドをしてはいけない、という身も蓋もない結論のような気がしますね。

この問題はSierraでは修正済みなのかもしれませんが、私の手元はEl Capitanで止まっているため未確認です。


(2018-01-23追記)誰が悪いのか

この問題はdirhelperのバグと言って差し支えないでしょう。アーカイバ類が作るファイルが過去のbirthtimeになるのは当然の挙動です。また、atimeを見ること自体は良いのですが、まず見るべきはctimeでしょう。昨日展開したファイルであればctimeだけは確実に昨日の日付になるわけですから、これを見ない理由がありません。今の仕様だと作ったばかりのファイルを即座に消されてしまう可能性さえあるので、dirhelperがatimeを見るような修正は入れて頂きたいところです。

一方で、atimeを見ていたとしても上記の問題が完全に解決するわけではありません。configureinstall-shの存在チェックしか行わないので、毎日ビルドを繰り返していたとしてもinstall-shのbirthtime・atime・ctimeとも更新されず、4日後には削除される運命です。

結局のところ、使われていないファイルだけを消すという要求自体が無茶だとも言えるでしょう。つまり、dirhelperの実装上の問題というよりは要求仕様の問題なのかもしれません。


参考URL