Rescue a Raspberry Pi SD card with ddrescue! After 10 years, the card failed, and dd couldn’t read it. ddrescue recovered the data, and journal removal fixed the ext4 superblock issue. Finally, PiShrink reduced the image size.
相模原市で IoT 設計を受託しているファームロジックスです。
「諦めない話」が続きます。
私事ですが先日、仕事場の UPS(無停電電源)がトラブルを起こしたとき、長いこと(10年くらいでしょうか)使っていた Raspberry Pi が再起動しなくなりました。そこに特別重要なデータが入っているとは思わなかったので放置していたのですが、先日とある事情で SD カードの中身を確認したくなりました。
再起動しない理由は SD カードの故障(wear)だと予想したのですが、とりあえず Linux(Ubuntu )の dd コマンドで読み出しを試みました。
以下のようなコマンドを実行します。
$ sudo dd if=/dev/sdb of=raspberrypi_backup.img bs=4M status=progress
しかし、全然進みません。dmesg してみると、案の定 SD カードからデータが読めていません。
I/O error, dev sdb, sector 149504 op 0x0:(READ) flags 0x80700 phys_seg 16 prio class 0
やっぱりダメか。諦めかけたのですが、ChatGPT に聞いてみます。すると、ddrescue というツールがあることが分かりました。それは知らなかった! さっそく試してみます。
GNU ddrescue で読み出す
$ sudo ddrescue -d -r3 /dev/sdb raspberrypi_rescue.img rescue.log
面白いツールです。こんなふうにレスキュー作業が始まりました。
$ sudo ddrescue -d -r3 /dev/sdb raspberrypi_rescue.img rescue.log
GNU ddrescue 1.23
Press Ctrl-C to interrupt
     ipos:    2823 MB, non-trimmed:    5832 kB,  current rate:   60854 B/s
     opos:    2823 MB, non-scraped:        0 B,  average rate:    531 kB/s
non-tried:   13126 MB,  bad-sector:        0 B,    error rate:       0 B/s
  rescued:    2799 MB,   bad areas:        0,        run time:  1h 27m 47s
pct rescued:   17.56%, read errors:       89,  remaining time:      5h 52m
                              time since last successful read:          0s
Copying non-tried blocks... Pass 1 (forwards)
しかし時間がかかります。総計 2日以上は費やしたでしょうか。最終的に、99.99% 以上のブロックが救出できました。(途中で一度うっかり Ctrl-C で停めてしまい再開したので、以下の時間表示はあてになりません。)
GNU ddrescue 1.23
Press Ctrl-C to interrupt
Initial status (read from mapfile)
rescued: 15930 MB, tried: 733184 B, bad-sector: 733184 B, bad areas: 520
Current status
     ipos:   15317 MB, non-trimmed:        0 B,  current rate:       0 B/s
     opos:   15317 MB, non-scraped:        0 B,  average rate:       1 B/s
non-tried:        0 B,  bad-sector:   530944 B,    error rate:      12 B/s
  rescued:   15931 MB,   bad areas:      376,        run time:  1d  9h 41m
pct rescued:   99.99%, read errors:     2779,  remaining time:      8h 11m
                              time since last successful read:     11m  7s
Finished
なんとか希望が持てました。
fsck がスーパーブロックを見つけられない!?
次は fsck でファイルシステムをチェック、および修正します。
その前に、作業を間違えて 2日間の苦労を無駄にしないよう、まずはバックアップを作ります。
$ sudo chown yokoyama.yokoyama raspberrypi_rescue.img $ cp -p raspberrypi_rescue.img raspberrypi_rescue_backup.img
loop デバイスをセットアップします。
$ sudo losetup -Pf --find raspberrypi_rescue.img $ losetup -a
fsck をかけます。
$ sudo fsck -y /dev/loop5p2
しかし無情にもエラーです。
$ fsck from util-linux 2.37.2 e2fsck 1.46.5 (30-Dec-2021) ext2fs_open2: Bad magic number in super-block fsck.ext2: Superblock invalid, trying backup blocks... /dev/loop5p2: recovering journal fsck.ext2: unable to set superblock flags on /dev/loop5p2 /dev/loop5p2: ***** FILE SYSTEM WAS MODIFIED ***** /dev/loop5p2: ********** WARNING: Filesystem still has errors **********
どうも、スーパーブロックをちゃんと読めていないようです。予備のスーパーブロックは大丈夫でしょうか。ChatGPT にお伺いを立てたところ、mke2fs というコマンドで、バックアップスーパーブロックの位置を確認できるそうです。(-n オプションを忘れないように!)
$ sudo mke2fs -n /dev/loop5p2
Superblock backups stored on blocks: 
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, ...
得られたスーパーブロックのバックアップ位置を参考に、次のようにしてスーパーブロックを明示的に指定して fsck します。
$ sudo fsck.ext4 -b 32768 -y /dev/loop5p2 $ sudo fsck.ext4 -b 98304 -y /dev/loop5p2 (snip)
どれを指定してもやっぱりだめです。ディスクブロックの 99.99% を復旧できているのに、全てのスーパーブロックが壊れているなんてことはあるのでしょうか?  私の作業が何か間違っているのでしょうか。本当に /dev/loop5p2は ext4 なのでしょうか。
$ sudo blkid /dev/loop5p2 /dev/loop5p2: UUID="f24a4949-f4b2-4cad-a780-a138695079ec" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="0009bf4f-02"
大丈夫そうですね。次に、dumpe2fs してみます。
$ sudo dumpe2fs /dev/loop5p2 dumpe2fs 1.46.5 (30-Dec-2021) Filesystem volume name: <none> Last mounted on: / Filesystem UUID: f24a4949-f4b2-4cad-a780-a138695079ec Filesystem magic number: 0xEF53 Filesystem revision #: 1 (dynamic) Filesystem features: has_journal ext_attr resize_inode dir_index filetype extent flex_bg sparse_super large_file uninit_bg dir_nlink extra_isize Filesystem flags: signed_directory_hash Default mount options: user_xattr acl Filesystem state: clean (snip)
これも大丈夫そうです。ChatGPT に再度助けを求めます。
すると、次のような回答が得られました。
問題の可能性
- 
- スーパーブロックは読み取れるが、ジャーナル (
has_journal) に問題があるfsckで「ジャーナルの回復ができない」とエラーが出ていたので、ジャーナルが破損している可能性 が高い。- 解決策: ジャーナルを強制的に削除し、再作成する。
 
 - マウントに必要なメタデータが破損
- スーパーブロックは 
dumpe2fsで表示されるが、実際のファイルシステム構造が破損している。 - 解決策: 
fsckを ジャーナルを無効化した状態で強制実行 する。 
 - スーパーブロックは 
 
 - スーパーブロックは読み取れるが、ジャーナル (
 
ジャーナルを強制削除する
まずは、ジャーナルの削除から試してみましょう。
$ sudo tune2fs -O ^has_journal /dev/loop5p2
なにも返事がありませんが、次に進みます。
$ sudo fsck.ext4 -f -y /dev/loop5p2
おーっ。無事に fsck が走りました。たくさんのメッセージ(作業)が表示されることもなく、ファイルシステムが復旧されました。
ジャーナルを再作成します。
$ sudo tune2fs -j /dev/loop5p2
ファイルシステムは無事に直りました。
なお、FAT のほうもチェックしておきます。
$ sudo fsck.vfat -y /dev/loop5p1
ファイルシステムを mountしてみると、ちゃんとファイルにアクセスできます。おそらくファイルシステム上のデータにはエラーがある(ファイルの一部が壊れている)のでしょうか、とりあえずできることはしました。
ファイルシステムをシュリンクする
次に、このイメージファイル raspberrypi_rescue.img には無駄な領域があって大きすぎるので、ファイルシステムをシュリンクしましょう。このツールが非常に便利です。
$ sudo pishrink.sh raspberrypi_rescue.img shrunk_rescue.img
無事にシュリンクできました!
まとめ
今回の作業にあたって、ChatGPT のおかげで 2つのことを助けて貰いました。
- Raspberry Pi の SD カードが壊れて、
ddで読めなくなっていても諦めない!
→ GNU ddrescue を使おう! - fsck で ext4 のスーパーブロックが見つからない?
→ ジャーナルの破損を疑おう! 一度ジャーナルを削除してから fsck しよう。 
皆さんも、困ったときには諦めずにファイルシステムの復旧にチャレンジしてください。
