首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

如何恢复 Linux 上删除的文件-特殊文件的恢复(2)

如何恢复 Linux 上删除的文件-特殊文件的恢复(2)

目录在 ext2 文件系统中,目录是一种特殊的文件,其索引节点的结构与普通文件没什么两样,唯一的区别是目录中的数据都是按照 ext2_dir_entry_2 结构存储在数据块中的(按照 4 字节对齐)。在开始尝试恢复目录之前,首先让我们详细了解一下目录数据块中的数据究竟是如何存储的。现在我们使用 debugfs 来查看一个已经准备好的目录的信息:
清单9. 用来测试的文件系统的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# debugfs /dev/sdb6
debugfs 1.39 (29-May-2006)
debugfs:  ls -l
      2   40755 (2)      0      0    4096 28-Nov-2007 16:57 .
      2   40755 (2)      0      0    4096 28-Nov-2007 16:57 ..
     11   40700 (2)      0      0   16384 28-Nov-2007 16:52 lost+found
     12  100755 (1)      0      0    1406 28-Nov-2007 16:53 createfile.sh
     13  100644 (1)      0      0   35840 28-Nov-2007 16:53 testfile.35K
     14  100644 (1)      0      0   10485760 28-Nov-2007 16:54 testfile.10M
  32577   40755 (2)      0      0    4096 28-Nov-2007 16:56 dir1
     15  100644 (1)      0      0   35840 28-Nov-2007 16:56 testfile.35K.orig
     16  100644 (1)      0      0   10485760 28-Nov-2007 16:57 testfile.10M.orig

debugfs:  ls -l dir1
  32577   40755 (2)      0      0    4096 28-Nov-2007 16:56 .
      2   40755 (2)      0      0    4096 28-Nov-2007 16:57 ..
  32578  100755 (1)      0      0    1406 28-Nov-2007 16:55 createfile.sh
  32579   40755 (2)      0      0    4096 28-Nov-2007 16:55 subdir11
  48865   40755 (2)      0      0    4096 28-Nov-2007 16:55 subdir12
  32580  100644 (1)      0      0   35840 28-Nov-2007 16:56 testfile.35K
  32581  100644 (1)      0      0   10485760 28-Nov-2007 16:56 testfile.10M




从输出结果中可以看出,每个目录结构中至少要包含两项:当前目录(.)和父目录(..)的信息。在一个文件系统中,会有一些特殊的索引节点是保留的,用户创建的文件无法使用这些索引节点。2 就是这样一个特殊的索引节点,表示根目录。结合上面的输出结果,当前目录(.)和父目录(..)对应的索引节点号都是2,表示这是该分区的根目录。特别地,在使用 mke2fs 命令创建一个 ext2 类型的文件系统时,会自动创建一个名为 lost+found 的目录,并为其预留 4 个数据块的大小(16KB),其用途稍后就会介绍。
我们在根目录下面创建了几个文件和一个名为 dir1(索引节点号为 32577)的目录,并在 dir1 中又创建了两个子目录(subdir1 和 subdir2)和几个文件。
要想完美地恢复目录,必须了解清楚在删除目录时究竟对系统进行了哪些操作,其中哪些是可以恢复的,哪些是无法恢复的,这样才能寻找适当的方式去尝试恢复数据。现在先让我们记录下这个文件系统目前的一些状态:
清单10. 根目录(索引节点 <2>)子目录 dir1的 inode 信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
debugfs:  stat <2>
Inode: 2   Type: directory    Mode:  0755   Flags: 0x0   Generation: 0
User:     0   Group:     0   Size: 4096
File ACL: 0    Directory ACL: 0
Links: 4   Blockcount: 8
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x474d2d63 -- Wed Nov 28 16:57:07 2007
atime: 0x474d3203 -- Wed Nov 28 17:16:51 2007
mtime: 0x474d2d63 -- Wed Nov 28 16:57:07 2007
BLOCKS:
(0):1536
TOTAL: 1

debugfs:  stat <32577>
Inode: 32577   Type: directory    Mode:  0755   Flags: 0x0   Generation: 1695264350
User:     0   Group:     0   Size: 4096
File ACL: 1542    Directory ACL: 0
Links: 4   Blockcount: 16
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x474d2d2a -- Wed Nov 28 16:56:10 2007
atime: 0x474d3203 -- Wed Nov 28 17:16:51 2007
mtime: 0x474d2d2a -- Wed Nov 28 16:56:10 2007
BLOCKS:
(0):88064
TOTAL: 1




以及根目录和 dir1 目录的数据块:
清单11. 备份根目录和子目录 dir1 的数据块
1
2
# dd if=/dev/sdb6 of=/tmp/recover/block.1536.orig bs=4096 count=1 skip=1536
# dd if=/dev/sdb6 of=/tmp/recover/block.88064.orig bs=4096 count=1 skip=88064




为了方便阅读目录数据块中的数据,我们编写了一个小程序,源代码如下所示:
清单12. read_dir_entry.c 源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <stdio.h>
#include <stdlib.h>
#include <ext2fs/ext2_fs.h>

struct ext2_dir_entry_part {
        __u32   inode;                  /* Inode number */
        __u16   rec_len;                /* Directory entry length */
        __u8    name_len;               /* Name length */
        __u8    file_type;
} dep;

void usage()
{
  printf("read_dir_entry [dir entry filename] [dir entry size]\n");
}

int main(int argc, char **argv)
{

  struct ext2_dir_entry_2 de;
  char *filename = NULL;
  FILE *fp = NULL;
  int rtn = 0;
  int length = 0;
  int de_size = 0;

  if (argc < 3)
  {
    printf("Too few parameters!\n");
    usage();
    exit(1);
  }

  filename = argv[1];
  de_size  = atoi(argv[2]);

  fp = fopen(filename, "r");
  if (!fp)
  {
    printf("cannot open file: %s\n", filename);
    exit(1);
  }

  printf("  offset | inode number | rec_len | name_len | file_type | name\n");
  printf("========================================\n");

  while ( rtn = fread(&dep, sizeof(struct ext2_dir_entry_part), 1, fp) )
  {
    if (dep.rec_len <= 0)
    {
      fclose(fp);  
      exit(0);
    }

    fseek(fp, 0 - sizeof(struct ext2_dir_entry_part), SEEK_CUR);

    fread(&de, ((int)(dep.name_len + 3)/4)*4 + sizeof(struct ext2_dir_entry_part), 1, fp);
    de.name[de.name_len] = '\0';

    printf("%6d: %12d%12d%12d%12d  %s\n",
                  length, de.inode, de.rec_len, de.name_len, de.file_type, de.name);

    length += dep.rec_len;

    if (length >= de_size - sizeof(struct ext2_dir_entry_part))
    {
      fclose(fp);  
      exit(0);
    }
  }
   
  fclose(fp);  
}




这段程序的基本想法是要遍历目录对应的数据块的内容,并打印每个目录项的内容(一个 ext2_dir_entry_2 结构)。需要注意的是,在遍历整个文件时,我们并没有采用 rec_length 作为步长,而是采用了 name_length + sizeof(struct ext2_dir_entry_part) 作为步长,这是为了能够读取到其中被标识为删除的目录项的数据,大家稍后就会明白这一点。
将这段程序保存为 read_dir_entry.c,并编译成可执行程序:
清单13. 编译 read_dir_entry.c
1
# gcc –o read_dir_entry read_dir_entry.c




并分析刚才得到的两个数据块的结果:
清单14. 分析原始目录项中的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ./read_dir_entry block.1536.orig 4096
  offset | inode number | rec_len | name_len | file_type | name
=================================================================
     0:            2          12           1           2  .
    12:            2          12           2           2  ..
    24:           11          20          10           2  lost+found
    44:           12          24          13           1  createfile.sh
    68:           13          20          12           1  testfile.35K
    88:           14          20          12           1  testfile.10M
   108:        32577          12           4           2  dir1
   120:           15          28          17           1  testfile.35K.orig
   148:           16        3948          17           1  testfile.10M.orig

# ./read_dir_entry block.88064.orig 4096
  offset | inode number | rec_len | name_len | file_type | name
=================================================================
     0:        32577          12           1           2  .
    12:            2          12           2           2  ..
    24:        32578          24          13           1  createfile.sh
    48:        32579          16           8           2  subdir11
    64:        48865          16           8           2  subdir12
    80:        32580          20          12           1  testfile.35K
   100:        32581        3996          12           1  testfile.10M




这与上面在 debugfs 中使用 ls 命令看到的结果是完全吻合的。
现在删除 dir1 这个目录,然后卸载测试目录(这是为了确保删除文件操作会被同步到磁盘上的数据块中),然后重新读取我们关注的这两个数据块的内容:
清单15. 删除目录并重新备份目录项数据
1
2
3
4
5
6
# rm –rf /tmp/test/dir1
# cd /
# umount /tmp/test

# dd if=/dev/sdb6 of=/tmp/recover/block.1536.deleted bs=4096 count=1 skip=1536
# dd if=/dev/sdb6 of=/tmp/recover/block.88064. deleted bs=4096 count=1 skip=88064

返回列表