壊れたかもしれないハードディスクからのデータサルベージ

2013年12月9日追記:

少し冗長なので、新ブログでリライトしました。あわせてこちらもご覧ください。

http://www.xmisao.com/2013/12/02/hdd-salvage-by-dd.html

発端

今月上旬、Webブラウジング中にハードディスクが「カターン」と断末魔をあげてPCがフリーズ、それ以来そのマシンでOSが立ち上がらなくなるというトラブルに遭遇しました。

もちろんディスクトラブルを疑いましたが、結論からいうと実はマザーボードが壊れていてハードディスクが巻き添えを食った形でした。幸いハードディスクはWindowsが入っていたパーティションの一部が論理的に破壊されただけで無事、必死のサルベージ作業の甲斐もありほとんどのデータが復旧できました。

以下はLinuxを使って挙動の妖しいハードディスクからデータをサルベージする方法の備忘録です。当初はディスクが物理的に壊れているかもしれないと思って作業していましたから、もう少し重傷な場合でもそのまま適応できると思います。

方針

壊れているかもしれないディスクを不用意に動かしたくないため、とりあえずディスクイメージを作成し、あとからごにょごにょすることにしました。トラブルに見舞われたマシンそのものが信用ならないので、まず問題のディスクを取り外し正常に動くとわかっているLinuxマシン(Debian etch)に接続。そのマシンにはサルベージ用に少なくとも壊れたハードディスクより大きな容量を持つハードディスクを準備しておきます。

ディスクイメージの作成

恐る恐るマシンを起動してdmesgを眺めると壊れているかもしれないハードディスクはどうにか/dev/hdbとして認識されているようでした。

ディスクイメージはシンプルにddで作成します(※)。以下の例はディスクをMBRも含めて全て読み込んでファイルとして出力する例です。

dd if=/dev/hdb of=/root/hdd.img bs=512 obs=1024k count=488397168 conv=sync,noerror

ifは入力でofは出力です。今回は入力を/dev/hdbに、出力を/root/hdd.imgというファイルしました。

bsは読み込み単位でセクタサイズの512が最小。bsは大きくすると高速化しますが、読み込めない場合は読み込み単位すべてがダメになるのでセクタサイズ(512byte)と同一に指定するのが良いらしいです。

逆にobsは出力する単位で、こちらはバッファリングするだけですので大きな値にすると高速化できるそうです。

countは後述するnoerrorで終端が無効になって無限ループするのを防ぐために指定します。countはbs単位です。countの値はdmesgやfdisk -luなどの出力などを見てハードディスク全体の長さになるよう決めます。

convはオプションでsyncは読み込めないセクタがあってもヌルパディングしてデータの位置を保持する、noerrorはエラーがあっても無視して続けることをそれぞれ表しています。

このコマンドを実行すると途中のエラーなどの情報が標準出力(エラー出力?)に出るのでお決まりで>log 2&>1などとして標準出力/エラー出力をリダイレクトすると良いかも知れません。

ddに与えるオプション指定については次のサイトを参考にさせていただきました。

またそもそもサルベージ対象のハードディスクを認識しない場合もatacontrolを使うと認識される場合もあるようです。

(※)僕はやりませんでしたが、不安定なディスクからデータを読む場合はDMAではなくPIOを使った方が良いという情報もあります。

ディスクイメージのマウントとデータ吸い出し

ちょっと困ったのはこのマウントで、単一のパーティションのイメージをマウントする例は豊富にあったのですが、ディスク全体のイメージ中の各ファイルシステムにアクセスする方法がわかりませんでした。

色々と調べてmountやらlosetupのマニュアルを熟読したところ、offsetを指定して各ファイルシステムをマウントする方法があることが判明。ものはためしとやってみました。

offsetの調査

とりあえずoffsetを調べるためにディスクイメージをloopデバイスに丸々マウントします。まず使われていないloopデバイスを調べて…。

losetup -f

空いているloopデバイスに対して先ほどのイメージをマウント。これが成功すればloop0が/dev/hdbの中身を全部持っている状態になります。

losetup /dev/loop0 /root/hdd.img

loop0がディスクの内容を全部持っているため、fdiskを使うとパーティションテーブルに触れます。セクタ単位でパーティションの位置を見たいのでfdiskには-lではなく-luを与えます。

fdisk -lu /dev/loop0

結果は以下。パーティションの内容と開始位置がわかります。このうち僕がサルベージしたいのは1つめと4つめのNTFSパーティションです。

Disk /dev/hdb: 250.0 GB, 250059350016 bytes
255 heads, 63 sectors/track, 30401 cylinders, total 488397168 sectors
Units = sectors of 1 * 512 = 512 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/loop0p1   *          63   131973974    65986956    7  HPFS/NTFS
/dev/loop0p2       131973975   149966774     8996400   83  Linux
/dev/loop0p3       149966775   151958834      996030   82  Linux swap / Solaris
/dev/loop0p4       151958835   488392064   168216615    7  HPFS/NTFS

loopデバイスはlosetup -dでアンマウントします。

losetup -d /dev/loop0

今回はパーティションテーブルが読める状態でしたが、パーティションテーブルが破壊されている場合はgpartを使ってパーティションの開始位置を推定する方法もあるようです。

個々のパーティションのマウント

簡単にやるならmountコマンドのオプションにoffsetを指定して以下のようにします(先ほどの1つめのパーティションの場合)。

mount /root/hdd.img /mnt/ntfs -t ntfs -o ro,loop,offset=32256,nls=utf8

offsetが重要でパーティションの開始位置をバイト単位で指定します。パーティションの開始位置とはセクタサイズ512byteのハードディスクの場合、fdisk -luで出てくるStartにセクタサイズの512をかけた値になります(今回は32256=512*63)。

roは読み込み専用の指定です。loopはファイルをloopデバイスにマウントして扱う時に指定します。loop=/dev/loop0とか指定することもできますが空いているloopデバイスを勝手に使ってくれます。

またntfsパーティションをマウントする場合はnls=utf8を指定しないと日本語ファイルがLinuxから見えなくなります。utf8の指定方法にはiocharset=utf8とする方法や、-oとは別に-utfする方法がありますが、いずれも現在は推奨されずnlsを使うのが良いようです。

manに書かれている通りmountコマンドのoffset指定はlosetupのオプションです。恐らく-o loopを指定すると裏で空いているloopデバイスを探してそこへマウントした後、tオプションで渡したファイルシステムと解釈してloopデバイスをmountしてくれるのでしょう。

実際に試してみると以下の様にlosetupしてからmountしても同じ事ができます。

losetup /dev/loop0 /root/hdd.img --offset=32256
mount /dev/loop0 /mnt/ntfs -t ntfs -o ro,loop,nls=utf8

もしoffset指定があっているのにファイルシステムが云々といってマウントできない場合はファイルシステムのはじまりが壊れている可能性あります。場合によってはmountに-fを付けて強制マウントすると復旧できるかもしれません。

これでファイルシステムに触れますから、あとは生き残った内容を祈りながら眺めたり、必要に応じてcpでコピーするだけです。

感想

今回はパーティションテーブルが無事で、一部セクタが破損していただけでデータ内容もほとんどが無事という大変幸運なケースでした。万全を期してddでイメージを作成しましたが、Linuxからはディスクを直接マウントできた感じなのでddも不要だったかもしれません(ただ壊れたディスクをさわると症状が悪化する気もするのでddしたほうが心理的には大分楽だと思います)。

今回はNTFSなので為す術はありませんでしたが、もしファイルシステムext3などの場合はfsckでエラーの回復処理も可能です。正常な別のディスクにddで書き戻して、fsckで回復処理をすれば論理的な破損も修復できて、実機で触れるハードディスクを復活できるため便利そうですね。