前言

本节将学习 Ext3 文件系统的 .journal 日志结构,存储原理,以及如何通过使用 Winhex 查看 .journal 日志对删除文件的恢复。

本节内容部分数据由 ChatGPT 提供,如有有误内容,将进行修改

总览

  • Ext3 文件系统的 .journal 日志结构

  • 恢复 Ext3 文件系统中被删除的文件

Ext3 文件系统的 .journal 日志结构

在 Ext3 文件系统中如果需要对删除文件恢复,离不开 .journal 日志文件,因为 .journal 日志文件中存储了文件系统的操作记录,包括文件的创建、删除、修改等操作,通过查看 .journal 日志文件,我们可以找到被删除文件的操作记录,从而恢复被删除的文件。

相比于 Ext2 文件系统,Ext3 文件系统在 Ext2 文件系统的基础上增加了 .journal 日志功能,.journal 日志功能可以保证文件系统的一致性,允许操作系统在数据写入磁盘之前先保留所有文件系统的更改日志,即使在系统崩溃或者断电的情况下,也能保证文件系统的完整性。

相较于 Ext2 FS,Ext3 FS 对删除文件的处理的不同 -- Linux Ext3 FAQ

Q: How can I recover (undelete) deleted files from my ext3 partition?

Actually, you can’t! This is what one of the developers, Andreas Dilger, said about it:
In order to ensure that ext3 can safely resume an unlink after a crash, it actually zeros out the block pointers in the inode, whereas
ext2 just marks these blocks as unused in the block bitmaps and marks the inode as “deleted” and leaves the block pointers alone.</p>

Your only hope is to “grep” for parts of your files that have been deleted and hope for the best.

— From the Linux Ext3 FAQ

Andreas Dilger: 为了确保 ext3 在崩溃后可以安全地恢复删除的文件,它实际上会将文件的块指针清零,而 ext2 只是将这 0 些块标记为未使用的块位图,并将 inode 标记为“已删除”,并保留块指针。

所以通过从底层提取 Ext3 FS 文件的流程来看,恢复被删除的文件是不可能的,因为 Ext3 FS 已经将文件的块指针清零了,所以我们只能通过查看 .journal 日志文件中被删除的文件的 i-node 来恢复被删除的文件。

.journal 的存储原理

Ext3 文件系统的 .journal 日志结构如上图所示,在日志结构中包含了一个 日志超级块,每次 Commit 事务会生成一条新的 日志记录,这些日志记录可以用来恢复文件系统至 Commit 前的状态。

每次 commit 的数据都由 描述块元数据块提交块 组成。

此外,在记录事务过程中撤销操作后,文件系统会将撤销操作的数据记录到 撤销块 中,这些撤销块可以用来恢复文件系统至撤销操作前的状态。

日志超级块

日志超级块是 .journal 日志文件的第一个块,它包含了日志文件的基本信息,如日志文件的大小、日志文件的版本号、日志文件的最大事务数、日志文件的最大事务块数等。

第 4.1 个表描述了 Journal 超级快的标准头。字节范围包括 0-3 的签名(Signature),0xc03b3998 的值指示了日志的版本信息。4-7 的块类型(Block type)定义了该块的用途,可以是描述块,提交(commit)块或者撤销(revoke)块。8-11 的序列号(Sequence number)定义了该块在提交中的先后顺序。

第 4.2 个表描述了日志超级块(journal superblock)的内容及其格式。12-15 的块大小(block size)定义了被记录的数据块的最大字节数(一般为 4KB)。16-19 的日志块数(Number of Journal blocks)标识了该日志共包含多少块。20-23 的日志起始块(Journal block where the journal actually starts)定义了日志的具体位置。24-27 的事务序列号(Sequence number of first transaction)确定了事务的起始位置。28-31 的事务起始日志块(Journal block of first transaction) 指定了记录这些存储块的起始位置。

第 4.3 个表描述了描述块(descriptor block)的内容及其格式。0-11 的标准头(Standard Header)参考之前定义的字节区间。12-15 的文件系统块(File system block)定义一个可恢复的文件系统块的位置。16-19 的条目标记(Entry flags)定义了标志块。如所示,0x01 表示此块与签名一致,0x02 表示此块具有相同的 UUID,0x04 表示此块已被当前事务删除(目前未使用),0x08 表示描述块中最后一项,20-23 的 UUID(Universal Unique Identifier)就是唯一标记,其中存储着特定文件系统的唯一标识符。如果设置了 SAME_UUID 标记,则不存在 UUID。

表 4.4 中的标准头部包含了一系列字节范围,它们描述了日志块的类型和序列号。表 4.5 中的标准头部也包含了一系列字节范围,其中还包括了用于撤回操作的尺寸,以及文件系统块地址列表。手动解码日志块时,遵循的是大尾数法,但要注意元数据/内容块的排序会取决于原始的系统。

日志操作过程的生命周期包括以下几个步骤:

写入条目:在执行任何更改之前,将更改写入 journal 条目。这可以确保在重新启动时能够回滚更改。
撤销操作:如果发生了撤销操作,首先写入 revoke block,以确保撤销操作被追踪。
提交:当所有的更改都写入 Journal 时,就可以开始提交了,这会将 Journal 中的条目提交到磁盘上。
重放:这可以在文件系统重新启动时进行,它会重新执行 Journal 中的更改,以确保文件系统的完整性和一致性。

Journal revoke 程序为通用文件系统 journaling 代码的一部分,是 ext2fs journaling 系统的一部分。

Revoke 是一种防止删除的元数据被使用同一块区域回放旧日志记录的机制。这种 revoke 机制在两个不同的位置应用:

提交 (Commit):在提交时,我们会将当前事务撤销的所有块记录到日志中。
恢复 (recovery):在恢复时,我们会记录所有撤销块的事务 ID。如果块的日志有多个撤销记录,只有最后一条有效,并且如果在最后一次撤销之后有日志记录,则该纪录仍将被重播。
我们可以在一次事务中得到撤销和新日志数据间的交互:

块被撤销,然后被写入日志:所需的最终结果是日志记录新块,所以我们在提交之前撤销此事务。
块被写入日志,然后被撤销:撤销必须优先于块的写入,因此我们需要取消日志登记或者在后续的 log 里写入撤销记录。在这种情况下,我们选择了后者:写入块时撤销当前事务中此块的任何撤销记录,因此事务中有任何撤销的块都必须在被写入日志之后撤销,因此撤销必须具有优先权。
revoke block 是在被删除的元数据的旧日志记录被重现在新数据上面时使用的机制。他们在两个不同的地方被使用:

提交:提交的时候,我们将当前事务中所有被撤销的块的完整列表写入日志中
恢复:恢复的时候,我们记录了所有被撤销块的事务 ID。如果一个块有多个撤销记录,只能看到最后一个,如果有比最后一个撤销更新的日志记录,他们得被重现。
新的日志记录和撤销之间也可能存在交互的情况:

块被撤销然后又写入到日志:我们期望的最终结果是将新块写入日志,所以要在事务提交之前取消撤销。
块被日志化然后又被撤销:这个撤销必须优先于写入该块,所以我们需要取消日志条目,或者在撤销之后才能写入日志块。在这种情况下,我们选择了后者:日志化一个块就会取消当前事务中该块的任何撤销记录,所以该事物中任何撤销块的操作都必须在日志块被日志化之后才可以,并且撤销必须优先发生。
块被撤销然后写入到数据:数据写入操作被允许成功,但是撤销操作不会被取消。我们仍然需要防止旧日志记录覆盖新数据,也不需要清除撤销位。
日志大小和分区大小之间的关系是,日志大小依赖于分区大小。在分配日志时,日志存储

日志撤销块是存储在 ext3 fs journal 中的,用于在发生文件删除时记录要撤销的日志记录,以释放对应资源。撤销块会在提交事务之前产生,以确保在更新文件内容而不是恢复文件之后,要撤销的日志记录不会被重新播放。journal 日志大小与分区大小之间是没有直接关联,但是一般来说,journal 的大小是根据文件系统中所存储的内容大小来调整。通过 i-节点号可以找到对应的日志记录,并从中找到适当的事件编号。使用底层 hex 编辑器可以编辑文件系统里的文件,但是如果一定需要使用命令操作,可以考虑使用 fsck 命令。