如何恢复 Linux 上删除的文件–自动恢复工具 e2undel 和特殊情况的恢复

恢复系统中删除的文件是一个非常繁琐的过程,而 e2undel 这个工具可以用来方便地恢复文件系统中已删除的文件。本文将首先讨论 e2undel 的工作原理和用法,并对之进行一些改进。然后讨论了文件系统故障、文件系统重建、磁盘物理损坏等情况下应该如何恢复数据。

在本系列文章的前两部分中,我们介绍了 ext2 文件系统中各种文件在磁盘上的存储结构,以及如何利用 debugfs 工具的辅助,手工恢复这些文件的详细过程。

通过这两部分的学习,我们可以看出恢复系统中删除的文件是一个非常繁琐的过程,需要非常仔细地考虑各种情况,并且要保持足够的细心,才可能把数据准确无误地恢复出来。稍有差错,就会造成数据丢失的情况。聪明的读者肯定会想,如果有一些好工具来自动或辅助完成数据的恢复过程,那简直就太好了。

幸运的是,已经有人开发了这样一些工具,来简化用户的数据恢复工作,e2undel 就是其中功能最为强大的一个。

自动恢复工具 e2undel

回想一下,在 ext2 文件系统中删除一个文件时,该文件本身的数据并没有被真正删除,实际执行的操作如下:

  1. 在块位图中将该文件所占用的数据块标识为可用状态。
  2. 在索引节点位图中将该文件所占用的索引节点标识为可用状态。
  3. 将该文件索引节点中的硬链接数目设置为 0。
  4. 将该文件索引节点中的删除时间设置为当前时间。
  5. 将父目录项中该文件对应项中的索引节点号设置为 0,并扩展前一项,使其包含该项所占用的空间。

而索引节点中的一些关键信息(或称为元数据,包括文件属主、访问权限、文件大小、该文件所占用的数据块等)都并没有发生任何变化。因此只要知道了索引节点号,就完全可以用本系列文章介绍的技术将文件完整地从磁盘上恢复出来了,这正是 e2undel 之类的工具赖以生存的基础。

然而,由于所删除的文件在目录项中对应的项中的索引节点号被清空了,因此我们就无法从索引节点中获得文件名的信息了。不过,由于文件大小、属主和删除时间信息依然能反映文件的原始信息,因此我们可以通过这些信息来帮助判断所删除的文件是哪个。

e2undel 是由Oliver Diedrich 开发的一个用来恢复 ext2 文件系统中已删除文件的工具,它会遍历所检测的文件系统的索引节点表,从中找出所有被标记为删除的索引节点,并按照属主和删除时间列出这些文件。另外,e2undel 还提供了文件大小信息,并试图按照 file 命令的方式来确定文件类型。如果您使用 rm –rf * 之类的命令一次删除了很多文件,这种信息就可以用来非常方便地帮助确定希望恢复的是哪些文件。在选择要恢复的文件之后,e2undel 会从磁盘上读取该文件占用的数据块(这些数据块的信息全部保存在索引节点中),并将其写入到一个新文件中。下面我们来看一下 e2undel 这个工具的详细用法。

首先请从 e2undel 的主页(http://e2undel.sourceforge.net/)上下载最新的源码包(截止到撰写本文为止,最新的版本是 0.82),并将其保存到本地文件系统中。不过这个源码包在最新的 Fedora Core 8 上编译时可能会有些问题,这是由于 ext2 文件系统内部实现中一些数据结构的变化引起来的,读者可以下载本文“下载”部分给出的补丁来修正这个问题(请下载这个补丁文件 e2undel-0.82.patch,并将其保存到与源码包相同的目录中)。要想编译 e2undel,系统中还必须安装 e2fsprogs 和 e2fsprogs-devel 这两个包,其中有编译 e2undel 所需要的一些头文件。Fedora Core 8 中自带的这两个包的版本号是 1.39-7:
清单1. 确认系统中已经安装了 e2fsprogs 和 e2fsprogs-devel

                
# rpm -qa | grep e2fsprogs
e2fsprogs-libs-1.39-7
e2fsprogs-1.39-7
e2fsprogs-devel-1.39-7

现在就可以开始编译 e2undel 了:

 
清单2. 编译 e2undel

                
# tar -zxf e2undel-0.82.tgz      
# patch -p0 < e2undel-0.82.patch 
patching file e2undel-0.82/Makefile
patching file e2undel-0.82/e2undel.h
patching file e2undel-0.82.orig/libundel.c
# cd e2undel-0.82
# make all

编译之后会生成一个名为 e2undel 的可执行文件,其用法如下:

 
清单3. e2undel 的用法

                
# ./e2undel 
./e2undel 0.82
usage: ./e2undel -d device -s path [-a] [-t]
usage: ./e2undel -l
'-d': file system where to look for deleted files
'-s': directory where to save undeleted files
'-a': work on all files, not only on those listed in undel log file
'-t': try to determine type of deleted files w/o names, works only with '-a'
'-l': just give a list of valid files in undel log file

e2undel 实际上并没有像前面介绍的使用 e2fsck 那样的方法一样真正将已经删除的文件恢复到原来的文件系统中,因为它并不会修改磁盘上 ext2 使用的内部数据结构(例如索引节点、块位图和索引节点位图)。相反,它仅仅是将所删除文件的数据恢复出来并将这些数据保存到一个新文件中。因此,-s 参数指定是保存恢复出来的文件的目录,最好是在另外一个文件系统上,否则可能会覆盖磁盘上的原有数据。如果指定了 -t 参数,e2undel 会试图读取文件的前 1KB 数据,并试图从中确定该文件的类型,其原理与系统中的 file 命令非常类似,这些信息可以帮助判断正在恢复的是什么文件。

 

下面让我们来看一个使用 e2undel 恢复文件系统的实例。
清单4. 使用 e2undel 恢复文件的实例

                
# ./e2undel -a -t -d /dev/sda2 -s /tmp/recover/
./e2undel 0.82
Trying to recover files on /dev/sda2, saving them on /tmp/recover/

 

/dev/sda2 opened for read-only access
/dev/sda2 was not cleanly unmounted.
Do you want wo continue (y/n)? y
489600 inodes (489583 free)
977956 blocks of 4096 bytes (941677 free)
last mounted on Fri Dec 28 16:21:50 2007

 

reading log file: opening log file: No such file or directory
no entries for /dev/sda2 in log file
searching for deleted inodes on /dev/sda2:
|==================================================|
489600 inodes scanned, 26 deleted files found

 

   user name | 1 <12 h | 2 <48 h | 3  <7 d | 4 <30 d | 5  <1 y | 6 older
-------------+---------+---------+---------+---------+---------+--------
        root |       0 |       0 |       0 |       2 |       0 |       0
       phost |      24 |       0 |       0 |       0 |       0 |       0
Select user name from table or press enter to exit: root
Select time interval (1 to 6) or press enter to exit: 4

 

  inode     size  deleted at        name
-----------------------------------------------------------
     13    35840  Dec 19 17:43 2007 * ASCII text
     14 10485760  Dec 19 17:43 2007 * ASCII text
Select an inode listed above or press enter to go back: 13  
35840 bytes written to /tmp/recover//inode-13-ASCII_text
Select an inode listed above or press enter to go back: 

 

   user name | 1 <12 h | 2 <48 h | 3  <7 d | 4 <30 d | 5  <1 y | 6 older
-------------+---------+---------+---------+---------+---------+--------
        root |       0 |       0 |       0 |       2 |       0 |       0
       phost |      24 |       0 |       0 |       0 |       0 |       0
Select user name from table or press enter to exit: 
#

e2undel 是一个交互式的命令,命令执行过程中需要输入的内容已经使用黑体表示出来了。从输出结果中可以看出,e2undel 一共在这个文件系统中找到了 26 个文件,其中 root 用户删除的有两个。这些文件按照删除时间的先后顺序被划分到几类中。索引节点号 13 对应的是一个 ASCII 正文的文本文件,最终被恢复到 /tmp/recover//inode-13-ASCII_text 文件中。查看一下这个文件就会发现,正是我们在本系列前两部分中删除的那个 35KB 的测试文件。

 

利用 libundel 库完美恢复文件

尽管 e2undel 可以非常方便地简化恢复文件的过程,但是美中不足的是,其恢复出来的文件的文件名却丢失了,其原因是文件名是保存在父目录的目录项中的,而不是保存在索引节点中的。本系列文章第 2 部分中给出了一种通过遍历父目录的目录项来查找已删除文件的文件名的方法,但是由于索引节点会被重用,因此通过这种方式恢复出来的文件名也许并不总是正确的。另外,如果目录结构的非常复杂,就很难确定某个文件的父目录究竟是哪个,因此查找正确文件名的难度就会变得很大。如果能在删除文件时记录下索引节点号和文件名之间的对应关系,这个问题就能完美地解决了。

这个问题在 e2undel 中得到了完美的解决。实际上,所有删除命令,例如 rm、unlink 都是通过一些底层的系统调用(例如 unlink(2)、rmdir(2))来实现的。基于这一点,e2undel 又利用了Linux 系统中动态链接库加载时提供的一种便利:如果设置了环境变量 LD_PRELOAD,那么在加载动态链接库时,会优先从 $LD_PRELOAD 指向的动态链接库中查找符号表,然后才会在系统使用 ldconfig 配置的动态链接库中继续查找符号表。因此,我们可以在自己编写的库函数中实现一部分系统调用,并将这个库优先于系统库加载,这样就能欺骗系统使用我们自己定义的系统调用来执行原有的操作。具体到 e2undel 上来说,就是要在调用这些系统调用删除文件时,记录下文件名和索引节点号之间的对应关系。

在编译 e2undel 源代码之后,还会生成一个库文件 libundel.so.1.0,其中包含了删除文件时所使用的一些系统调用的钩子函数。e2undel官方主页上下载的源码包中仅仅包括了对 unlink(2) 和 remove(3) 这两个系统调用的钩子函数,但是从 2.6.16 版本的内核开始,引入了一系列新的系统调用,包括 faccessat(2), fchmodat(2), fchownat(2), fstatat(2), futimesat(2), linkat(2), mkdirat(2), mknodat(2), openat(2), readlinkat(2), renameat(2), symlinkat(2), unlinkat(2), mkfifoat(3) 等,尽管这些系统调用目前还没有成为POSIX标准的一部分,但是相信这个过程不会很久了。目前诸如 Fecora Core 8 之类的系统中自带的 rm 命令(属于 coreutils)包已经使用这些新的系统调用进行了改写,另外本文下载部分中的补丁文件中已经提供了对 rmdir 和 unlinkat 的钩子函数。部分源代码如下所示:
清单5. libundel.c 代码片断

                
void _init()
{
  f = fopen("/var/e2undel/e2undel", "a");
  if (!f)
    fprintf(stderr, "libundel: can't open log file, undeletion disabled\n");
}

 

......

 

int rmdir(const char *pathname)
{
  int err;
  struct stat buf;
  char pwd[PATH_MAX];
  int (*rmdirp)(char *) = dlsym(RTLD_NEXT, "rmdir");

 

  if (NULL != pathname)
  {
    if (__lxstat(3, pathname, &buf)) buf.st_ino = 0;
    if (!realpath(pathname, pwd)) pwd[0] = '\0';
  }
  err = (*rmdirp)((char *) pathname);
  if (err) return err;      /* remove() did not succeed */
  if (f)
  {
    if (!S_ISLNK(buf.st_mode))  /* !!! should we check for other file types? */
    {                         /* don't log deleted symlinks */
      fprintf(f, "%ld,%ld::%ld::%s\n",
              (long) (buf.st_dev & 0xff00) / 256,
              (long) buf.st_dev & 0xff,
              (long) buf.st_ino, pwd[0] ? pwd : pathname);
      fflush(f);
    }
  }   /* if (f) */
    return err;
}   /* rmdir() */

 

......

 

void _fini()
{
  if (f) fclose(f);
}

_init 和 _fini 这两个函数分别在打开和关闭动态链接库时执行,分别用来打开和关闭日志文件。如果某个命令(例如 rm)执行了 rmdir 系统调用,就会被这个库接管。rmdir 的处理比较简单,它先搜索到真正的 rmdir 系统调用的符号表,然后使用同样的参数来执行这个系统调用,如果成功,就将有关索引节点和文件名之类的信息记录到日志中(默认是 /var/e2undel/ e2undel)。

 

这个库的使用非常简单,请执行下面的命令:
清单6. libundel 的设置

                
# cp libundel.so.1.0 /usr/local/lib
# cd /usr/local/lib
# ln -s libundel.so.1.0 libundel.so.1
# ln –s libundel.so.1.0 libundel.so

 

# ldconfig
# mkdir /var/e2undel
# chmod 711 /var/e2undel
# touch /var/e2undel/e2undel
# chmod 622 /var/e2undel/e2undel

上面的设置仅仅允许 root 用户可以恢复文件,如果希望让普通用户也能恢复文件,就需要修改对应文件的权限设置。

 

现在尝试以另外一个用户的身份来删除些文件:
清单7. 设置libundel之后删除文件

                
$ export LD_PRELOAD=/usr/local/lib/libundel.so
$ rm -rf e2undel-0.82

要想记录所有用户的删除文件的操作,可以将 export LD_PRELOAD=/usr/local/lib/libundel.so 这行内容加入到 /etc/profile 文件中。

 

现在使用 e2undel 来恢复已删除的文件就变得简单多了,因为已经可以通过文件名来恢复文件了:
清单8. e2undel 利用 libundel 日志恢复删除文件

                
# ./e2undel -a -t -d /dev/sda2 -s /tmp/recover/ 
./e2undel 0.82
Trying to recover files on /dev/sda2, saving them on /tmp/recover/

 

/dev/sda2 opened for read-only access
/dev/sda2 was not cleanly unmounted.
Do you want wo continue (y/n)? y
489600 inodes (489531 free)
977956 blocks of 4096 bytes (941559 free)
last mounted on Fri Dec 28 20:45:05 2007

 

reading log file: 
found 24 entries for /dev/sda2 in log file
searching for deleted inodes on /dev/sda2:
|==================================================|
489600 inodes scanned, 26 deleted files found
checking names from log file for deleted files: 24 deleted files with names

 

   user name | 1 <12 h | 2 <48 h | 3  <7 d | 4 <30 d | 5  <1 y | 6 older
-------------+---------+---------+---------+---------+---------+--------
        root |       0 |       0 |       0 |       2 |       0 |       0
       phost |      24 |       0 |       0 |       0 |       0 |       0
Select user name from table or press enter to exit: phost
Select time interval (1 to 6) or press enter to exit: 1

 

  inode     size  deleted at        name
-----------------------------------------------------------
 310083        0  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82
 310113     2792  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/BUGS
 310115     3268  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/HISTORY
 310116     1349  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/INSTALL
 310117     1841  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/INSTALL.de
 310118     2175  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/Makefile
 310119    12247  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/README
 310120     9545  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/README.de
 310121    13690  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/apprentice.c
 310122    19665  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/ascmagic.c
 310123      221  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/common.h
 310124     1036  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/compactlog.c
 310125    30109  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/e2undel.c
 310127     2447  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/e2undel.h
 310128     1077  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/file.c
 310129     2080  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/file.h
 310130     4484  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/find_del.c
 310131     2141  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/is_tar.c
 310132     2373  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/libundel.c
 310133     7655  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/log.c
 310134    39600  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/magic.h
 310135     4591  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/names.h
 310136    13117  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/softmagic.c
 310137     5183  Dec 29 17:23 2007 /tmp/test/undel/e2undel-0.82/tar.h

如果对所有用户都打开这个功能,由于日志文件是单向增长的,随着时间的推移,可能会变得很大,不过 e2undel 中还提供了一个 compactlog 工具来删除日志文件中的重复项。

 

在学习本系列文章介绍的技术之后,利用 e2undel 之类的工具,并使用本系列文章第一部分中提供的补丁,恢复删除文件就变得非常简单了。但是在日常使用过程中,大家可能还会碰到一些意外的情况,比如文件系统发生问题,从而无法正常挂载;使用 mke2fs 之类的工具重做了文件系统;甚至磁盘上出现坏道。此时应该如何恢复系统中的文件呢,下面让我们来逐一看一下如何解决这些问题。
文件系统故障的恢复

回想一下,在超级块中保存了有关文件系统本身的一些数据,在 ext2 文件系统中,还使用块组描述符保存了有关块组的信息;另外,索引节点位图、块位图中分别保存了索引节点和磁盘上数据块的使用情况,而文件本身的索引节点信息(即文件的元数据)则保存在索引节点表中。这些数据对于文件系统来说都是至关重要的,它们是存取文件的基础。如果超级块和块组描述符的信息一旦出错,则会造成文件系统无法正常挂载的情况出现。造成这些信息出错的原因有:

  • 系统管理员操作失误。
  • 设备驱动程序或第三方软件(例如mke2fs之类的)有 bug。
  • 电源意外断电。
  • 内核有 bug。

如果出现这种问题,可能造成的后果有:

  1. 文件系统无法挂载。
  2. 操作系统挂起。
  3. 即使文件系统能够成功挂载,在系统重启时也可能会看到一些错误,或者目录列表中出现乱字符的情况等。

下面让我们来模拟一个出现这种错误的情况。我们知道,超级块信息就保存在分区中的第一个块中,现在我们来试验一下清空这个块中数据的后果:
清单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 等,这些文件系统上删除的文件能否成功恢复呢?有哪些工具可以用来辅助恢复文件呢?本系列后续文章将继续探讨这个问题。

 


作者简介

 

冯锐,软件工程师,目前在 IBM 开发中心从事 AIX 性能测试方面的工作,您可以通过 fengrui@cn.ibm.com 与他联系。

王磊,软件工程师,目前在 IBM 开发中心从事 AIX 性能测试方面的工作,您可以通过 cdlwangl@cn.ibm.com 与他联系。


下载

 

描述 名字 大小 下载方法
修正 e2undel 源码问题的补丁 e2undel-0.82.patch 5KB HTTP

 

 

参考资料

作者简介

冯锐,软件工程师,目前在 IBM 开发中心从事 AIX 性能测试方面的工作。您可以通过 fengrui@cn.ibm.com 与他联系。

 

王磊,软件工程师,目前在 IBM 开发中心从事 AIX 性能测试方面的工作,您可以通过 cdlwangl@cn.ibm.com 与他联系。

 

出处:http://www.ibm.com/developerworks/cn/linux/l-cn-filesrc3/

 

发表评论