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

一直在我桌面电脑上工作,服役超过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% 回来。因为出现过坏块,所以这块硬盘也不太可能继续服役了,屏蔽坏块让他恢复正常什么的就下次再说吧。

评论

秦淮一梦lm902
秦淮一梦lm902

“对了,不要忘记在 screen 或者 tmux 里执行,否则……”
能想象到答主经历了什么惨无人道的痛苦

发表评论

发表评论代表你授权本网站存储并在必要情况下使用你输入的邮箱地址、连接本站服务器使用的 IP 地址和用户代理字符串 (User Agent) 用于发送评论回复邮件,以及将上述信息分享给 Libravatar Akismet,用于显示头像和反垃圾。