如何恢复 Linux 上删除的文件-原理及普通文件的恢复(4)
- UID
- 1066743
|
如何恢复 Linux 上删除的文件-原理及普通文件的恢复(4)
恢复删除文件现在将刚才创建的两个文件删除:
清单14. 删除测试文件1
| # rm -f testfile.35K testfile.10M
|
debugfs 的 lsdel 命令可以查看文件系统中删除的索引节点的信息:
清单15. 使用 lsdel 命令搜索已删除的文件1
2
3
4
5
6
| # echo "lsdel" | debugfs /dev/sdb6
debugfs 1.39 (29-May-2006)
Inode Owner Mode Size Blocks Time deleted
13 0 100644 35840 9/9 Mon Oct 29 20:32:05 2007
14 0 100644 10485760 2564/2564 Mon Oct 29 20:32:05 2007
2 deleted inodes found.
|
回想一下 inode 结构中有 4 个有关时间的域,分别是 i_atime、i_ctime、i_mtime和i_dtime,分别表示该索引节点的最近访问时间、创建时间、修改时间和删除时间。其中 i_dtime域只有在该索引节点对应的文件或目录被删除时才会被设置。dubugfs 的 lsdel 命令会去扫描磁盘上索引节点表中的所有索引节点,其中 i_dtime 不为空的项就被认为是已经删除的文件所对应的索引节点。
从上面的结果可以看到,刚才删除的两个文件都已经找到了,我们可以通过文件大小区分这两个文件,二者一个大小为35K,另外一个大小为10M,正式我们刚才删除的两个文件。debugfs 的 dump 命令可以帮助恢复文件:
清单16. 使用 dump 命令恢复已删除的文件1
2
| # echo "dump <13> /tmp/recover/testfile.35K.dump" | debugfs /dev/sdb6
# echo "dump <14> /tmp/recover/testfile.10M.dump" | debugfs /dev/sdb6
|
执行上面的命令之后,在 /tmp/recover 目录中会生成两个文件,比较这两个文件与我们前面备份的文件的内容就会发现,testfile.35K.dump 与 testfile.35K.orig 的内容完全相同,而 testfile.10M.dump 文件中则仅有前 48K 数据是对的,后面的数据全部为 0 了。这是否意味着删除文件时间已经把数据也同时删除了呢?实际上不是,我们还是有办法把数据全部恢复出来的。记得我们刚才使用 debugfs 的 stat 命令查看索引节点 14 时的 BLOCKS 的数据吗?这些数据记录了整个文件在磁盘上存储的位置,有了这些数据就可以把整个文件恢复出来了,请执行下面的命令:
清单 17. 使用 dd 命令手工恢复已删除的文件1
2
3
4
5
6
| # dd if=/dev/sdb6 of=/tmp/recover/testfile.10M.dd.part1 bs=4096 count=12 skip=24576
# dd if=/dev/sdb6 of=/tmp/recover/testfile.10M.dd.part2 bs=4096 count=1024 skip=24589
# dd if=/dev/sdb6 of=/tmp/recover/testfile.10M.dd.part2 bs=4096 count=1024 skip=25615
# dd if=/dev/sdb6 of=/tmp/recover/testfile.10M.dd.part4 bs=4096 count=500 skip=26640
# cat /tmp/recover/testfile.10M.dd.part[1-4] > /tmp/recover/ testfile.10M.dd
|
比较一下最终的 testfile.10M.dd 文件和已经备份过的 testfile.10M.orig 文件就会发现,二者完全相同:
清单 18. 使用 diff 命令对恢复文件和原文件进行比较1
| # diff /tmp/recover/ testfile.10M.dd /tmp/test/ testfile.10M.orig
|
数据明明存在,但是刚才我们为什么没法使用 debugfs 的 dump 命令将数据恢复出来呢?现在使用 debugfs 的 stat 命令再次查看一下索引节点 14 的信息:
清单 19. 再次查看索引节点 <14> 的详细信息1
2
3
4
5
6
7
8
9
10
11
12
| # echo "stat <14>" | debugfs /dev/sdb6
debugfs 1.39 (29-May-2006)
Inode: 14 Type: regular Mode: 0644 Flags: 0x0 Generation: 2957086760
User: 0 Group: 0 Size: 10485760
File ACL: 0 Directory ACL: 0
Links: 0 Blockcount: 20512
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x47268995 -- Mon Oct 29 20:32:05 2007
atime: 0x472684a5 -- Mon Oct 29 20:11:01 2007
mtime: 0x47268485 -- Mon Oct 29 20:10:29 2007
dtime: 0x47268995 -- Mon Oct 29 20:32:05 2007
BLOCKS0-11):24576-24587, (IND):24588, (DIND):25613TOTAL: 14
|
与前面的结果比较一下不难发现,BLOCKS后面的数据说明总块数为 14,而且也没有整个文件所占据的数据块的详细说明了。既然文件的数据全部都没有发生变化,那么间接寻址所使用的那些索引数据块会不会有问题呢?现在我们来查看一下 24588 这个间接索引块中的内容:
清单 20. 查看间接索引块 24588 中的内容1
2
3
4
5
6
| # dd if=/dev/sdb6 of=block. 24588 bs=4096 count=1 skip=24588
# hexdump block. 24588
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
0001000
|
显然,这个数据块的内容被全部清零了。debugfs 的dump 命令按照原来的寻址方式试图恢复文件时,所访问到的实际上都是第0 个数据块(引导块)中的内容。这个分区不是可引导分区,因此这个数据块中没有写入任何数据,因此 dump 恢复出来的数据只有前48K是正确的,其后所有的数据全部为0。
实际上,ext2 是一种非常优秀的文件系统,在磁盘空间足够的情况下,它总是试图将数据写入到磁盘上的连续数据块中,因此我们可以假定数据是连续存放的,跳过间接索引所占据的 24588、25613、25614和26639,将从24576 开始的其余 2500 个数据块读出,就能将整个文件完整地恢复出来。但是在磁盘空间有限的情况下,这种假设并不成立,如果系统中磁盘碎片较多,或者同一个块组中已经没有足够大的空间来保存整个文件,那么文件势必会被保存到一些不连续的数据块中,此时上面的方法就无法正常工作了。
反之,如果在删除文件的时候能够将间接寻址使用的索引数据块中的信息保存下来,那么不管文件在磁盘上是否连续,就都可以将文件完整地恢复出来了,但是这样就需要修改 ext2 文件系统的实现了。在 ext2 的实现中,与之有关的有两个函数:ext2_free_data 和 ext2_free_branches(都在 fs/ext2/inode.c 中)。2.6 版本内核中这两个函数的实现如下:
清单 21. 内核中 ext2_free_data 和 ext2_free_branches 函数的实现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
74
75
76
77
78
79
80
81
82
83
84
| 814 /**
815 * ext2_free_data - free a list of data blocks
816 * @inode: inode we are dealing with
817 * @p: array of block numbers
818 * @q: points immediately past the end of array
819 *
820 * We are freeing all blocks refered from that array (numbers are
821 * stored as little-endian 32-bit) and updating @inode->i_blocks
822 * appropriately.
823 */
824 static inline void ext2_free_data(struct inode *inode, __le32 *p, __le32 *q)
825 {
826 unsigned long block_to_free = 0, count = 0;
827 unsigned long nr;
828
829 for ( ; p < q ; p++) {
830 nr = le32_to_cpu(*p);
831 if (nr) {
832 *p = 0;
833 /* accumulate blocks to free if they're contiguous */
834 if (count == 0)
835 goto free_this;
836 else if (block_to_free == nr - count)
837 count++;
838 else {
839 mark_inode_dirty(inode);
840 ext2_free_blocks (inode, block_to_free, count);
841 free_this:
842 block_to_free = nr;
843 count = 1;
844 }
845 }
846 }
847 if (count > 0) {
848 mark_inode_dirty(inode);
849 ext2_free_blocks (inode, block_to_free, count);
850 }
851 }
852
853 /**
854 * ext2_free_branches - free an array of branches
855 * @inode: inode we are dealing with
856 * @p: array of block numbers
857 * @q: pointer immediately past the end of array
858 * @depth: depth of the branches to free
859 *
860 * We are freeing all blocks refered from these branches (numbers are
861 * stored as little-endian 32-bit) and updating @inode->i_blocks
862 * appropriately.
863 */
864 static void ext2_free_branches(struct inode *inode, __le32 *p, __le32 *q, int depth)
865 {
866 struct buffer_head * bh;
867 unsigned long nr;
868
869 if (depth--) {
870 int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb);
871 for ( ; p < q ; p++) {
872 nr = le32_to_cpu(*p);
873 if (!nr)
874 continue;
875 *p = 0;
876 bh = sb_bread(inode->i_sb, nr);
877 /*
878 * A read failure? Report error and clear slot
879 * (should be rare).
880 */
881 if (!bh) {
882 ext2_error(inode->i_sb, "ext2_free_branches",
883 "Read failure, inode=%ld, block=%ld",
884 inode->i_ino, nr);
885 continue;
886 }
887 ext2_free_branches(inode,
888 (__le32*)bh->b_data,
889 (__le32*)bh->b_data + addr_per_block,
890 depth);
891 bforget(bh);
892 ext2_free_blocks(inode, nr, 1);
893 mark_inode_dirty(inode);
894 }
895 } else
896 ext2_free_data(inode, p, q);
897 }
|
注意第 832 和 875 这两行就是用来将对应的索引项置为 0 的。将这两行代码注释掉(对于最新版本的内核 2.6.23 可以下载本文给的补丁)并重新编译 ext2 模块,然后重新加载新编译出来的模块,并重复上面的实验,就会发现利用 debugfs 的 dump 命令又可以完美地恢复出整个文件来了。
显然,这个补丁并不完善,因为这个补丁中的处理只是保留了索引数据块中的索引节点数据,但是还没有考虑数据块位图的处理,如果对应的数据块没有设置为正在使用的状态,并且刚好这些数据块被重用了,其中的索引节点数据就有可能会被覆盖掉了,这样就彻底没有办法再恢复文件了。感兴趣的读者可以沿用这个思路自行开发一个比较完善的补丁。 |
|
|
|
|
|