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

MacOSX El Capitan(10.11)で起きたトラブルです（2020-06-06追記： Mojave(10.14)でも状況は同じです）。

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

消されるファイルのうち`install-sh`は`configure`で必要なファイルなので、昨日作業したディレクトリで今日も`./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`か？

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

- [人類最大の謎。JenkinsのCSS壊れる問題が解明された - laiso](http://blog.lai.so/entry/2013/12/12/%E4%BA%BA%E9%A1%9E%E6%9C%80%E5%A4%A7%E3%81%AE%E8%AC%8E%E3%80%82Jenkins%E3%81%AECSS%E5%A3%8A%E3%82%8C%E3%82%8B%E5%95%8F%E9%A1%8C%E3%81%8C%E8%A7%A3%E6%98%8E%E3%81%95%E3%82%8C%E3%81%9F)
- [Macの定期処理 - Qiita](http://qiita.com/2or3/items/88724e8b5b90a1ed1c80)

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のソースコード](https://opensource.apple.com/source/system_cmds/system_cmds-597.90.1/dirhelper.tproj/dirhelper.c.auto.html)を見ると、`/var/folders`以下の掃除をしているらしいこと、ファイルのbirthtimeとatimeの両方が指定日数より古かったらファイルを消していることがわかります。

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

- [os x - tmuxのunixドメインソケットを消している犯人を特定したい - スタック・オーバーフロー](https://ja.stackoverflow.com/questions/21222/tmux%E3%81%AEunix%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E6%B6%88%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E7%8A%AF%E4%BA%BA%E3%82%92%E7%89%B9%E5%AE%9A%E3%81%97%E3%81%9F%E3%81%84/21492)

# なぜ一部のファイルだけ消えたのか

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

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

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

私の場合ビルドしていたので、ビルドに必要なファイルはコンパイラが読むためatimeが更新されて削除対象から外れたものの、問題となった`install-sh`はファイルの存在チェックしか行われないためatimeが書き換わらず、`dirhelper`に消されてしまったというわけです。

ちなみにGNU tarを使えばファイル展開時にatimeが現在時刻になるため、展開後3日間は消されることはありません。

# こんないつファイルが消されるかわからないディレクトリに居られるか！俺は出て行くぞ！

どうやらmacOSの`$TMPDIR`以下でOSSのビルドをしてはいけない、という身も蓋もない結論のような気がしますね。`$HOME/src/`以下で行うなどした方が平和なんじゃないでしょうか。

# （2018-01-23追記）誰が悪いのか

この問題は`dirhelper`のバグと言って差し支えないでしょう。使われていないファイルを探すのにbirthtimeとatimeしか見ないというのは現実に即していません。

アーカイバ類が作るファイルが過去のbirthtimeになるのは当然の挙動です。既に指摘したように`bsdtar`だとatimeもアテにならないわけで、まず見るべきはctimeでしょう。昨日展開したファイルであればctimeだけは確実に昨日の日付になるわけですから、これを見ない理由がありません。今のままだと3時30分に展開したファイルを3時35分に消されてしまう可能性さえあるので、`dirhelper`がctimeも見るような修正をぜひ入れて頂きたいところです。

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

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

# （2020-06-06追記）最近のmacOSでも挙動は変わっていない

macOS Mojave(10.14)でも上記の挙動は変わっていませんでした。この挙動はバグだろうと私は考えていますが、Appleはそう考えていないようですね。

バグかどうかはさておき、macOSの`dirhelper`がファイルを消す基準と`bsdtar`の相性が悪いのは間違いありません。ユーザーとしては`$TMPDIR`の使い方に注意するしかないでしょう。

# 参考URL

- http://www.mobileread.mobi/forums/showthread.php?t=273999 （ほぼ同じ話題）