如何恢复 Linux 上删除的文件-特殊文件的恢复(2)
- UID
- 1066743
|
如何恢复 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.c1
| # 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
|
|
|
|
|
|
|