如何恢复Linux上删除的文件
文件系统故障的恢复
回想一下,在超级块中保存了有关文件系统本身的一些数据,在 ext2 文件系统中,还使用块组描述符保存了有关块组的信息;另外,索引节点位图、块位图中分别保存了索引节点和磁盘上数据块的使用情况,而文件本身的索引节点信息(即文件的元数据)则保存在索引节点表中。这些数据对于文件系统来说都是至关重要的,它们是存取文件的基础。如果超级块和块组描述符的信息一旦出错,则会造成文件系统无法正常挂载的情况出现。造成这些信息出错的原因有:
系统管理员操作失误。
设备驱动程序或第三方软件(例如mke2fs之类的)有 bug。
电源意外断电。
内核有 bug。
如果出现这种问题,可能造成的后果有:
文件系统无法挂载。
操作系统挂起。
即使文件系统能够成功挂载,在系统重启时也可能会看到一些错误,或者目录列表中出现乱字符的情况等。
下面让我们来模拟一个出现这种错误的情况。我们知道,超级块信息就保存在分区中的第一个块中,现在我们来试验一下清空这个块中数据的后果:
清单9. 清空超级块信息的后果
# dd if=/dev/zero of=/dev/sda2 bs=4096 count=1
# mount /dev/sda2 /tmp/test -t ext2
mount: wrong fs type, bad option, bad superblock on /dev/sda2,
missing codepage or helper program, or other error
In some cases useful info is found in syslog - try
dmesg | tail or so
由于无法从磁盘上读取到有效的超级块信息,mount 命令已经无法挂载 /dev/sda2 设备上的文件系统了。
为了防止这个问题会造成严重的后果,ext2 文件系统会在每个块组中保存一份超级块的拷贝。当然,这会造成一定的空间浪费,因此在最新的 ext2 文件系统中,只是在特定的块组中保存一份超级块的拷贝。具体来说,是在第 0、1 个块组和第 3、5、7 的整数次幂个块组中保存一份超级块的拷贝,而其他块组中的空间都可以节省出来了。下面来看一个 20GB 大小的文件系统的实际例子:
清单10. ext2 文件系统中超级块拷贝的位置
# dumpe2fs /dev/sdb6 | grep -i superblock
dumpe2fs 1.39 (29-May-2006)
Primary superblock at 0, Group descriptors at 1-2
Backup superblock at 32768, Group descriptors at 32769-32770
Backup superblock at 98304, Group descriptors at 98305-98306
Backup superblock at 163840, Group descriptors at 163841-163842
Backup superblock at 229376, Group descriptors at 229377-229378
Backup superblock at 294912, Group descriptors at 294913-294914
Backup superblock at 819200, Group descriptors at 819201-819202
Backup superblock at 884736, Group descriptors at 884737-884738
Backup superblock at 1605632, Group descriptors at 1605633-1605634
Backup superblock at 2654208, Group descriptors at 2654209-2654210
Backup superblock at 4096000, Group descriptors at 4096001-4096002
这是一个 20GB 大的 ext2 文件系统,每个块组的大小是 32768 个块,超级块一共有 11 个拷贝,分别存储在第 0、1、3、5、7、9、25、27、49、81 和 125 个块组中。默认情况下,内核只会使用第一个超级块中的信息来对磁盘进行操作。在出现故障的情况下,就可以使用这些超级块的备份来恢复数据了。具体说来,有两种方法:首先 mount 命令可以使用 sb 选项指定备用超级块信息来挂载文件系统:
清单11. 使用超级块拷贝挂载文件系统
# mount -o sb=131072 /dev/sda2 /tmp/test -t ext2
需要注意的是,mount 命令中块大小是以 1024 字节为单位计算的,而这个文件系统则采用的是 4096 字节为单位的块,因此 sb 的值应该是 32768*4=131072。
尽管 mount 命令可以使用备用超级块来挂载文件系统,但却无法修复主超级块的问题,这需要使用 e2fsck 这个工具来完成:
清单12. 利用 e2fsck 工具修复 ext2 文件系统中主超级块的问题
# e2fsck /dev/sda2
e2fsck 1.40.2 (12-Jul-2007)
Couldn’t find ext2 superblock, trying backup blocks…
/dev/sda2 was not cleanly unmounted, check forced.
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/sda2: ***** FILE SYSTEM WAS MODIFIED *****
/dev/sda2: 11/489600 files (9.1% non-contiguous), 17286/977956 blocks
# mount /dev/sda2 /tmp/test -t ext2
e2fsck 工具可以检查出主超级块的问题,然后从其他超级块拷贝中读取数据,并使用它来恢复主超级块中的数据(在 ext2 文件系统中,超级块信息保存在一个 ext2_super_block 的数据结构中,详细信息请参考内核源代码)。修复主超级块的问题之后,mount 命令就可以成功挂载原来的文件系统了。
重建文件系统的解决办法
在日常使用过程中,可能碰到的另外一个问题是管理员可能错误地执行了某些命令,例如使用mke2fs 重建了文件系统,从而造成数据的丢失。实际上,在 mke2fs 创建文件系统的过程中,并不会真正去清空原有文件系统中存储的文件的数据,但却会重新生成超级块、块组描述符之类的信息,并清空索引节点位图和块位图中的数据,最为关键的是,它还会清空索引节点表中的数据。因此尽管文件数据依然存储在磁盘上,但是由于索引节点中存储的文件元数据已经丢失了,要想完整地恢复原有文件,已经变得非常困难了。
然而,这个问题也并非完全无法解决。在 e2fsprogs 包中还提供了一个名为 e2image 的工具,可以用来将 ext2 文件系统中的元数据保存到一个文件中,下面是一个例子:
清单13. 使用超级块拷贝挂载文件系统
# e2image -r /dev/sda2 sda2.e2image
这会生成一个与文件系统大小相同的文件,其中包含了文件系统的元数据,包括索引节点中的间接块数据以及目录数据。另外,其中所有数据的位置均与磁盘上存储的位置完全相同,因此可以使用 debugfs、dumpe2fs 之类的工具直接查看:
清单14. 使用 debugfs 查看 e2image 映像文件的信息
# debugfs sda2.e2image.raw
debugfs 1.40.2 (12-Jul-2007)
debugfs: ls -l
2 40755 (2) 0 0 4096 31-Dec-2007 15:56 .
2 40755 (2) 0 0 4096 31-Dec-2007 15:56 ..
11 40700 (2) 0 0 16384 31-Dec-2007 15:54 lost+found
12 100644 (1) 0 0 10485760 31-Dec-2007 15:56 testfile.10M
13 100644 (1) 0 0 35840 31-Dec-2007 15:56 testfile.35K
为了节省空间,这些映像文件以稀疏文件的形式保存在磁盘上,在一个 4GB 的文件系统中,如果 55 万个索引节点中已经使用了 1 万 5 千个,使用 bizp2 压缩后的文件大概只有 3MB左右。
当然,这些映像文件中并没有包含实际文件的数据,不过文件数据依然保存在磁盘上,因此只要及时备份相关信息,在发生意外的情况下是有可能恢复大部分数据的。
磁盘坏道情况的处理
随着磁盘使用的时间越来越长,难免会出现磁盘上出现一些物理故障,比如产生物理坏道。根据物理损坏的严重程度,可能会造成文件丢失、文件系统无法加载甚至整个磁盘都无法识别的情况出现。因此要想将损失控制在最小范围内,除了经常备份数据,在发现问题的第一时间采取及时地应对措施也非常重要。
物理故障一旦出现,极有可能会有加剧趋势,因此应该在恢复数据的同时,尽量减少对磁盘的使用,dd 命令可以用来创建磁盘的完美映像。应该使用的命令如下:
清单15. 使用 dd 命令创建磁盘映像
# dd if=/dev/sdb of=/images/sdb.image bs=4096 conv=noerror,sync
noerror 参数告诉 dd 在碰到读写错(可能是由于坏道引起的)时继续向下操作,而不是停止退出。sync 参数说明对于从源设备无法正常读取的块,就使用NULL填充。默认情况下,dd 使用 512 字节作为一个块的单位来读写 I/O 设备,指定 bs 为 4096 字节可以在一定程度上加速 I/O 操作,但同时也会造成一个问题:如果所读取的这个 4096 字节为单位的数据块中某一部分出现问题,则整个 4096 字节的就全部被清空了,这样会造成数据的丢失。为了解决这种问题,我们可以使用 dd_rescue 这个工具(可以从 http://www.garloff.de/kurt/linux/ddrescue/ 上下载),其用法如下:
清单16. 使用 dd_rescue 命令创建磁盘映像
# dd_rescue /dev/sdb /images/sdb.image –b 65536 –B 512
与 dd 相比,dd_rescue 强大之处在于在碰到错误时,可以以更小的数据块为单位重新读取这段数据,从而确保能够读出尽量多的数据。上面命令中的参数指明正常操作时以 64KB 为单位读取磁盘数据,一旦出错,则以 512 字节为单位重新读取这段数据,直至整个硬盘被完整读出为止。
获得磁盘映像之后,就可以将其当作普通磁盘一样进行操作了。应用本系列文章中介绍的技术,应该能从中恢复出尽可能多的数据。当然,对于那些刚好处于坏道位置的数据,那就实在回天乏力了。
恢复文件策略
截至到现在,本系列文章中介绍的都是在删除文件或出现意外情况之后如何恢复文件,实际上,对于保证数据可用性的目的来讲,这些方法都无非是亡羊补牢而已。制定恰当地数据备份策略,并及时备份重要数据才是真正的解决之道。
不过即使有良好的数据备份策略,也难免会出现有部分数据没有备份的情况。因此,一旦出现误删文件的情况,应该立即执行相应的对策,防止文件数据被覆盖:
断开所有对文件系统的访问。fuser 命令可以用来帮助查看和杀死相关进程,详细用法请参看 fuser 的手册。
如果业务无法停顿,就将文件系统以只读方式重新加载,命令格式为:mount -r -n -o remount mountpoint
应用本系列文章介绍的技术恢复文件。
当然,在进行数据备份的同时,也需要考虑本文中介绍的一些技术本身的要求,例如 e2image映像文件、e2undel 的日志文件等,都非常重要,值得及时备份。
小结
本文介绍了一个功能非常强大的工具 e2undel,可以用来方便地恢复已删除的文件。然后讨论了文件系统故障、文件系统重建、磁盘物理损坏等情况下应该如何恢复数据。随着文件系统的不断发展,Linux 上常用的文件系统也越来越多,例如 ext3/ext4/reiserfs/jfs 等,这些文件系统上删除的文件能否成功恢复呢?有哪些工具可以用来辅助恢复文件呢?本系列后续文章将继续探讨这个问题。