一块硬盘的救赎——尝试从无法读取的机械硬盘里拯救数据

一直在我桌面电脑上工作,服役超过4年的一块西数硬盘昨天重启电脑之后突然无法被 Windows 识别了。因为我系统里大量用户数据在这个盘,结果导致我整个 explorer.exe 乃至任务管理器都无法启动,只好打开机箱把它拔掉,才得以进入 Windows。拜此事故所赐,我才意识到原来我有那么多日常使用东西是放在这块机械盘上的——此前我一直以为除了一些下载内容和一些过程产物巨大的 C++ 项目之外别无他物。然而硬盘已经坏掉,此时想这么多已经于事无补,不如想想办法把数据能救多少是多少。

⚠警告:本文包含大量个人经验和未经大规模验证的操作记录,因参考本文操作导致的任何数据损坏或丢失,作者概不负责。数据无价,请三思而后行!

首先来说一下这块盘的基本信息。这是一块 WDC WD10EZEX-08WN4A0 ,商品名就是西数蓝盘,接口是 SATA 3.1 的,尺寸是 3.5 寸。用 SMART 去看硬盘信息,通电已经 1.1 万小时了,而且不知道什么时候 C5 已经警告了,挂掉也不是什么太稀罕的事情。因为挂到我 Windows 上面,Windows 会尝试去读取它(然后失败)而导致 explorer.exe 开不起来,因此我把它拔了下来,挂到了另一台 homelab 上打算尝试导出数据。然而事与愿违,这块盘挂到我 homelab 机器上之后,连着机器上原有的盘一起失踪了,BIOS 都看不到他们俩。可能是我这主板的 SATA 口有什么毛病,总之屋漏偏逢连夜雨,我暂时性地失去了访问这块硬盘的方法。

DiskGenius 的 SMART 截图,上面显示 C5 项已经是警告状态了
用 DiskGenius 查看的这块盘的 SMART 信息

于是我在京东下单了一个 USB-SATA 数据线——说是数据线其实也不太准确,因为这玩意里面有一个 SATA 控制器——是优越者(UNITEK)的 Y-1093BBK,从 lspci 信息上看是 ASMedia 的 ASM1062 的控制器。因为我这块盘是 3.5 寸的,于是选择了有电源适配器的型号,用于给硬盘做供电。到货之后,简单地将硬盘连接到数据线上,再插入外接供电,然后把 USB 插入到 NAS 的 USB3.0 接口上。幸运地,NAS 上的 Ubuntu 正确地识别了这块硬盘而没有发生任何预料外的问题——不是和 homelab 一样直接和另一块盘一起捉迷藏,或者和桌面电脑一样让系统挂掉——总之正确地识别到了这块盘,这也让恢复数据有的放矢起来。

首先运行一个 lsblk 看看块设备:

$ lsblk -o name,label,size,fstype,model
NAME    LABEL            SIZE FSTYPE            MODEL
[... 此处省略无关硬盘 ...]
sdf                    931.5G                   EZEX-08WN4A0
├─sdf1                   128M
└─sdf2  Storage        931.4G ntfs

从 lsblk 的信息来看,这块盘的设备位于 sdf。既然识别到了,那么首先要做的事情就是把这块盘做个镜像,方便之后的数据恢复。说到镜像,我首先想到的是 dd,但这块硬盘已经有一些物理故障了, dd 未必能很好地完成任务。咨询了有很多块硬盘的 @orzfly 后,他告诉我可以使用 ddrescue 来为有问题的硬盘做镜像。

ddresuce —— 我是说,GNU ddrescue,是一个数据恢复工具。它能用各种方法跳过硬盘里的坏块,将尽量好的部分读取出来,并以此创建一个部分的镜像,从而避免进一步的数据损失。在 Ubuntu 上,gddresuce 是提供它的包,因此我首先使用 apt 以安装它:

apt install gddrescue

然后就是尝试读取这块硬盘了。首先,普通地运行一个镜像命令(对了,不要忘记在 screen 或者 tmux 里执行,否则……):

ddrescue /dev/sdf disk_recover_from_crash.img disk_recover_from_crash.log

ddrescue 的第一个参数就是我们要恢复的硬盘。安全起见,我没有使用某个分区,而是尝试将整个硬盘作为镜像源;第二个参数是输出路径,我这里输出到一个镜像文件;第三个参数是日志文件,这个日志文件会记录扇区的好坏信息,用于进一步的操作,这十分重要。

运行之后就能看到一些实时更新的统计信息,大概这样:

     ipos:  640862 MB, non-trimmed:    65536 B,  current rate:  34799 kB/s
     ipos:  640827 MB, non-trimmed:    65536 B,  current rate:  35520 kB/s
     opos:  640827 MB, non-scraped:        0 B,  average rate:  33094 kB/s
non-tried:  640293 MB,  bad-sector:        0 B,    error rate:       0 B/s
  rescued:  359911 MB,   bad areas:        0,        run time:      3h 59s
pct rescued:   35.98%, read errors:        0,  remaining time:      5h  4m
                              time since last successful read:          0s
Copying non-tried blocks... Pass 1 (backwards)

这些数据的含义在 ddrescue 的 manual 里都能查到,这里就不多赘述。我关注的主要是 “rescued” 和 “bad-sector” 这两个数据;前者决定了有多少数据已经被读取出来了,而后者是说有多少数据无法读取。如果幸运的话,等它运行完就能得到一个完整的磁盘镜像了——可惜我并非幸运的。运行 5 秒后,我的 ddrescue 直接卡住了,哪怕是 run time 也不再更新。等待了半个小时无果后,我冒着数据再次丢失的风险把 USB 拔了出来。拔出来的操作可谓是立竿见影,ddrescue 抱怨了一句 Input device is missing 之后就退出了。

当然,恢复工作还得继续。抱着玄学的想法,我把 USB 数据线换了一个接口,并且把命令改成了从后往前读。因为机械硬盘前面部分写入读取较多,前面也更容易坏,所以我觉得也许从后往前读会更稳一些。反过来的命令是这样:

ddrescue -R /dev/sdf disk_recover_from_crash.img disk_recover_from_crash.log

只是加了一个 -R 的参数而已。幸运的是,这次终于运行良好。我也不知道是移动 USB 接口起了作用还是倒序读取起了作用,总之它在正常地运行了。根据程序输出的提示,我大概需要等待8小时左右。

…a few moments later…

跑完之后,来看结果:

     ipos:  533937 kB, non-trimmed:        0 B,  current rate:       0 B/s
     opos:  533937 kB, non-scraped:        0 B,  average rate:  34502 kB/s
non-tried:        0 B,  bad-sector:     8192 B,    error rate:     128 B/s
  rescued:    1000 GB,   bad areas:        2,        run time:  8h  2m 54s
pct rescued:   99.99%, read errors:       17,  remaining time:         n/a
                              time since last successful read:         44s
Finished

我还挺幸运的,只有 8192B 的地方读不出来。换言之这盘只坏了两个扇区,就让 Windows 读不出来了……真是玄学。出于不死心,我又尝试了强行去读取这两个损坏的扇区:

ddrescue -r3 /dev/sdf disk_recover_from_crash.img disk_recover_from_crash.log

参数 -r 可以指定对坏块的重试次数,这里尝试三次。后面的两个参数保持不变,这样 ddrescue 才能读取 log 里保存的进度,以跳过已经恢复好的部分。不过这两个块可能真的坏了,我这里尝试了之后也没有任何改善。看日志:

$ cat disk_recover_from_crash.log
# Mapfile. Created by GNU ddrescue version 1.22
# Command line: ddrescue -r3 /dev/sdf disk_recover_from_crash.img disk_recover_from_crash.log
# Start time:   2021-01-09 22:40:26
# Current time: 2021-01-09 22:43:29
# Finished
# current_pos  current_status  current_pass
0x221D4E00     +               3
#      pos        size  status
0x00000000  0x1FD33000  +
0x1FD33000  0x00001000  -
0x1FD34000  0x024A0000  +
0x221D4000  0x00001000  -
0x221D5000  0xE8BEBE1000  +

可以看到两个坏掉的扇区偏移量和坏掉的尺寸。因为两个都正好是 4k,我又用 fdisk 检查了一下 sector size:

$ fdisk -l /dev/sdf
Disk /dev/sdf: 931.5 GiB, 1000204886016 bytes, 1953525168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: gpt

结果 fdisk 说这个盘的 sector size 是 512 bytes。但 ddrescue 读取的单位可能是 4k,因为有内核缓存什么的。既然知道了 sector size,还可以尝试一下 direct disc access 来看看能不能多救回来几k的数据:

ddrescue -r3 -d --sector-size=512 /dev/sdf disk_recover_from_crash.img disk_recover_from_crash.log

加了 -d 代表 direct disc access,--sector-size 则是用 fdisk 读出来的 sector size。继续执行,让 ddrescue 再多读了几次。我这里反正没有成功,这 4k 的数据大概是永远都救不回来了。

救回来之后的 disk image 可以用 Mount Partitions in Disk Image 文中介绍的方法挂载:

losetup -P /dev/loop0 disk_recover_from_crash.img
mkdir /tmp/recover
mount -t ntfs -o ro /dev/loop0p2 /tmp/recover
cd /tmp/recover

简单看了下,表面上数据还是挺正常的,至少这 4K 的数据丢了造成的影响并不是特别明显。最后参考 Disconnect/Eject SATA devices in Linux 一文卸载硬盘:

echo 1 > /sys/block/sdf/device/delete

之后拔掉 USB 和电源。至此,这块硬盘上的数据已经拯救了 99.99% 回来。因为出现过坏块,所以这块硬盘也不太可能继续服役了,屏蔽坏块让他恢复正常什么的就下次再说吧。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code