调用 rename() 而不覆盖意外数据且不覆盖意外数据

Calling rename() without overwriting unexpected data and without overwriting with unexpected data

假设您有一个名为 foo 的文件,其中包含一些确定的字节序列 X,并且您想自动将其替换为一个名为 bar 且包含字节序列的文件Y。这通常通过 rename() 系统调用完成——在本例中,通过调用 rename("bar", "foo")。但是,您希望遵守以下两个约束条件:

  1. 仅当名为 bar 的文件确实包含数据 Y 时才应执行替换,否则应该失败。
  2. 仅当名为 foo 的文件确实包含数据 X 时才应执行替换,否则应该会失败。

如何正确地做到这一点?

为了防止 foobar 在我们调用 rename() 之前被编辑,我们可以用 fnctl 或等价物锁定它们。但是锁只能帮助防止修改文件数据,它们对目录条目没有影响,因此,当 rename() 发挥作用时,数据 foobar 引用可能不一样。

针对上述两个约束的数据丢失场景的两个示例:

    • 我们已锁定名为 bar 的文件并确保它包含数据 Y
    • 在我们用 bar 替换 foo 之前,一些程序将 bar 替换为以前名为 qux 的文件,该文件保存数据 Z.
    • 我们用bar替换foo
    • 现在,我们希望包含 bar 数据的文件 foo 包含了 qux 的数据。 foobar的数据都丢失了。
    • 我们已锁定名为 foo 的文件并确保它包含数据 X
    • 在我们用 bar 替换 foo 之前,一些程序将 foo 替换为以前名为 qux 的文件,该文件保存数据 Z.
    • 我们用bar替换foo
    • 现在文件foo确实包含了bar的数据,但是文件qux的数据在这个过程中丢失了。

根据您的评论:

It's for a deduplication tool. I want to replace foo with a link to another file that holds the same data as foo, without losing data in the process

我认为您遇到了 XY 问题。您不能使 rename 操作相对于文件的内容是原子的。但是您的目标只是避免在重复数据删除过程中文件意外更改时造成数据丢失。这适用于其他方法,例如保留到旧文件的硬链接并将其恢复(恢复到原始名称或特殊恢复区)after 执行重命名然后比较以检测它已经改变了。

然而,有很多基本问题仍然使这个问题成为问题,至少从以下方面开始:

  • 一个进程可能有一个打开的句柄用于写入旧文件,但尚未对其进行修改,并且可能会在您对它进行重复数据删除后修改并关闭它。在那种情况下,关闭操作将孤立它并且数据将丢失。

  • 任何打算修改正在删除重复的文件之一的进程将同时修改所有重复项,一旦它们 hard-linked,可能与您的预期相反。

如果您的目标是删除重复数据以保存 space,但保留语义以允许修改,那么您确实需要一个文件系统来删除具有 copy-on-write 语义的 fs 块,而不是硬链接。另一方面,如果你想要硬链接,你应该在 期间和 重复数据删除操作之后将整个树视为本质上 read-only。