ext3 文件系统中恢复删除文件的方法 1:正文匹配
我们知道,磁盘以及磁盘上的某个分区在系统中都以设备文件的形式存在,我们可以将这些设备文件当作普通文件一样来读取数据。不过,既然已经无法通过文件名来定位文件的数据块位置,现在要想恢复文件,就必须了解文件的数据,并通过正文匹配进行检索了。自然,grep
就是这样一个理想的工具:
清单8. 使用 grep 搜索磁盘上的文件
# ./creatfile.sh 35 testfile.35K
# rm -f testfile.35K
# cd ..
# umount test
# grep -A 1 -B 1 -a -n " 10:0" /dev/sdb7 > sdb7.testfile.35K
grep: memory exhausted
# cat sdb7.testfile.35K
545- 9:0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
546:10:0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
547- 11:0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
在清单 8 中的例子中,我们首先使用本系列文章第 1 部分中提供的脚本创建了一个测试文件,在删除该文件后,通过利用 grep
以“ 10:0”作为关键字对设备文件进行正文匹配,我们顺利地找到了测试文件中的第 10 行数据。需要注意的是,grep
命令中我们使用了几个参数,-a 参数表明将设备文件当作文本文件进行处理,-B 和 –A
参数分别说明同时打印匹配项的前/后几行的数据。同一关键字可能会在很多文件中多次出现,因此如何从中挑选出所需的数据就完全取决于对数据的熟悉程度了。
利用 grep 进行正文匹配也存在一个问题,由于打开的设备文件非常大,grep
会产生内存不足的问题,因此无法完成整个设备的正文匹配工作。解决这个问题的方法是使用 strings。strings
命令可以将文件中所有可打印字符全部打印出来,因此对于正文匹配的目的来说,它可以很好地实现文本数据的提取工作。
清单9. 使用 strings 提取设备文件中的可打印数据
# time strings /dev/sdb7 > sdb7.strings
real 12m42.386s
user 10m44.140s
sys 1m42.950s
# grep " 10:0" sdb7.strings
10:0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,
清单 9 中的例子使用 strings 将 /dev/sdb7
中的可打印字符提取出来,并重定向到一个文本文件中,此后就可以使用任何文本编辑工具或正文匹配工具从这个文件中寻找自己的数据了。不过扫描磁盘需要的时间可能会比较长,在上面这个例子中,扫描
20GB 的分区大概需要 13 分钟。
ext3 文件系统中恢复删除文件的方法 2:提前备份元数据
利用 grep 或 strings
对磁盘数据进行正文匹配,这种方法有一定的局限性:它只是对文本文件的恢复比较有用,这还要依赖于用户对数据的熟悉程度;而对于二进制文件来说,除非有其他备份,否则要通过这种正文匹配的方式来恢复文件,几乎是不可能完成的任务。然而,如果没有其他机制的辅助,在
ext3
文件系统中,这却几乎是唯一可以尝试用来恢复数据的方法了。究其原因是因为,这种方法仅仅是一种亡羊补牢的做法,而所需要的一些关键数据已经不存在了,因此恢复文件就变得愈发困难了。不过,索引节点中的这些关键信息在删除文件之前肯定是存在的。
受本系列文章介绍过的 debugfs 和 libundel 的启发,我们可以对 libundel
进行扩充,使其在删除文件的同时,除了记录文件名、磁盘设备、索引节点信息之外,把文件大小、数据块位置等重要信息也同时记录下来,这样就可以根据日志文件中的元数据信息完美地恢复文件了。
为了实现以上想法,我们需要对 libundel 的实现进行很大的调整,下面介绍所使用的几个关键的函数。
清单10. get_bmap 函数的实现
static unsigned long get_bmap(int fd, unsigned long block)
{
int ret;
unsigned int b;
b = block;
ret = ioctl(fd, FIBMAP, &b);
if (ret < 0)
{
if (errno == EPERM)
{
if (f)
{
{
fprintf(f, "No permission to use FIBMAP ioctl.\n");
fflush(f);
}
}
return 0;
}
}
return b;
}
get_bmap 函数是整个改进的基础,其作用是返回磁盘上保存指定文件的某块数据使用的数据块的位置,这是通过 ioctl 的
FIBMAP 命令来实现的。ioctl 提供了一种在用户空间与内核进行交互的便捷机制,在内核中会通过调用
f_op->ioctl() 进行处理。对于 FIBMAP 命令来说,会由 VFS 调用
f_op->bmap() 获取指定数据块在磁盘上的块号,并将结果保存到第 3
个参数指向的地址中。
清单11. get_blocks 函数的实现
int get_blocks(const char *filename)
{
#ifdef HAVE_FSTAT64
struct stat64 fileinfo;
#else
struct stat fileinfo;
#endif
int bs;
long fd;
unsigned long block, last_block = 0, first_cblock = 0, numblocks, i;
long bpib;
char cblock_list[256];
char pwd[PATH_MAX];
if (NULL != filename)
{
if (__lxstat(3, filename, &fileinfo))
fileinfo.st_ino = 0;
if (!realpath(filename, pwd))
pwd[0] = '\0';
}
#ifdef HAVE_OPEN64
fd = open64(filename, O_RDONLY);
#else
fd = open(filename, O_RDONLY);
#endif
if (fd < 0) {
fprintf(stderr, "cannot open the file of %s\n", filename);
return -1;
}
if (ioctl(fd, FIGETBSZ, &bs) < 0) {
perror("FIGETBSZ");
close(fd);
return -1;
}
bpib = bs / 4;
numblocks = (fileinfo.st_size + (bs-1)) / bs;
sprintf(block_list, "%ld,%ld::%ld::%ld::%ld::",
(long) (fileinfo.st_dev & 0xff00) / 256,
(long) fileinfo.st_dev & 0xff,
(long) fileinfo.st_ino,
(long) fileinfo.st_size, (long)bs);
for (i=0; i < numblocks; i++) {
block = get_bmap(fd, i);
if (last_block == 0) {
first_cblock = block;
}
if (last_block && (block != last_block +1) ) {
sprintf(cblock_list, "(%ld-%ld):%ld-%ld,",
i-(last_block-first_cblock)-1, i-1, first_cblock, last_block);
strcat(block_list, cblock_list);
first_cblock = block;
}
if (i == numblocks - 1 ) {
if (last_block == 0) {
sprintf(cblock_list, "(%ld-%ld):%ld-%ld", i, i, first_cblock, block);
}
else {
sprintf(cblock_list, "(%ld-%ld):%ld-%ld",
i-(last_block-first_cblock)-1, i, first_cblock, block);
}
strcat(block_list, cblock_list);
}
last_block = block;
}
sprintf(cblock_list, "::%s", pwd[0] ? pwd : filename);
strcat(block_list, cblock_list);
close(fd);
return 0;
}
get_blocks
函数的作用是遍历文件包含的每个数据块,获取它们在磁盘上保存的数据块位置。为了保证日志文件中的数据尽量精简,数据块位置会按照本身的连续情况被划分成一个个的连续块域,每个都记录为下面的形式:(文件中的起始块号-文件中的结束块号):磁盘上的起始数据块号-磁盘上的结束数据块号。
自然,get_blocks 函数应该是在用户调用删除文件的系统调用时被调用的,这些系统调用的实现也应该进行修改。清单 12
给出了 remove 库函数修改后的例子。
清单12. remove 库函数的实现
int remove(const char *pathname)
{
int err;
int (*removep)(char *) = dlsym(RTLD_NEXT, "remove");
err = get_blocks(pathname);
if (err < 0)
{
fprintf(stderr, "error while reading blocks from %s\n", pathname);
}
err = (*removep)((char *) pathname);
if (err) return err;
if (f)
{
fprintf(f, "%s\n", block_list);
fflush(f);
}
return err;
}
现在,记录元数据信息的日志文件 /var/e2undel/e2undel 如清单 13 所示:
清单13. 修改后的 /var/e2undel/e2undel 样例文件
8,23::48865::13690::4096::(0-3):106496-106499::/tmp/test/apprentice.c
8,23::48867::19665::4096::(0-4):106528-106532::/tmp/test/ascmagic.c
8,23::48872::1036::4096::(0-0):106545-106545::/tmp/test/compactlog.c
8,23::48875::31272::4096::(0-7):106596-106603::/tmp/test/e2undel.c
8,23::48878::1077::4096::(0-0):106616-106616::/tmp/test/file.c
8,23::48880::4462::4096::(0-1):106618-106619::/tmp/test/find_del.c
8,23::48885::2141::4096::(0-0):106628-106628::/tmp/test/is_tar.c
8,23::48887::6540::4096::(0-1):106631-106632::/tmp/test/libundel.c
8,23::48890::8983::4096::(0-2):106637-106639::/tmp/test/log.c
8,23::48897::13117::4096::(0-3):106663-106666::/tmp/test/softmagic.c
8,23::48866::10485760::4096::(0-11):108544-108555,(12-1035):108557-109580,\
(1036-2059):109583-110606,(2060-2559):110608-111107::/tmp/test/testfile.10M
8,23::48866::7169::4096::(1-1):129024-129024::/tmp/test/hole
8,23::48865::21505::4096::(1-1):129025-129025,(5-5):129026-129026::/tmp/test/hole2
文件名前所增加的 3 项分别为文件大小、块大小和数据块列表。
当然,为了能够利用 /var/e2undel/e2undel 中记录的信息恢复文件,e2undel
的对应实现也必须相应地进行修改。详细实现请参看本文下载部分给出的补丁,在此不再详述。修改后的 libundel
的用法与原来完全相同,详细介绍请参看本系列文章的第 3 部分。
ext3 文件系统中恢复删除文件的方法 3:修改 ext3 实现
利用 libundel 方法的确可以完美地恢复删除文件,但是这毕竟是一种治标不治本的方法,就像是本系列文章第 3
部分中所介绍的一样,这要依赖于环境变量 LD_PRELOAD
的设置。如果用户在删除文件之前,并没有正确设置这个环境变量,那么对应的删除操作就不会记录到日志文件
/var/e2undel/e2undel 中,因此也就无从恢复文件了。
还记得在本系列文章的第 2 部分中我们是如何通过修改 Linux 内核来支持大文件的删除的 吗?采用同样的思路,我们也可以修改
ext3 的实现,使其支持文件的恢复,这样本系列前面文章中介绍的工具就都可以在 ext3 文件系统上正常使用了。
总结一下,在删除文件时,内核中执行的操作至少包括:
在块位图中将该文件所占用的数据块标识为可用状态。
在索引节点位图中将该文件所占用的索引节点标识为可用状态。
将该文件索引节点中的硬链接数目设置为 0。
清空间接索引节点中的数据.
清空 i_block 数组中各个成员中的数据。
将索引节点中的文件大小(i_size)和占用块数(i_blocks)设置为 0。
将该文件索引节点中的删除时间设置为当前时间。
将父目录项中该文件对应项中的索引节点号设置为 0,并扩展前一项,使其包含该项所占用的空间。
其中步骤 5 和 6 只适用于 ext3 文件系统,在 ext2 文件系统中并不需要执行。在 ext3
文件系统的实现中,它们分别是由 fs/ext3/inode.c 中的 ext3_delete_inode 和
ext3_truncate 函数实现的:
清单14. ext3_delete_inode 函数实现
182 void ext3_delete_inode (struct inode * inode)
183 {
184 handle_t *handle;
185
186 truncate_inode_pages(&inode->i_data, 0);
187
188 if (is_bad_inode(inode))
189 goto no_delete;
190
191 handle = start_transaction(inode);
192 if (IS_ERR(handle)) {
193
198 ext3_orphan_del(NULL, inode);
199 goto no_delete;
200 }
201
202 if (IS_SYNC(inode))
203 handle->h_sync = 1;
204 inode->i_size = 0;
205 if (inode->i_blocks)
206 ext3_truncate(inode);
207
215 ext3_orphan_del(handle, inode);
216 EXT3_I(inode)->i_dtime = get_seconds();
217
218
225 if (ext3_mark_inode_dirty(handle, inode))
226
227 clear_inode(inode);
228 else
229 ext3_free_inode(handle, inode);
230 ext3_journal_stop(handle);
231 return;
232 no_delete:
233 clear_inode(inode);
234 }
清单15. ext3_truncate 函数实现
2219 void ext3_truncate(struct inode *inode)
2220 {
2221 handle_t *handle;
2222 struct ext3_inode_info *ei = EXT3_I(inode);
2223 __le32 *i_data = ei->i_data;
2224 int addr_per_block = EXT3_ADDR_PER_BLOCK(inode->i_sb);
2225 struct address_space *mapping = inode->i_mapping;
2226 int offsets[4];
2227 Indirect chain[4];
2228 Indirect *partial;
2229 __le32 nr = 0;
2230 int n;
2231 long last_block;
2232 unsigned blocksize = inode->i_sb->s_blocksize;
2233 struct page *page;
2234
...
2247 if ((inode->i_size & (blocksize - 1)) == 0) {
2248
2249 page = NULL;
2250 } else {
2251 page = grab_cache_page(mapping,
2252 inode->i_size >> PAGE_CACHE_SHIFT);
2253 if (!page)
2254 return;
2255 }
2256
2257 handle = start_transaction(inode);
2258 if (IS_ERR(handle)) {
2259 if (page) {
2260 clear_highpage(page);
2261 flush_dcache_page(page);
2262 unlock_page(page);
2263 page_cache_release(page);
2264 }
2265 return;
2266 }
2267
2268 last_block = (inode->i_size + blocksize-1)
2269 >> EXT3_BLOCK_SIZE_BITS(inode->i_sb);
2270
2271 if (page)
2272 ext3_block_truncate_page(handle, page, mapping, inode->i_size);
2273
2274 n = ext3_block_to_path(inode, last_block, offsets, NULL);
2275 if (n == 0)
2276 goto out_stop;
...
2287 if (ext3_orphan_add(handle, inode))
2288 goto out_stop;
2289
...
2297 ei->i_disksize = inode->i_size;
...
2303 mutex_lock(&ei->truncate_mutex);
2304
2305 if (n == 1) {
2306 ext3_free_data(handle, inode, NULL, i_data+offsets[0],
2307 i_data + EXT3_NDIR_BLOCKS);
2308 goto do_indirects;
2309 }
2310
2311 partial = ext3_find_shared(inode, n, offsets, chain, &nr);
2312
2313 if (nr) {
2314 if (partial == chain) {
2315
2316 ext3_free_branches(handle, inode, NULL,
2317 &nr, &nr+1, (chain+n-1) - partial);
2318 *partial->p = 0;
2319
2323 } else {
2324
2325 BUFFER_TRACE(partial->bh, "get_write_access");
2326 ext3_free_branches(handle, inode, partial->bh,
2327 partial->p,
2328 partial->p+1, (chain+n-1) - partial);
2329 }
2330 }
2331
2332 while (partial > chain) {
2333 ext3_free_branches(handle, inode, partial->bh, partial->p + 1,
2334 (__le32*)partial->bh->b_data+addr_per_block,
2335 (chain+n-1) - partial);
2336 BUFFER_TRACE(partial->bh, "call brelse");
2337 brelse (partial->bh);
2338 partial--;
2339 }
2340 do_indirects:
2341
2342 switch (offsets[0]) {
2343 default:
2344 nr = i_data[EXT3_IND_BLOCK];
2345 if (nr) {
2346 ext3_free_branches(handle, inode, NULL, &nr, &nr+1, 1);
2347 i_data[EXT3_IND_BLOCK] = 0;
2348 }
2349 case EXT3_IND_BLOCK:
2350 nr = i_data[EXT3_DIND_BLOCK];
2351 if (nr) {
2352 ext3_free_branches(handle, inode, NULL, &nr, &nr+1, 2);
2353 i_data[EXT3_DIND_BLOCK] = 0;
2354 }
2355 case EXT3_DIND_BLOCK:
2356 nr = i_data[EXT3_TIND_BLOCK];
2357 if (nr) {
2358 ext3_free_branches(handle, inode, NULL, &nr, &nr+1, 3);
2359 i_data[EXT3_TIND_BLOCK] = 0;
2360 }
2361 case EXT3_TIND_BLOCK:
2362 ;
2363 }
2364
2365 ext3_discard_reservation(inode);
2366
2367 mutex_unlock(&ei->truncate_mutex);
2368 inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC;
2369 ext3_mark_inode_dirty(handle, inode);
2370
2371
2375 if (IS_SYNC(inode))
2376 handle->h_sync = 1;
2377 out_stop:
2378
2385 if (inode->i_nlink)
2386 ext3_orphan_del(handle, inode);
2387
2388 ext3_journal_stop(handle);
2389 }
清单 14 和 15 列出的 ext3_delete_inode 和 ext3_truncate
函数实现中,使用黑体标出了与前面提到的问题相关的部分代码。本文下载部分给出的针对 ext3
文件系统的补丁中,包括了对这些问题的一些修改。清单 16 给出了使用这个补丁之后在 ext3 文件系统中删除文件的一个例子。
清单16. 利用 debugfs 工具查看删除文件的信息
# ./creatfile.sh 90 testfile.90K
# ls -li
total 116
12 -rwxr-xr-x 1 root root 1407 2008-01-23 07:25 creatfile.sh
11 drwx------ 2 root root 16384 2008-01-23 07:23 lost+found
13 -rw-r--r-- 1 root root 92160 2008-01-23 07:25 testfile.90K
# rm -f testfile.90K
# cd ..
# umount /tmp/test
# debugfs /dev/sda3
debugfs 1.40.2 (12-Jul-2007)
debugfs: lsdel
Inode Owner Mode Size Blocks Time deleted
0 deleted inodes found.
debugfs: stat <13>
Inode: 13 Type: regular Mode: 0644 Flags: 0x0 Generation: 3438957668
User: 0 Group: 0 Size: 92160
File ACL: 0 Directory ACL: 0
Links: 1 Blockcount: 192
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x47967b69 -- Wed Jan 23 07:25:29 2008
atime: 0x47967b68 -- Wed Jan 23 07:25:28 2008
mtime: 0x47967b69 -- Wed Jan 23 07:25:29 2008
BLOCKS:
(0-11):22528-22539, (IND):22540, (12-22):22541-22551
TOTAL: 24
debugfs:
在清单 16 中,我们首先创建了一个大小为 90KB 的测试文件,删除该文件并使用 debugfs
查看对应的设备文件时,stat <13>
显示的信息说明数据块的位置信息都仍然保存在了索引节点中,不过 lsdel
命令并未找到这个索引节点。因此下载部分中给出的补丁尚不完善,感兴趣的读者可以自行开发出更加完善的补丁。
小结
本文首先介绍了 ext3 文件系统作为一种日志文件系统的一些特性,然后针对这些特性介绍了 3
种恢复删除文件的方法:正文匹配、利用 libundel 和修改内核中 ext3 文件系统的实现,并给出了对 libundel 和
ext3 文件系统实现的补丁程序。应用本文介绍的方法,读者可以最大程度地恢复 ext3
文件系统中删除的文件。本系列的后续文章将继续探讨在其他文件系统上如何恢复已删除的文件,并着手设计更加通用的文件备份方法。