不使用 fsync() 替换现有文件会“损坏”吗?

不使用 fsync() 替换现有文件会“损坏”吗?

在 Linux 的mount(2)手册页中,我注意到以下摘录:

许多损坏的应用程序在通过以下模式替换现有文件时不使用 fsync()

fd = open("foo.new")/write(fd,...)/close(fd)/ rename("foo.new", "foo")

或者更糟

fd = open("foo", O_TRUNC)/write(fd,...)/close(fd).

如果启用了auto_da_alloc,ext4将检测replace-via-rename和replace-via-truncate模式,并强制分配任何延迟的分配块,以便在下一次日志提交时,在默认的data=ordered模式下,在提交 rename() 操作之前,新文件将被强制写入磁盘。这提供了与 ext3 大致相同级别的保证,并避免了在延迟分配块被强制写入磁盘之前系统崩溃时可能发生的“零长度”问题。

这段代码在什么意义上被“破坏”了?他们是否说该代码非法或不符合标准(POSIX 等)?

fsync()对于那些担心系统崩溃会发生什么的人来说,显然这可能是一个好主意。但是假设系统不会崩溃,那么两个版本的示例代码(没有 )是否都fsync()做了正确的事情?

答案1

rename预计是原子的:它要么完全完成,要么根本不完成。重命名 A 来代替 B 应该会让 A 和 B 都完好无损(这根本没有发生);或仅将 A 的内容放在 B 名下(已完全完成)。

只要系统不崩溃,无论fsync(等)调用如何,这种情况都会发生。

但是,如果系统确实崩溃,则可能会发现重命名本身已到达磁盘(并因此完成)。请记住,名称!=文件。文件/索引节点可以有多个名称。重命名是更改名称,而不是底层文件/数据。

所以你可以有这样的状态:你的程序写了A,将其重命名为替换B,然后断电了。结果文件系统将重命名写入磁盘,而不是 A 中的实际数据。没有fsync.因此,您最终会得到一个零长度的 B 或一个零填充的 B。

应用程序执行写入临时文件+重命名而不是仅仅覆盖文件的原因是因为它想要碰撞安全。如果用户的重要文档的写了一半的临时副本放在未修改的良好副本旁边,用户也不会太生气。但如果没有留下好的副本,用户将不会满意。

答案2

该代码是合法的,但“天真”。问题正是发生碰撞时发生的情况

存在潜在风险,即在目录更新之前新数据不会分配空间,因此存在数据丢失的风险。

一个好的应用程序会调用fflush()fsync()确保数据刷新到磁盘。这些auto_da_alloc例程试图在内核中启发式地执行此操作。

https://bugzilla.kernel.org/show_bug.cgi?id=103111#c10解释了一些“陷阱”。

答案3

如果完全合法,它会起作用,但它不会做你想要的。

第二个是显而易见的。它会在保存新的之前销毁原始的。

第一个不太明显,看起来如果系统出现故障(例如断电),那么您将什么也不做(未启动);有2个文件:旧的和新的;或成功了。然而事实并非如此,除非你按照它说的去做fsync

他们使用“损坏”这个词来引起您的注意。它已损坏,因为您的用户将丢失数据。也许不是今天。也许不是明天,但很快,而且是余生。

这就是所谓的间歇性错误:这种错误可能会在出现症状之前停留数年。

如果您不关心用户的数据完整性,那么为什么要执行第一个示例。

相关内容