2012年11月6日火曜日

GNU ddrescueを使って、壊れたHDDからデーターを救出。

の続きです。

前回のブログでは、初期化用シェルスクリプトの 修正版 tar2disk を利用して、新しい 1.0 TB HDD を初期化。その HDD を HDL-GS500 に組み込み、正常に起動できることを確認しました。

これで、壊れた HDL-GS500 の HDD から救出したデータを、新しい HDD に受け入れる準備が整いました。今回はデータの救出作業にとりかかります。


I/O エラーで見えなくなる HDD への対策

ここでもう一度、壊れた HDD の状態を整理しておきます。

パーティションテーブル
読み出し可能。バックアップ済み。
sda1:ブート
読み出し可能。バックアップ済み。
sda2:システム
読み出し失敗。読み出し途中でエラー発生。
sda3:スワップ
swap領域につき未確認。
sda4:拡張領域
拡張領域につき未確認。
sda5:アプリケーション等の領域
読み出し失敗。読み出し途中でエラー発生。
sda6:保存データ領域
今回の作業対象領域。

作業を進めるにあたり、この HDD に起こっている厄介な問題がありました。それは I/O エラー です。

HDD の読み込み時に、いちど I/O エラー が発生すると、それ以降は HDD が見えなくなり、その HDD にアクセスできない状態になってしまいました。

そのための対応策は PC の再起動でした。当たり前のような方法ですが、これで再び HDD にアクセスできるようになりました。言い換えると、 I/O エラー が発生する都度に、PC を再起動する必要があるということになります。

はじめは、SATA 経由で高速に処理できることを期待して、両方の HDD をデスクトップ PC の SATA に接続して作業しようと考えていました。しかし、作業途中で I/O エラー が発生して中断することが頻繁に起こり。さらに、エラーの監視や再起動操作のために、作業中は PC の前から離れることができない状態でした。

PC の再起動を何十回と繰り返しながらの作業では、まるで PC 本体の連続再起動耐久試験 のようになってしまい、このままでは PC が壊れてしまいそうに思えてきました。(このときの dd を使った救出作業※1は、結果としては無駄に終わりました。)


電源再投入の対策として、壊れた HDD を USB 変換アダプタ経由で接続する

試行錯誤を繰り返した結果、この方法に落ち着きました。 I/O エラー が発生して HDD が見えなくなったときには、この USB 変換アダプタの電源を再投入することで、再び HDD を認識できるようになります。転送速度が低下するデメリットが有りますが、これで PC 本体を再起動する必要が無くなりました。

もう一つの問題の I/O エラーの監視と USB-HDD の再起動 については、GNU ddrescue と I/O エラーを監視するシェルスクリプト※2で行うことにしました。ただし、 USB アダプタの電源再投入だけは、手動で操作する必要があります。


困った時の強い味方、GNU ddrescue

エラーで読めない、壊れた HDD からデータを救出したい! こんなときに便利なツールが GNU ddrescue です。 今回は、この GNU ddrescue を使って作業しました。

GNU ddrescue の特徴は、

  • 正常に読み込めるブロックを優先して救出処理を行います。読み込みエラーのブロックを読み飛ばし、そのときのエラーブロックをログファイルに記録していきます。
  • 1 回目の救出処理が最終ブロックまで終わると、今度は逆方向に走査を開始して、ログファイルに記録したエラー・ブロックからの救出処理に再挑戦します。以降は、救出できるデータがある間は、エラー・ブロックが無くなるまで、この処理を繰り返していきます。
  • 繰り返し回数が進む都度に、読み込むブロック・サイズをセクター単位まで小さくしていくことで、できる限りデータ救出を行う努力をします。

また、作業途中でCtrl + Cで中断しても、ログファイルがあれば再び中断したブロックから処理を再開することができます。

書式

# ddrescue オプション 入力元 出力先 ログファイル 

今回使用したオプションは

-d   キャッシュをバイパスして、直接ディスクにアクセスする。
-f   出力先へ強制的に上書きする。(出力先がファイルで無い場合に必要なようです。)
-r1  リトライ回数を1回に指定。
-v   経過を詳細に表示する。
書式のサンプル:(ログファイル名の指定はありませんので、ファイル名は任意です。)
# ddrescue -d -f -r1 -v /dev/sdb6 /dev/sda6 /mnt/usb/dd.log


作業環境は、SystemRescueCd で

knoppix でも GNU ddrescue を使うことができますがインストール作業が必要です。※3
今回は SystemRescueCd を使いました。はじめから GNU ddrescue がインストール済みなので、起動して直ぐに使うことができます。(前述の、 PC 再起動無限ループになったときに knoppix から切り替えました。)

SystemRescueCd のダウンロード
http://www.sysresccd.org/Download

SystemRescueCd の起動メニュー 7 を選択すると、グラフィカル環境で起動します。

キーマップを質問してくるので、日本語キーボードなら 22 を入力します。このとき、何も操作しないと、20 秒後にはデフォルトの us キーボードが選択されてしまいます。

グラフィカル環境が起動します。

GNU ddrescue のバージョンは 1.16 でした。


作業の準備

再初期化後の動作確認(前回に確認済み)が終わった HDL-GS500 から、新しい HDD を取り出してデスクトップ PC の SATA に接続しました。このとき間違いを避けるために、元々 PC に搭載している HDD の接続を取り外しておくと安全に作業できます。後で困らないために、元の配線がどこに繋がっていたのかは忘れないようにします。

続いて、救出するデータが入っている 壊れた HDD を、USB-SATA 変換アダプタ経由で PC に接続しました。今回使用した USB-SATA 変換アダプタは 玄人志向の USB-PSATA です。電源スイッチが付いていれば、他の機種でも使えるかもしれません。それから、 HDD には状態が区別できるように、それぞれ何か目印をつけておくといいかもしれません。


※2 I/O エラー監視用シェルスクリプト

壊れた HDD からのデータ救出中に I/O エラーが発生すると、 HDD が見えなくなってしまいます。このとき GNU ddrescue は HDD が見えなくなったことが判断できないようです。そして、読み込めないエラーブロックとして次々とログファイルに記録し続けます。このままでは残りすべてがエラーブロックになってしまい、残された読み込み可能なデータさえも、救出することができません。

このシェルスクリプトを使えば、 I/O エラー時には GNU ddrescue を停止させるので、ログファイルに無意味なエラーブロックを記録し続けることを防げます。そして、エラー発生を知らせるビープ音が鳴ったときだけ、 USB-SATA アダプタの電源を再投入操作をすれば良く、 PC 画面を常時注視しながら待機する必要もなくなります。

このシェルスクリプト ddrescue_auto.sh は、 GNU ddrescue の自動停止と自動再開を行います。

  • 自動停止
    /var/log/messagesを監視して、I/O エラーのメッセージ "end_request: I/O error" を検出すると、 GNU ddrescue にシグナルを送り処理を中断します。そして中断したことをビープ音で知らせます。
  • 自動再開
    USB-SATA アダプタの電源を再投入した後、 HDD の接続メッセージ "Attached SCSI disk" を検出すると、 GNU ddrescue を開始します。

I/O エラーと USB-HDD 監視用シェルスクリプト ddrescue_auto.sh は、あらかじめ USB メモリーに保存しておきます。

# cp ddrescue_auto.sh /mnt/usb


ddrescue_auto.sh

#!/bin/bash
#
# ddrescue auto control program
#
# source disk = /dev/sdb
# dist disk   = /dev/sda
# ddrescue logfile = /mnt/usb/dd.log
#
# source disk drive is connected by USB-SATA.
#
MESSAGE_LOG="/var/log/messages"

ERROR_MESSAGE="end_request: I/O error"
COMMAND_NAME="ddrescue"
RESTART_MESSAGE="Detected I/O ERROR! Please RESTART USB Disk drive."

ATTACH_MESSAGE="Attached SCSI disk"
EXEC_COMMAND="ddrescue -d -f -r1 -v /dev/sdb6 /dev/sda6 /mnt/usb/dd.log"

error_detection()
{
while read line1
do
 echo "$line1" | grep "$ERROR_MESSAGE" > /dev/null
 if [ $? -eq "0" ]; then
  COMMAND_PID=`pidof $COMMAND_NAME`
  if [ $? -eq "0" ]; then
   kill -INT $COMMAND_PID
   beep -l 200 -D 100 -n -l 100 -D 100 -n -l 200
  fi
  echo $RESTART_MESSAGE
 fi
done
}

attached_disk_detection()
{
while read line2
do
 echo "$line2" | grep "$ATTACH_MESSAGE" > /dev/null
 if [ $? -eq "0" ]; then
  echo "$ATTACH_MESSAGE! START $COMMAND_NAME"
  sleep 1; beep -f 2000 -l 120 -n -f 1000 -l 120
  $EXEC_COMMAND
 fi
done
}

echo "Waiting! USB Disk drive"
tail -n 1 -f $MESSAGE_LOG | attached_disk_detection &
tail -n 1 -f $MESSAGE_LOG | error_detection


SystemRescueCd で作業開始

PC の電源を入れて SystemRescueCd を起動すると、はじめから root で作業できます。

はじめに、 HDD の取り違いを起こさないために、接続した HDD のそれぞれのデバイス・ノードを、 dmesg などを使って確認しておきます。 HDD を間違えると、救出すべきデータを消してしまうことになるので注意します。

最初の状態としては

/dev/sda には、前回初期化済みの新しいHDD。
/dev/sdb には、救出するデータが入っている壊れたHDD
が接続されている状態とします。

ログファイルの保存用に、USB メモリーをマウントしておきます。

root@sysresccd /root % mkdir /mnt/usb
root@sysresccd /root % mount /dev/sdc1 /mnt/usb

GNU ddrescue の起動は、先ほど USB メモリーに保存しておいたシェルスクリプト ddrescue_auto.sh から行います。

root@sysresccd /root % cd /mnt/usb 
root@sysresccd /root % ./ddrescue_auto.sh
で起動します。

起動すると Waiting! USB Disk drive メッセージを表示して待機します。
USB-SATA アダプタの電源が入ると、 Attached SCSI disk! START ddrescue のメッセージを表示後 GNU ddrescue を実行します。端末画面には GNU ddrescue の作業状態が表示されます。

root@sysresccd /root % ./mnt/usb/ddrescue_auto.sh
Waiting! USB Diskdrive
Attached SCSI disk! START ddrescue


GNU ddrescue 1.16
About to copy 496880 MBytes from /dev/sdb6 to /dev/sda6
    Starting positions: infile = 0 B,  outfile = 0 B
    Copy block size: 128 sectors       Initial skip size: 128 sectors
Sector size: 512 Bytes

Press Ctrl-C to interrupt
Initial status (read from logfile)
rescued:   496531 MB,  errsize:    348 MB,  errors:   46721
Current status
rescued:   496535 MB,  errsize:    345 MB,  current rate:        0 B/s
   ipos:    23009 MB,   errors:   46579,    average rate:     347 kB/s
   opos:    23009 MB,     time since last successful read:       1 s
Retrying bad sectors... Retry 1

救出処理の途中で、HDD の I/O エラー "end_request: I/O error"

Oct 16 01:18:45 sysresccd kernel: [98220.549082] sd 286:0:0:0: [sdb] Unhandled sense code
Oct 16 01:18:45 sysresccd kernel: [98220.549085] sd 286:0:0:0: [sdb]  Result: hostbyte=DID_ERROR driverbyte=DRIVER_SENSE
Oct 16 01:18:45 sysresccd kernel: [98220.549088] sd 286:0:0:0: [sdb]  Sense Key : Hardware Error [current] 
Oct 16 01:18:45 sysresccd kernel: [98220.549091] sd 286:0:0:0: [sdb]  Add. Sense: No additional sense information
Oct 16 01:18:45 sysresccd kernel: [98220.549094] sd 286:0:0:0: [sdb] CDB: Read(10): 28 00 00 dd 64 1f 00 00 01 00
Oct 16 01:18:45 sysresccd kernel: [98220.549101] end_request: I/O error, dev sdb, sector 14509087
メッセージを検出すると、 GNU ddrescue を自動停止します。その時はビープ音で知らせます。
次のメッセージを表示して、 USB-SATA アダプタの電源再投入まで待機します。
Interrupted by user
Detected I/O ERROR! Please RESTART USB Disk drive.
^C

ここで、 手動操作により USB-HDD の電源を再投入 します。

USB-HDD の電源を手動操作で再投入し "Attached SCSI disk"

Oct 16 01:11:27 sysresccd kernel: [97782.514026] usb 1-1: new high-speed USB device number 30 using ehci_hcd
Oct 16 01:11:27 sysresccd kernel: [97782.629756] usb 1-1: New USB device found, idVendor=152d, idProduct=2338
Oct 16 01:11:27 sysresccd kernel: [97782.629759] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=5
Oct 16 01:11:27 sysresccd kernel: [97782.629762] usb 1-1: Product: USB to ATA/ATAPI Bridge
Oct 16 01:11:27 sysresccd kernel: [97782.629765] usb 1-1: Manufacturer: JMicron
Oct 16 01:11:27 sysresccd kernel: [97782.629767] usb 1-1: SerialNumber: XXXXXXXXXXXX
Oct 16 01:11:27 sysresccd kernel: [97782.631108] scsi286 : usb-storage 1-1:1.0
Oct 16 01:11:28 sysresccd kernel: [97783.633178] scsi 286:0:0:0: Direct-Access     ST350082 0AS                   PQ: 0 ANSI: 2 CCS
Oct 16 01:11:28 sysresccd kernel: [97783.633959] sd 286:0:0:0: Attached scsi generic sg2 type 0
Oct 16 01:11:28 sysresccd kernel: [97783.634764] sd 286:0:0:0: [sdb] 976773168 512-byte logical blocks: (500 GB/465 GiB)
Oct 16 01:11:28 sysresccd kernel: [97783.635508] sd 286:0:0:0: [sdb] Write Protect is off
Oct 16 01:11:28 sysresccd kernel: [97783.635511] sd 286:0:0:0: [sdb] Mode Sense: 00 38 00 00
Oct 16 01:11:28 sysresccd kernel: [97783.636258] sd 286:0:0:0: [sdb] Asking for cache data failed
Oct 16 01:11:28 sysresccd kernel: [97783.636260] sd 286:0:0:0: [sdb] Assuming drive cache: write through
Oct 16 01:11:28 sysresccd kernel: [97783.638513] sd 286:0:0:0: [sdb] Asking for cache data failed
Oct 16 01:11:28 sysresccd kernel: [97783.638516] sd 286:0:0:0: [sdb] Assuming drive cache: write through
Oct 16 01:11:28 sysresccd kernel: [97783.684756]  sdb: sdb1 sdb2 sdb3 sdb4 < sdb5 sdb6 >
Oct 16 01:11:28 sysresccd kernel: [97783.687637] sd 286:0:0:0: [sdb] Asking for cache data failed
Oct 16 01:11:28 sysresccd kernel: [97783.687640] sd 286:0:0:0: [sdb] Assuming drive cache: write through
Oct 16 01:11:28 sysresccd kernel: [97783.687642] sd 286:0:0:0: [sdb] Attached SCSI disk
を検出すると、検出音と共にメッセージを表示後、再び GNU ddrescue を起動します。
root@sysresccd /root % sh /mnt/usb/ddrescue_auto_exec2.sh
Waiting! USB Diskdrive
Attached SCSI disk! START ddrescue

この後は、壊れた HDD からデータを読み出せる限りの間、

  1. I/O エラー検出、 ddrescue を自動停止。
  2. ビープ音で知らせて待機。
  3. 手動操作で USB-HDD の電源を再投入する。
  4. HDD 接続を検出、 ddrescue を自動起動。
この作業を忍耐強く繰り返すことになります。


そして 1 ヶ月後、データ救出完了

sda6 のデータ救出作業を続けること約 1 ヶ月、ようやく終了しました。これで全てのデータを、無事に救出することができました。

I/O エラーの HDD から、無事にデータを救出することができたのは幸運でした。 GNU ddrescue は、 HDD のデータ救出には大変強力なツールだと実感しました。

完了時の端末画面は、このような表示になりました。

GNU ddrescue 1.16
About to copy 496880 MBytes from /dev/sdb6 to /dev/sda6
    Starting positions: infile = 0 B,  outfile = 0 B
    Copy block size: 128 sectors       Initial skip size: 128 sectors
Sector size: 512 Bytes

Press Ctrl-C to interrupt
Initial status (read from logfile)
rescued:   496880 MB,  errsize:    1024 B,  errors:       2
Current status
rescued:   496880 MB,  errsize:       0 B,  current rate:      512 B/s
   ipos:    14948 MB,   errors:       0,    average rate:      341 B/s
   opos:    14948 MB,     time since last successful read:       0 s
Finished                       
^C

この後、この HDD を HDL-GS500 に再組み込みした後で、 sda6 のデータ保存用フォルダの内容を確認しました。いくつかのファイルを試しましたが、いずれも正常に読み込めているようでした。

今回はデータ救出のために 1.0TB の HDD を 500GB として初期化していました。
次回は、救出したデータのバックアップ完了後に、前回修正した tar2disk に少し手を加えたものを使い、 HDL-GS500 を 1.0TB の HDL-GS500 として再初期化します。

づづきは

HDL-GS500 を、1.0 TB に換装して再初期化する。



※1 失敗編: dd で sda6 パーティションをコピーする

SATA 接続した sda6 のパーティションを、 dd で一括コピーします(する予定でした)。

# dd if=/dev/sdb6 of=/dev/sda6 bs=512 conv=noerror,sync
しかし、途中で I/O エラー になって中断してしまいました。

いちど I/O エラー が発生すると、 壊れた HDD が見えなくなってしまいます。この 壊れた HDD を再認識するためには、再起動しなければなりませんでした。

さらに困ったことに dd は、どのセクターまで正常にコピーできたのか? エラーで読み取れなかったセクターは? 次はどこのセクターから再開すればいいのか? の状態に陥りました。

普段は便利な dd なのですが、今回のような I/O エラー が起きる HDD での使用にはあまり向いていないようです。



※3 GNU ddrescue を knoppix で使う場合

knoppix には入っていないようなので、 aptitude でインストールしてから使います。

knoppix DVD を起動したら、端末を立ち上げてsu - root になります。

先ず初めにパッケージを更新します。

# aptitude update

続いて GNU ddrescue のインストールを行います。

# aptitude install gddrescue
以下の新規パッケージがインストールされます:
  gddrescue 
更新: 0 個、新規インストール: 1 個、削除: 0 個、保留: 1866 個。
96.9 k バイトのアーカイブを取得する必要があります。展開後に 208 k バイトのディスク領域が新たに消費されます。
取得: 1 http://ftp.jp.debian.org/debian/ testing/main gddrescue i386 1.16-1 [96.9 kB]
Fetched 96.9 kB in 0秒 (148 kB/s)
Selecting previously unselected package gddrescue.
(データベースを読み込んでいます ... 現在 432445 個のファイルとディレクトリがインストールされています。)
(.../gddrescue_1.16-1_i386.deb から) gddrescue を展開しています...
install-info のトリガを処理しています ...
man-db のトリガを処理しています ...
gddrescue (1.16-1) を設定しています ...
これでインストールが完了しました。

コマンドが起動できるか、バージョン表示で確認しておきます。

# ddrescue -V
GNU ddrescue 1.16
Copyright (C) 2012 Antonio Diaz Diaz.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.