Developer's Blog

Ubuntu で $ rm ~/.bashrc を実行してしまった

新規事業部の高田です。

この記事は、私が業務中に体験した血の気が引く失敗とその顛末を、弊社の金曜のお昼に開かれているランチタイム勉強会でお話したところエンジニアだけにウケたので、「よーし、世間のウケも取りに行っちゃうぞー」と調子に乗って下心まる出しで焼き直しネタをお送りする予定です。

ランチライム勉強会とは

弊社では有志が集まって毎週金曜日のお昼にランチライム勉強会というものを開催しています。

勉強会とは言え、各支社をテレビ会議システムでつなぎながらご飯を集まって食べつつ、ゆるーいネタを発表しあうような場です。大きなマサカリは飛んでこないので、気軽に発表の練習が出来る場でもあります。

ところで

ところで

 

あなたの血の気が引く瞬間は?
(業務内で)

 

案件に着手してから、
見積りから機能が抜け落ちていて
「あっ!?」
ってなった瞬間?

 

納期が来週なのに、
週末の帰る直前に
「これ、バグじゃないですか?」
とか相談された瞬間?

 

コード以前に契約書にバグがあって
「あれっ、これも契約書の  
  範囲内なんでしたっけ?」
と締結後に言われてから
その存在に気づいた瞬間?

 

もっと他に、
身近なものがありますよね?

 









− + ×
$ rm ~/.bashrc
$

 

今回は、間違って rm を実行した場合のお話です。

rm を知らない人にダメージの度合いを説明する

Windows や Mac を日常的に使っている人なら、ファイルを捨てたらゴミ箱にあるという認識があるんじゃないでしょうか。

なので、間違って捨てればそのファイルを拾いに行けばいい、と。

しかし、 rm でファイルを消すとどこも経由せずに「スッ」とファイルがいなくなります。当然ゴミ箱にもありません。

もし大切なファイルを間違って rm で消してしまって、ゴミ箱を漁ったところで見つかりもしないとしたら、どうでしょうか。

なんで rm してしまったの?

このランチタイム勉強会が開催される前日に、ちょうど Python を書いていたんです。

で、Python 3.6 を使ってコードを書きたかったんですが、私が日常的に開発に使用している Lubuntu (Ubuntu) 16.04 LTS では、 Python 3.6 が公式配信されていないんですよね。

そうなるともう、私としては

「あっ、こりゃ自分で Python 3.6 そのものをソースからビルドするしかないな」

ってなるじゃないですか。

 

で、「pyenvとか入れちゃうぞー」とかして、その設定を .bash_profile に書いてみたんですが、「やっぱり .bashrc に書きなおそうかな」と思ってですね、こう、咄嗟に

− + ×
$ rm ~/.bashr

 

まで打って、tab を押してですね

− + ×
$ rm ~/.bashrc

 

「あっ、これ違うわ」

と思いながら、こう、おもむろに小指がですね、Enter にですね

− + ×
$ rm ~/.bashrc
$

 

うわああああああああああああああああ!!!!

さて、どうしよう?

環境変数は今開いているターミナルに含まれてはいますが、これを env コマンドで全部表示して .bashrc に移植すれば……

あっ、ダメそう _(´ཀ`」 ∠)_

誰か助けて

inode から復活させる方法の考察

inode が分かれば、debugfs コマンドでディスク上のデータブロックを特定して、dd コマンドで複写することは可能です。ですが、消したファイルの inode はどう調べれば……?

ファイルを消すと、ディレクトリエントリ上ではデータの残骸が残ります。そのエリアに更にデータが上書きされるまでの間は、かろうじて読みだすことが可能です。

私の開発環境は他のエンジニアとは違い 2012 年製の DOS/V マシンに Linux を入れて動かしている環境だったため、幸いにもディスクが HDD でした。 HDD であれば、 SSD のように知らない内に trim されてデータがバイバイされることは極めて少ないです。

私の環境で使用している ext3 ではジャーナリング機能を有しています。ジャーナルログには、inodeとファイル実体の関係をひも付けたデータを持っています。

inodeはブロックポインタを指し示しており、このブロックポインタを参照なし(0)に書き換えることでファイルを削除します。これで最新のジャーナルからはファイルが見えなくなるので、消えたように見えるという仕組みです。

そして、最後にinodeテーブル上のデータブロックを0埋めして、inode情報を削除し、ジャーナリングのトランザクションは終了すr(ry

「おっと、これは詰んだんじゃないかい?」

逆転の発想

inode が分かれば解決します。なので、その逆を行くと良いはずです。

先ず、ディレクトリエントリを全部舐めます。

この時、レコード長を無視して無理やりファイル名を取り出して、そのファイル名からレコード長を逆算してやれば不可視のディレクトリエントリを読むことが出来るはずです。

これを元にファイル名と inode の情報が取得できるので、ジャーナルログにおける時間範囲を設定して、その範囲でログを追いかければ、ファイルの実体境界を特定して、ファイルとして取り出すことが可能になる……はず?

あくまで、ディレクトリエントリが破壊され、求める時間範囲のジャーナルログが残っている、このロスタイムのような瞬間にだけ許された技……

「あっ、でもこんな面倒くさいことしたくないぞ?」

extundelete

面倒くさいことを自動でやってくれる都合が良いコマンドが存在します。それが extundelete です。

幸運にも

  • Ubuntuだった
  • ファイルシステムがext3だった
  • ターミナルを開いたままにしていた
  • apt-get を削除していなかった

という条件が重なっていたため、救済方法が存在しました。

先ず、インストールします

− + ×
$ sudo apt-get update
$ sudo apt-get install extundelete

 

Ubuntu の場合、公式リポジトリで該当のコマンドが配信されていました。 apt を rm で殺していなかったので、今回はサラッとインストールできました。

次に、restore対象のdisk名称を調べます。 df コマンドとかでいいんじゃないでしょうか。

私の環境では /dev/sda1 と分かったので、これを現時点から 10 分前まで舐めてみました。

− + ×
$ sudo extundelete \
> –after (消したと思しきunixtime) \
> –restore-all /dev/sda1

 

このコマンドを実行したディレクトリに RECOVERED_FILES フォルダが作成され、その中に起点から現時点までに削除されて、なおかつ復活できそうなファイルが出力されます。
ただし、 inode が重複したりして追跡が出来なかったようなファイルは、最新の inode で復活されるので、目的のファイルが復活されない場合もあります。
私の場合、今回は無事に .bashrc を復活できました。

最後に

ブロックとかの概念は磁気テープや FD を扱った人間であればイメージしやすいかも知れないですが、最近の若手エンジニアだと SSD が基本な人種も少なくないはずです(そもそも、磁気テープの存在や FD の存在を知らない子も居ますしね)。

それでも、ファイルシステムの低レベル制御の原理を知っていればどうにかなることも、世の中にはあります。

追記) rm の追憶


フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!

フェンリル採用チームの Twitter アカウントです。応募前のお問い合わせや、ちょっとした相談ごとなどお気軽にどうぞ!

Copyright © 2019 Fenrir Inc. All rights reserved.