Board logo

标题: 如何恢复 Linux 上删除的文件-原理及普通文件的恢复(4) [打印本页]

作者: look_w    时间: 2018-5-22 14:58     标题: 如何恢复 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 命令又可以完美地恢复出整个文件来了。
显然,这个补丁并不完善,因为这个补丁中的处理只是保留了索引数据块中的索引节点数据,但是还没有考虑数据块位图的处理,如果对应的数据块没有设置为正在使用的状态,并且刚好这些数据块被重用了,其中的索引节点数据就有可能会被覆盖掉了,这样就彻底没有办法再恢复文件了。感兴趣的读者可以沿用这个思路自行开发一个比较完善的补丁。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0