GNU/Linux 如何拯救损坏的 USB 驱动器
我朋友的兄弟有一个 512MB 的 Lexar Media Jumpdrive Pro USB 驱动器,在 Windows 2000 中使用后损坏了。他的 IT 部门能够恢复部分但不是全部的文件内容,但没有任何文件名。他自己尝试了一些恢复工具,但都失败了。然而,使用典型的 Linux 发行版——在本例中是 SuSE 8.0——不难从驱动器中恢复几乎所有数据以及文件名,并将内容刻录到 CD-ROM 中。
以下是我听说的关于数据丢失的情况
Date: Sun, 1 Aug 2004 17:06:03 -0700 Subject: USB ... My USB drive is a Lexar Media USB Jumpdrive Pro 2.0 (512 MB). I was working on it in a computer with Windows 2000 and logged off before ejecting the drive. Next time when I tried to use it, it showed up as a Removable drive rather than the usual Lexar Media drive and when I tried to open it, it said the drive was not formatted; and under Properties, 0 bytes free and used space and file system "RAW" According to Lexar tech support, there is a bug with Windows 2000 (that MS never bothered to fix) and can corrupt the drive when it is removed without proper eject. They recommend EasyRecovery Pro for data recovery which did allow me to recover some files (> 500) using their RAW data recovery program (all other tool failed because usually said "no recognizable file on disc"). Unfortunately, all the file names are lost and some files are gone.
最大的问题是“Linux 能读取驱动器吗?” 在网上搜索“linux usb jumpdrive pro”让我看到了希望,我的内核版本 2.4.18(在 SuSE 8.0 上)能够识别这个驱动器。所以,以 root 用户身份,我输入了
# tail -f /var/log/messages
并将驱动器插入 USB 接口。以下是显示的内容;我从每行中删除了“Aug 5 01:32:15 linux kernel:”)
usb.c: registered new driver usb-storage scsi0 : SCSI emulation for USB Mass Storage devices usb-uhci.c: interrupt, status 3, frame# 1313 Vendor: LEXAR Model: JUMPDRIVE PRO Rev: 0 Type: Direct-Access ANSI SCSI revision: 02 Attached scsi removable disk sda at scsi0, channel 0, id 0, lun 0 SCSI device sda: 1001952 512-byte hdwr sectors (513 MB) sda: Write Protect is off sda: sda1 WARNING: USB Mass Storage data integrity not assured USB Mass Storage device found at 4 USB Mass Storage support registered.
受到该报告的鼓舞,我尝试了
# dd if=/dev/sda of=/tmp/r1 bs=512
它报告已传输 1,001,952 个块。然后我拔下驱动器,并使用存储在 /dev/sda 中的镜像完成了剩下的工作。
主引导记录是整个驱动器的引导扇区和第一个扇区,它有一个分区表,以及其他有趣的东西
# od -Ax -tx1 /tmp/r1 | less ... * 0001b0 00 00 00 00 00 00 00 00 48 04 07 c9 00 00 80 01 0001c0 01 00 06 0f ff e0 3f 00 00 00 b1 45 0f 00 00 00 0001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * 0001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa
引导扇区有一个看起来合理的分区表,其中包含一个条目。它从偏移量 0x1be 开始,两个字节是 80 01。您可以使用您最喜欢的搜索引擎查找有关分区表的其他信息,但我在这里注意到两件事。首先,该条目具有 LBA32 格式——起始逻辑扇区 0x3f,长度 0xf45b1。现在,0xf45b1 是十进制的 1000881。加上 63 (0x3f) 是 1000944。1001952 和 1000944 之间的差值是 1008,即 63*16。我猜这与柱面边界有关。第二个值得注意的地方是 0x1c2 处的字节,值为 06;这是分区类型。06 是什么意思?
输入fdisk /dev/hda作为 root 用户并给出命令l进行列表,显示类型 6 是
0 Empty 1c Hidden Win95 FA 65 Novell Netware bb Boot Wizard hid 1 FAT12 1e Hidden Win95 FA 70 DiskSecure Mult c1 DRDOS/sec (FAT- 2 XENIX root 24 NEC DOS 75 PC/IX c4 DRDOS/sec (FAT- 3 XENIX usr 39 Plan 9 80 Old Minix c6 DRDOS/sec (FAT- 4 FAT16 <32M 3c PartitionMagic 81 Minix / old Lin c7 Syrinx 5 Extended 40 Venix 80286 82 Linux swap da Non-FS data 6 FAT16 41 PPC PReP Boot 83 Linux db CP/M / CTOS / . ...
所以,它是 FAT16。
现在,如果我仔细观察,我应该从这行知道sda: sda1/var/log/messages 中的信息表明分区表是正常的,并且只包含一个条目。
但是,当我实际开始查看时,我真的不确定这是 FAT16 还是 FAT12。驱动器的 512MB 容量表明它可能是 FAT16 或 FAT32。我还有一种印象,即分区可能在同一分区类型中包含 FAT32 文件系统。当我继续查看文件系统时,我注意到了这一点
# od -Ax -w8 -tx1 -tc /tmp/r1 | less 045400 4c 45 58 41 52 20 4d 45 L E X A R M E 045408 44 49 41 28 00 00 00 00 D I A ( \0 \0 \0 \0 045410 00 00 00 00 00 00 4b 5a \0 \0 \0 \0 \0 \0 K Z 045418 33 2b 00 00 00 00 00 00 3 + \0 \0 \0 \0 \0 \0 045420 41 52 00 53 00 54 00 55 A R \0 S \0 T \0 045428 00 4c 00 0f 00 9a 6f 00 \0 L \0 017 \0 232 o \0 045430 67 00 2e 00 78 00 6c 00 g \0 . \0 x \0 l \0 045438 73 00 00 00 00 00 ff ff s \0 \0 \0 \0 \0 045440 52 4e 41 4c 4f 47 7e 31 R S T L O G ~ 1 045448 58 4c 53 20 00 b8 03 61 X L S \0 003 a 045450 50 30 e4 30 00 00 ca 74 P 0 0 \0 \0 t 045458 4b 30 f2 6a 00 3e 00 00 K 0 j \0 > \0 \0 ...
顺便说一句,我最近痛苦地发现CMD | less如果 CMD 的输出太长,它不会执行您想要的操作。在这种情况下,使用它还可以,但并非总是如此;这可能取决于系统。如果您的硬盘驱动器上有足够的空间,那么执行以下操作可能会有所帮助
# od -Ax -w8 -tx1 -tc /tmp/r1 > /tmp/r2; less r2
或
# hexdump -C /tmp/r1 > /tmp/r2; less r2
所以这看起来像是一个目录的开始。然而,在那个区域的正上方,我看到了这个
042420 00 00 00 00 00 00 14 dd 15 dd 16 dd 17 dd 18 dd 042430 19 dd 1a dd 1b dd 1c dd 1d dd 1e dd 1f dd 20 dd 042440 21 dd 22 dd 23 dd 24 dd 25 dd 26 dd 27 dd 28 dd 042450 29 dd 2a dd 2b dd 2c dd 2d dd 2e dd 2f dd 30 dd 042460 31 dd 32 dd 33 dd 34 dd 35 dd 36 dd ff ff 00 00 042470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 *
这看起来像是一个带有 16 位条目的分配链。如果这些条目采用以下形式31 dd 00 00 32 dd 00 00而不是31 dd 32 dd,我可能会认为我正在查看 FAT32。
我听说通常可以找到两个 FAT 紧挨在一起,一个紧随另一个。我告诉 less(1) 查找另一行类似于 0x42460 行的行,方法是输入?31 dd 32 dd 33 dd。作为回应,less(1) 向我展示了这个
023a20 00 00 00 00 00 00 14 dd 15 dd 16 dd 17 dd 18 dd 023a30 19 dd 1a dd 1b dd 1c dd 1d dd 1e dd 1f dd 20 dd 023a40 21 dd 22 dd 23 dd 24 dd 25 dd 26 dd 27 dd 28 dd 023a50 29 dd 2a dd 2b dd 2c dd 2d dd 2e dd 2f dd 30 dd 023a60 31 dd 32 dd 33 dd 34 dd 35 dd 36 dd ff ff 00 00 023a70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * 026a00 f8 ff ff ff 03 00 e6 02 21 03 a0 03 15 03 91 03 026a10 ff ff 0a 00 0b 00 ff ff 0d 00 0e 00 0f 00 10 00
0x42460 和 0x23a60 处的数据是相同的;这告诉我表之间的偏移量是
0x42460 - 0x23a60 = 0x1ea00
因为 0x26a00 是 FAT#2 的起始位置。因此,FAT#1 的起始位置应该在
0x26a00 - 0x1ea00 = 0x08000
但是当我查看那里时,我看到的却是这个
007c00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff * 012400 01 52 02 52 03 52 04 52 05 52 06 52 07 52 08 52 012410 09 52 0a 52 0b 52 0c 52 0d 52 0e 52 0f 52 10 52 012420 11 52 12 52 13 52 14 52 15 52 16 52 17 52 18 52
有人写入了一大堆 0xff 字节。我猜这是损坏的一部分。
此时,0x12400 看起来没问题,但真的是这样吗?FAT#2 中相应位置的内容是什么?
0x12400 + 0x1ea00 = 0x30e00 030e00 01 52 02 52 03 52 04 52 05 52 06 52 07 52 08 52 030e10 09 52 0a 52 0b 52 0c 52 0d 52 0e 52 0f 52 10 52 030e20 11 52 12 52 13 52 14 52 15 52 16 52 17 52 18 52
幸运的是,这个看起来也不错。事实上,FAT#2 可能是完全正常的,即使 FAT#1 的前 40KB 左右已被损坏。
所有这些都很有趣,但此练习的重点是修复文件系统并读取数据。因此,我现在求助于我的朋友 fsck 进行修复工作,特别是 fsck.msdos、err 和 dosfsck(8)。我使用了文件系统镜像,并使用备用循环设备完成了需要做的事情
# losetup /dev/loop2 /tmp/r1 # fsck.msdos /dev/loop2
但根据 fsck.msdos(8) 的说法,“磁盘”声称有大约 165 个 FAT,而 fsck.msdos 仅支持两个。显然,一些文件系统参数被严重搞砸了。
我开始查看 mkfs.msdos(也称为 mkdosfs(8))的源代码,但随后想出了一个更好的主意。如果我可以创建一个文件系统,使其 FAT 参数的排列方式使得这个新文件系统中的 FAT 和目录与我已经拥有的磁盘镜像中的 FAT 和目录位于相同的位置,那会怎么样?读取 LEXAR MEDIA 的字节可能是卷名。也许,通过为 mkfs.msdos(8) 提供正确的参数,我可以创建一个文件系统镜像,其中 0x08000 指向第一个 FAT,0x26a00 指向第二个 FAT,而 0x45400 指向卷标。
在 mkdosfs(8) 的手册页上,我找到了
SYNOPSIS mkdosfs [ -A ] [ -b sector-of-backup ] [ -c ] [ -l file name ] [ -C ] [ -f number-of-FATs ] [ -F FAT-size ] [ -i volume-id ] [ -I ] [ -m message-file ] [ -n volume-name ] [ -r root-dir-entries ] [ -R number-of-reserved-sectors ] [ -s sectors-per-cluster ] [ -S logical-sector-size ] [ -v ] device [ block-count ]
因此,我指定了-f 2用于两个 FAT,以及-n mkfs__msdos——也就是说,一个我可以轻松找到的字符串——用于卷名。这样我就可以知道卷名落在哪里。
其他参数呢?我在上面看到 FAT 之间的距离为 0x1ea00 字节;如果它们之间的距离不正确,我可以调整 -F,也许还可以调整 -s。我在网上找到,对于这种大小的文件系统,簇将为 8192 字节;换句话说,每个簇将有 16 个 512 字节的扇区。簇是由 FAT 描述的文件分配单元。因此,它将是-s 16.
至于在哪里创建文件系统,将其放在 USB 驱动器上是不行的。相反,我创建了一个与驱动器镜像大小相同但填充了零的文件
# dd if=/dev/zero of=/tmp/r2x bs=512 count=1001952
创建文件系统后,我打算挂载它并创建一个文件。该文件将包含足够的数据,以便我们可以看到合理的分配链。为了完成此操作,我编写了一个脚本,并准备使用参数调用它,直到我碰巧找到我想要的一切。我称之为 b.sh
#!/bin/bash # parameters added to mkfs.msdos.... ARGS="$*" if mount | grep /tmp/r2d; then umount /tmp/r2d; fi losetup -d /dev/loop2 losetup /dev/loop2 /tmp/r2x mkfs.msdos -n mkfs__msdos -s 16 $ARGS /dev/loop2 mount -t vfat /dev/loop2 /tmp/r2d yes hello | dd bs=8192 count=3 of=/tmp/r2d/foo.txt umount /tmp/r2d
我的计划是尝试使用不同的参数运行此脚本,直到我得到正确的结果。0x8000 是 32KB。在 512 字节的扇区中,那是 64。因为第一个 FAT 从 0x8000 开始,所以我决定尝试-R 64,像这样
# sh b.sh -R 64 mkfs.msdos 2.8 (28 Feb 2001) Loop device does not match a floppy size, using default hd params 2+1 records in 2+1 records out #
令人惊讶的是,我的第一个猜测竟然是正确的,至少在 FAT 放置方面是正确的
# hexdump -C /tmp/r2x | less ... 00008000 f8 ff ff ff 03 00 04 00 f8 ff 00 00 00 00 00 00 |..........| 00008010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00026a00 f8 ff ff ff 03 00 04 00 f8 ff 00 00 00 00 00 00 |..........| 00026a10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00045400 6d 6b 66 73 5f 5f 6d 73 64 6f 73 08 00 00 71 89 |mkfs__msdos...q.| 00045410 0f 31 0f 31 00 00 71 89 0f 31 00 00 00 00 00 00 |.1.1..q..1......| 00045420 41 66 00 6f 00 6f 00 2e 00 74 00 0f 00 65 78 00 |Af.o.o...t...ex.| 00045430 74 00 00 00 ff ff ff ff ff ff 00 00 ff ff ff ff |t.....| 00045440 46 4f 4f 20 20 20 20 20 54 58 54 20 00 00 71 89 |FOO TXT ..q.| 00045450 0f 31 0f 31 00 00 71 89 0f 31 02 00 00 50 00 00 |.1.1..q..1...P..| 00045460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00049400 68 65 6c 6c 6f 0a 68 65 6c 6c 6f 0a 68 65 6c 6c |hello.hello.hell| 00049410 6f 0a 68 65 6c 6c 6f 0a 68 65 6c 6c 6f 0a 68 65 |o.hello.hello.he| ...
我没有检查目录大小,但显然它也没问题——稍后会详细介绍。
我现在有了一个引导扇区,它会告诉 fsck.msdos 期望 FAT 和根目录位于所有正确的位置。那么,如果我创建一个文件系统镜像,其中第一个扇区是那个扇区,但其余扇区都包含来自 USB 驱动器的数据,那会怎么样?然后,fsck.msdos 将读取引导扇区;我会告诉它使用 FAT#2 来修复所有内容;我们会看到结果如何。
总结一下究竟是什么修复了 USB 设备
步骤 1:创建一个大小正确的文件系统镜像,其中 FAT 和目录位于正确的位置
# dd if=/dev/zero of=/tmp/r2x bs=512 count=1001952 # losetup /dev/loop2 /tmp/r2x # mkfs.msdos -n mkfs__msdos -s 16 -R 64 /dev/loop2
步骤 2:将字节从损坏的镜像复制到步骤 1 中创建的文件系统镜像上,但引导扇区除外
# dd if=r1 of=r2x bs=512 skip=1 seek=1
步骤 3:对该镜像执行文件系统修复
# fsck.msdos -f -r /dev/loop2
因为我知道 FAT1 是伪造的,所以我告诉它使用 FAT2,它报告成功。它问我是否要写入更改,我说“是”。
/tmp/r2x 和 /dev/loop2 中的文件系统镜像现在是一致的。最终的测试是尝试挂载文件系统
# mkdir /tmp/r2d # mount -t vfat /dev/loop2 /tmp/r2d # ls -lRA /tmp/r2d
之后,各种好东西都出现了。
注意:一个好的结果是ls -lR表明我在另一方面也很幸运:我不知道引导扇区是否具有根目录大小的正确值,即 mkfs.msdos 的 -r 参数。我只是使用了默认值,结果很好。
此时,我决定最好刻录一张 CD。我一直在 Linux 上刻录和读取 CD,但我很少刻录供 Windows 读取的 CD。我再次进行了网络搜索,IBM DeveloperWorks 网站上的一个页面出现了。我搜索了“linux burn CD windows”或类似的内容。所以我尝试了
# mkisofs -J -r -v /tmp/r2d | \ cdrecord -v -pad -eject fs=4m speed=4 dev=0,0,0 -
我不能 100% 确定 Windows 会喜欢这张 CD,但幸运的是,我在 Win4Lin 下安装了 Windows 95。它对我来说唯一的目的是运行 Quicken 和 TurboTax,但我启动了它,并将 Windows 资源管理器指向刚刚刻录的 CD-ROM。资源管理器很喜欢它。我使用 gimp(1) 捕获了一个屏幕截图,并将图像通过电子邮件发送给了我朋友的兄弟——他欣喜若狂。
Shell 爱好者无需阅读此部分。
1 #!/bin/bash 2 # parameters added to mkfs.msdos.... 3 ARGS="$*" 4 if mount | grep /tmp/r2d; then umount /tmp/r2d; fi 5 losetup -d /dev/loop2 6 losetup /dev/loop2 /tmp/r2x 7 mkfs.msdos -n mkfs__msdos -s 16 $ARGS /dev/loop2 8 mount -t vfat /dev/loop2 /tmp/r2d 9 yes hello | dd bs=8192 count=3 of=/tmp/r2d/foo.txt 10 umount /tmp/r2d
第 1 行向 exec(2) 表明这应该由 shell 运行。我已经习惯了 bash,即 Bourne again shell。
第 2 行简单地解释了第 3 行,您在b.sh之后键入的参数是要添加到 mkfs.msdos 命令行中的参数。
第 4-6 行将 /dev/loop2 建立为块设备,其内容位于 /dev/r2x 中保存的文件系统镜像中。第 4 行卸载了人工文件系统(如果已挂载);这样做是因为我们即将对其进行一些更改。第 5-6 行确保 /dev/loop2 连接到 /tmp/r2x 并且仅连接到 /tmp/r2x。
第 7 行使用用户给出的任何其他参数创建人工文件系统镜像——还记得第 3 行中的 $ARGS 吗?
第 8 行将文件系统挂载到 /tmp/r2d。第 9 行创建一个大约 24KB(三个簇)的文件,所以我有一个文件名可以在目录的开头查找。
第 10 行然后卸载人工文件系统镜像,以便内核不会认为如果我操作 /tmp/r2x 会出现不一致。