壊れた tar.bz2 ファイルからファイルを抽出

ずーっと昔に tar.bz2 で固めたバックアップファイル。ハードディスク内に何年か放っておいて、 ときどき容量が足りなくなったときにあっちこっちに移動させたりしているうちに、なんかおかしくなってしまった。

$ ls -la ariel-home.tar.bz2
-r-xrw-r-- 1 t users 3581694796 Jan 13  2002 ariel-home.tar.bz2
$ tar jxf ariel-home.tar.bz2
bzip2: Data integrity error when decompressing.
        Input file = (stdin), output file = (stdout)

It is possible that the compressed file(s) have become corrupted.
You can use the -tvv option to test integrity of such files.

You can use the `bzip2recover' program to attempt to recover
data from undamaged sections of corrupted files.

tar: Unexpected EOF in archive
tar: Error is not recoverable: exiting now

言われたとおり、 素直に bzip2recover をかけると、ファイルの中の圧縮ブロックがひとつひとつ切り出されて rec00001ariel-home.tar.bz2, rec00002ariel-home.tar.bz2, ... と 4903 個のファイルができた。 こいつを解凍するには、まとめて bzcat に放り込めばいいはずだ。

$ bzcat rec*ariel-home.tar.bz2 | tar xvf -
bash: /bin/bzcat: Argument list too long

ありゃー。というわけでもうすこし頭を使う。xargs に -n1 することで、エラーのブロックがあっても続きを解凍し続けるはず。

$ find . -name "rec*.bz2" | sort | xargs -n1 bzcat | tar xvf -

これでなにやら解凍が始まった。しかし、エラーのブロックのあとには、

tar: Skipping to next header
tar: Archive contains obsolescent base-64 headers

と表示され、その後はうんともすんともいわないまま終わってしまう。おかしいなぁ。途中が抜けたら残りが解凍できないようではテープアーカイブの意味がないじゃん。

どうも、tar はデフォルトだと512バイトのブロック単位で情報を格納するらしく、それがずれると解凍できなくなるらしい。 厄介なことですこと。

しょうがないのでダンプして解析。

$ bzcat rec00001ariel-home.tar.bz2 | hexcat | head -20
00000000 - 68 6f 6d 65  2f 00 00 00  00 00 00 00  00 00 00 00  home/...........
00000010 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000020 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000030 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000040 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000050 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000060 - 00 00 00 00  30 30 34 32  37 37 35 00  30 30 30 30  ....0042775.0000
00000070 - 30 30 30 00  30 30 30 30  30 36 32 00  30 30 30 30  000.0000062.0000
00000080 - 30 30 30 30  30 30 30 00  30 37 33 33  32 33 37 31  0000000.07332371
00000090 - 31 31 30 00  30 31 30 36  34 31 00 20  35 00 00 00  110.010641. 5...
000000a0 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000b0 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000c0 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000d0 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000e0 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000f0 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000100 - 00 75 73 74  61 72 20 20  00 72 6f 6f  74 00 00 00  .ustar  .root...
00000110 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000120 - 00 00 00 00  00 00 00 00  00 73 74 61  66 66 00 00  .........staff..
00000130 - 00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................

ヘッダそのものは 0 バイト目からのファイル名からはじまっている様子。 少なくとも GNU tar だと、0x101 バイトからの ustar というのがヘッダの目印になるらしい。

とりあえず壊れているブロックと、その前まで (正しく解凍できる分) の rec*.bz2 は削除して、 残りを解凍したなかからヘッダを探してみる。

$ find . -name "rec*.bz2" | sort | xargs -n1 bzcat | hexcat | grep ustar
00046f50 - 00 00 00 75  73 74 61 72  20 20 00 74  00 00 00 00  ...ustar  .t....
00108f49 - 00 00 00 00  00 00 00 00  00 00 75 73  74 61 72 20  ..........ustar
001c474d - 00 00 00 00  00 00 75 73  74 61 72 20  20 00 74 00  ......ustar  .t.
...
$ find . -name "rec*.bz2" | sort | xargs -n1 bzcat | hexcat | grep home/t
00046e50 - 00 00 68 6f  6d 65 2f 74  2f 44 43 49  4d 2f 32 30  ..home/t/DCIM/20
00108e49 - 00 00 00 00  00 00 00 00  00 68 6f 6d  65 2f 74 2f  .........home/t/
001c464d - 00 00 00 00  00 68 6f 6d  65 2f 74 2f  44 43 49 4d  .....home/t/DCIM
...

こんな探し方だと、ヘキサダンプの改行をまたぐと発見できないので、ちゃんとバイナリエディタを使いましょう。 それと、tar の中に tar が入っていたりとか、極悪な可能性はいろいろあるが、全部無視。 ヘッダの場所から tar に食わせてやる。

$ printf "%d\n" 0x46e52
290386
$ find . -name "rec*.bz2" | sort | xargs -n1 bzcat | dd bs=1 skip=290386 | tar xvf -
home/t/DCIM/20010402/DSCF1152.JPG
home/t/DCIM/20010402/DSCF1153.JPG
...

なにやら解凍しはじめた様子。しかし遅い。dd のブロックサイズが1などという設定が悪い。 別にtar 的にはブロック単位にそろっていればヘッダー前のごみを全部除去する必要もないので、ブロックサイズをでかくしてみる。

$ printf "%d\n" 0x452
1106
$ find . -name "rec*.bz2" | sort | xargs -n1 bzcat | dd bs=1106 skip=1 | tar xvf -
tar: This does not look like a tar archive
tar: Skipping to next header
tar: Archive contains obsolescent base-64 headers
home/t/DCIM/20010402/DSCF1152.JPG
home/t/DCIM/20010402/DSCF1153.JPG
...

これでなんとか解凍できそう。ほかにもいくつか壊れたブロックがあったが、同じプロセスの繰り返しで回避成功。 壊れた部分のファイルは、たまたま別のところにも保存してあったので、問題なし。よかったよかった。

と、いいたいところだが、このバックアップの中に探していたファイルはなかったのであった。 あーあ、なんだかなぁ・・・。やっぱり13日の金曜日なのか・・・。