10. データブロックの復旧

ここでの手順は、復旧しようとするファイルの大きさが 12 ディスクブロック以下 であるか否かによって、非常に簡単にもなり、逆に面倒にもなり得ます。

10.1. 小さなファイル

ファイルのサイズが 12 ブロック以下なら、ファイルのデータを保持している ブロックナンバーのすべてが、検出した i-node の中に書き込まれています。 その i-node に対して stat を実行すると、 ブロックナンバーを直接読み出すことができます。 さらに debugfs には、 この処理を自動化するコマンドも組み込まれています。 前章で取り上げた例をここでも使って説明します。

  debugfs:  stat <148003>
  Inode: 148003   Type: regular    Mode:  0644   Flags: 0x0   Version: 1
  User:   503   Group:   100   Size: 6065
  File ACL: 0    Directory ACL: 0
  Links: 0   Blockcount: 12
  Fragment:  Address: 0    Number: 0    Size: 0
  ctime: 0x31a9a574 -- Mon May 27 13:52:04 1996
  atime: 0x31a21dd1 -- Tue May 21 20:47:29 1996
  mtime: 0x313bf4d7 -- Tue Mar  5 08:01:27 1996
  dtime: 0x31a9a574 -- Mon May 27 13:52:04 1996
  BLOCKS:
  594810 594811 594814 594815 594816 594817
  TOTAL: 6

上記ファイルは 6 つのブロックから成り立っています。12 ブロックという 限度以下なので、debugfs コマンドを使ってこの ファイルを別の場所、ここでは /mnt/recovered.000 に書き出すことができます。

  debugfs:  dump <148003> /mnt/recovered.000

もちろん fsgrab コマンドでも同様の処理が可能です。 fsgrab の場合は、次のようにします。

  # fsgrab -c 2 -s 594810 /dev/hda5 > /mnt/recovered.000
  # fsgrab -c 4 -s 594814 /dev/hda5 >> /mnt/recovered.000

debugfs でも fsgrab でも、 /mnt/recovered.000 の末尾に不要な情報が付加されて しまいますが、全く問題はありません。この余分な情報を取り除くには、 i-node の Size フィールドにある値を取り出し、 この値を dd コマンドの bs オプションに指定するのが最も簡単でしょう。

  # dd count=1 if=/mnt/recovered.000 of=/mnt/resized.000 bs=6065

もちろん、ファイルを構成していたディスクブロックのいくつかは、既に上書き されてしまっているかもしれません。その場合は運が悪かったとしか言えません。 そのブロックを復旧することは不可能です。(パーティションをすばやく umount できたか否かが勝負の分かれ目です。)

10.2. 大きなファイル

ファイルのデータが 12 ブロック以上ある場合は、ちょっと面倒です。さしあたり、 先ず UNIX のファイルシステムがどういう構造になっているのかを若干理解しておく 必要があります。ファイルのデータは、「ブロック」と呼ばれる単位に分割されて ディスク上に保存されています。このブロックには、頭から順に番号が付けられて います。また、ファイルは「i-node」という情報も持ち、i-node にはファイル 所有者やパーミッション、ファイルタイプ等の情報が記載されています。ブロック と同じく i-node にも順に番号が振られていますが、 この二つのシーケンスは全く別のものです。 ディレクトリエントリは、ファイル名と i-node 番号とからなります。

しかしこれだけでは、カーネルはディレクトリエントリに対応するデータを 見つけることが出来ません。それゆえ i-node には、次のように、ファイル データのブロックの位置に関する情報も記載されるようになっています。

以上をもう一度読んでください。確かにややこしいですが、非常に重要です。

現在、バージョン 2.0.36 以降のすべてのカーネルでは、ファイルを削除する際に 間接ブロック (および二重間接ブロック以上) を すべてゼロしてしまうようになっています。それゆえ、12 ブロック以上の ファイルの場合、ファイル内容の復旧どころか、必要なブロック番号を見つけ出せる 保証すらありません。

いまのところ著者がたどり着いた唯一の方法は、そのファイルのデータがフラグメント 化せずに連続して並んでいると仮定して、作業を行ってみるということです。 フラグメント化してしまっている場合は非常にやっかいです。フラグメント化 していないと仮定するなら、データブロックの並び方には (ファイルの データブロック数に対応して) 次のようなパターンが考えられます。

0 から 12 ブロック

上述したように、ブロック番号は、i-node に記載されています。

13 から 268 ブロック

直接ブロックのあとに間接ブロックがひとつあり、その後に 256 個の データブロックが並びます。

269 から 65804 ブロック

上記同様、12 個の直接ブロック、ひとつの (不要な) 間接ブロック、そして 256 個 のブロックが並びます。その後に、ひとつの (不要な) 二重間接ブロックがあり、 さらにひとつの (不要な) 間接ブロックと 256 個のデータブロックという組み合わせ が 256 組続きます。

65805 ブロック以上

冒頭の 65804 ブロックまでの並びは上記と同様です。その後に、ひとつの(不要な) 三重間接ブロックがあり、さらに 256 組の「二重間接シーケンス」が 続きます。二重間接シーケンスは、(不要な) 二重間接ブロックを先頭とし、その後に ひとつの (不要な) 間接ブロックと 256 個のデータブロックという組み合わせが 256 組続く並びによって構成されます。

もちろん、ここで仮定したデータブロックのブロック番号が正しいとしても、 その中身であるデータそのものが書き換えられていないという保証はありません。 さらに、ファイルが大きくなればなるほど、そのファイルが(特殊な環境を覗いて) なんらかのフラグメンテーションを起こさずに、ファイルシステムに書き込まれて いる可能性は小さくなります。

注意してほしいのは、この文書ではこれまでブロックサイズが標準的な 1024 バイトで あると仮定した上で説明しているということです。ブロックサイズをそれ以上に 設定している場合、上述の数字は変化します。特に、個々のブロック番号は 4 バイト 長なので、"ブロックサイズ/4" は、個々の間接ブロックにあるブロック番号の数と 一致します。それゆえ、上記の説明で 256 という数字が現れたときは、 必ず "ブロックサイズ/4" に置き換えてください。「必要なブロックの数」の境界も 変わることになるはずです。

それでは、大きなファイルの復旧例を見てみましょう。

  debugfs:  stat <1387>
  Inode: 148004   Type: regular    Mode:  0644   Flags: 0x0   Version: 1
  User:   503   Group:   100   Size: 1851347
  File ACL: 0    Directory ACL: 0
  Links: 0   Blockcount: 3616
  Fragment:  Address: 0    Number: 0    Size: 0
  ctime: 0x31a9a574 -- Mon May 27 13:52:04 1996
  atime: 0x31a21dd1 -- Tue May 21 20:47:29 1996
  mtime: 0x313bf4d7 -- Tue Mar  5 08:01:27 1996
  dtime: 0x31a9a574 -- Mon May 27 13:52:04 1996
  BLOCKS:
  8314 8315 8316 8317 8318 8319 8320 8321 8322 8323 8324 8325 8326 8583
  TOTAL: 14

上記ファイルがフラグメント化していない可能性は、そこそこあるように思われます。 i-node にリストアップされている冒頭の 12 ブロック (これらはすべてデータブロックです) は確かに連続しています。 まずこれらのブロックを復旧することから始めましょう。

  # fsgrab -c 12 -s 8314 /dev/hda5 > /mnt/recovered.001

ここで、i-node にリストアップされている次のブロック、すなわち 8326 は 間接ブロックなので無視できます。次にこの間接ブロックに続いて 256 個の データブロックが連続していると信じましょう (8327 番から 8582 番までです)。

  # fsgrab -c 256 -s 8327 /dev/hda5 >> /mnt/recovered.001

i-node に表記された最後のブロックは 8583 です。ここでもまた、ファイルの データブロックが連続しているものとみなします。上記で書き出した 最後のデータブロックは、8327 + 225 = 8582 番でした。次の 8583 ブロックは 二重間接ブロックなので無視できます。そしてこのブロックの後に、 間接ブロック (これは無視) に続く 256 のデータブロックが 256 組続くわけです。 これらをすばやく計算すると、次のコマンドを打つべきだということになります。 二重間接ブロック 8583 と (おそらく) そのすぐ後にある間接ブロック 8584 はスキップして、 データブロックである 8585 から始めている点に注意してください。

  # fsgrab -c 256 -s 8585 /dev/hda5 >> /mnt/recovered.001
  # fsgrab -c 256 -s 8842 /dev/hda5 >> /mnt/recovered.001
  # fsgrab -c 256 -s 9099 /dev/hda5 >> /mnt/recovered.001
  # fsgrab -c 256 -s 9356 /dev/hda5 >> /mnt/recovered.001
  # fsgrab -c 256 -s 9613 /dev/hda5 >> /mnt/recovered.001
  # fsgrab -c 256 -s 9870 /dev/hda5 >> /mnt/recovered.001

上記をまとめると、これまでに 12 + (7 * 256) 個のブロック、すなわち 1804 個の ブロックを書き出したことになります。i-node を stat コマンドで調べた結果は、'blockcount' で 3616 個と 表示されていました。残念ながら、この表記でのブロック長は (UNIX での伝統を 引き継いで) 512 バイトで計算されているので、実際は 1024 バイトで計算すると 3616/2 = 1808 ブロックということになります。つまり、あと 4 ブロック足りない だけだということです。最後に書き出されたブロックは 10125 番であったので、 再度これまでと同様に、間接ブロック(10126 番)をスキップして、それら最後の 4 ブロックを書き出します。

  # fsgrab -c 4 -s 10127 /dev/hda5 >> /mnt/recovered.001

以上で、運が良ければ、ファイル全体を復旧することに成功したことになります。