書き込み中に削除されたファイルを救出する

こんにちは、末廣です。
これは TECHSCORE Advent Calendar 2015 の 18日目の記事です。

ある日のこと

とあるアプリケーションでログファイルのローテーションが失敗して、削除されたファイルに書き込み続けるということが起こりました。

後輩「ログファイルのローテーションに失敗して、削除されたファイルに書き込み続けてるんですけど、救い出す方法ないですかね。」

私「どれどれ、PID は 26778 なのね……」

私「あー、app.log はもう削除されてるのね。アプリが掴んだままだからファイルの実体は残ってるけど、アクセスする手段がないなー」

後輩「/proc/26778/fd/5 を cat したらどうっすか?」

私「それって、シンボリックリンクみたいなもんやからアカンやろ。シンボリックリンクは参照するファイル名が書かれてるだけやし。」

私「でもまあ、一応試してみるか。」

私「Σ(゚д゚;) ヌオォ!? こいつ、読み出せるのかー」

後輩「これで救い出せますね!」

対象の /proc/<pid>/fd/<fd> を読み出すことで、削除されたファイルの内容を救い出せることが分かりました。

更に調べてみる

stat に "-L" オプションを付けて /proc/26778/fd/5 の参照先の情報を表示してみます。

きちんと参照先の情報が取得できているようです。通常と異なるのは Link カウントが 0 になっている点です。これはファイル名がなくなっているからですね。

ファイル名を付与できないか?

参照先の情報(inode)が取得できるのなら、"ln -L" でファイル名を付与することができそうな気がします。"-L" オプションは「リンクを作成する対象のシンボリックリンクをたどる」ことを示します。

では、試してみます。

残念、失敗です。対象の inode 番号を知ることはできるのに、ハードリンクは作らせてくれません。
ln コマンド内部で呼び出されているシステムコールを調べたところ、linkat システムコールが使われていることが分かりました。

linkat(2) の man ページによると、linkat などの *at 系のシステムコールは Linux 2.6.16 で追加され、glibc 2.4 でサポートされたそうです。

link システムコールは oldpath と newpath を指定するだけでしたが、linkat システムコールはそれぞれの親ディレクトリをファイルディスクリプターで指定することができます。

flags に指定できるのは次の 2つです。

"link -L" は AT_SYMLINK_FOLLOW を指定していたんですね。

最後のあがき

ダメ元で AT_EMPTY_PATH の方も試してみます。これは ln コマンドではできないので、C で簡単なプログラムを書いてみました。

12行目の open では O_PATH フラグを指定しています。こうすると linkat などの *at 系のシステムコールや close、fstat、dup などでの利用に制限されたファイルディスクリプターを得ることができます。ファイル自体はオープンしていないので、read や write などのオペレーションをすることはできません。

では、コンパイルして実行してみます。AT_EMPTY_PATH は Linux 独自の拡張なので _GNU_SOURCE を define してやる必要があります。

削除されたファイルをきちんと指定できていることは inode 番号から確認できます。がしかし、ハードリンクの作成はできませんでした。

linkat(2) の AT_EMPTY_PATH の項をよく読むと、これは O_TMPFILE フラグを指定して作成した名無しのファイルに名前を与えるときに使うのを意図しているようです。それ以外の名無しの場合は ENOENT エラーとなるので、どだい無理な話でした。

O_TMPFILE フラグはいくつか便利な用途があるようなので、機会があれば記事にしてみたいと思います。

まとめ

書き込み中に削除されたファイルは、対応する /proc/<pid>/fd/<fd> を読み出すことで内容を救出することができました。削除されたファイルの inode まで到達できるものの、ファイル名を付与することはできなさそうです。
Linux のファイル操作まわりは、open 系システムコールの新たなフラグや、*at 系のシステムコールなど、まだまだ新しい機能が追加されていて面白そうです。

Comments are closed, but you can leave a trackback: Trackback URL.

Advent Calendar 2015の連載記事

  1. TECHSCORE Advent Calendar 2015
  2. Redshift と PostgreSQL に同時に JDBC 接続する
  3. Lombok で Spice up your Java!
  4. 画像を指定するだけ!非デザイナーでも簡単にそれっぽい配色ができるツールを作ってみた
  5. 新卒文系エンジニアの記録:配属半年間の失敗を振り返ってみた
  6. 非同期処理のすすめ
  7. ioDrive2の導入で支える、そのIOPS - 導入検討編.
  8. GoでパイプラインからSlackに通知する
  9. fuse でオレオレファイルシステムを作ってみた (Haskell で)
  10. Erlang はじめました
  11. ちょっと地味なビルドとリリースの話 (レガシーシステム改革、はじめの一歩)
  12. Java8 最速 boolean[] to Stream 選手権
  13. Google Apps の Directory API にてWebブラウザを介さずに認証する
  14. 風データをビジュアルに表現する
  15. マイクロフレームワーク「Ninja」を使ってみる
  16. 赤ちゃんvimmerからよちよちvimmerにクラスチェンジを果たすためのTips
  17. PostgreSQL FDW を作ってSQLでログ検索してみた
  18. Goで偽名ジェネレータを作りました
  19. 書き込み中に削除されたファイルを救出する
  20. 運用情報更新のススメ
  21. ちゃんと読んでくれましたか?
  22. Presto コネクターを実装する 第三回
  23. Ruby2.3を触ってみる
  24. Git 困ったときのtips集
  25. 5分で読む入門編:Java 8 ラムダ式 コレクション編(2)リストの検索
  26. CloudFront (+ S3) + JWPLAYER で様々なデバイスのブラウザから動画をストリーミング再生する