很抱歉在旧线程中添加了一些内容。并且写了这么长的帖子。
我只知道一种方法可以在没有锁定的情况下实现完全免费的竞争条件rename(),这几乎可以在任何文件系统上工作,即使是在服务器间歇性重启和客户端时间扭曲到位的 NFS 上也是如此。
以下配方是无竞争条件的,因为在任何情况下数据都不会丢失。它也不需要锁,并且可以由不想合作的客户端执行,除非它们都使用相同的算法。
从某种意义上说,如果某些东西严重损坏,一切都会保持干净整洁的状态,这不是无竞争条件。它也有一段很短的时间,源头和目的地都不在他们的位置,但是源头仍然以另一个名字存在。并且对于攻击者试图造成伤害的情况(rename() 是罪魁祸首,去看看),它并没有得到强化。
S是源,D是目的地,P(x)是dirname(x),C(x,y)是x/y路径拼接
- 检查目标不存在。只是为了确保我们不会徒劳地执行后续步骤。
- 创建一个可能唯一的名称 T := C(P(D),random)
- mkdir(T),如果失败则循环到上一步
- open(C(T,"lock"),O_EXCL),如果失败 rmdir(T) 忽略错误并循环到上一步
- 重命名(S,C(T,"tmp"))
- 链接(C(T,"tmp"),D)
- 取消链接(C(T,"tmp"))
- 取消链接(C(T,"lock"))
- rmdir(T)
算法safe_rename(S,D)解释:
问题是我们要确保不存在竞争条件,无论是在源上还是在目的地上。假设在每个步骤之间(几乎)任何事情都可能发生,但所有其他进程在进行无竞争条件重命名时都遵循完全相同的算法。这包括临时目录 T 永远不会被触及,除非在确保(这是一个手动过程)使用该目录的进程已经死亡并且无法复活(例如在恢复后继续 VM 休眠)之后。
要正确执行rename(),我们需要一个地方躲起来。因此,我们构建了一个目录,以确保没有其他人(遵循相同算法的人)会意外使用它。
但是,mkdir() 不能保证在 NFS 上是原子的。因此,我们需要确保我们有一些保证,我们在目录中是单独的。这是锁定文件上的O_EXCL。这 - 严格来说 - 不是锁定,它是一个信号量。
除了这种罕见的情况,mkdir() 通常是原子的。我们还可以为目录创建使用一些加密安全的随机名称,添加一些 GUID、主机名和 PID,以确保其他人不太可能偶然选择相同的名称。然而,为了证明算法是正确的,我们需要这个名为 lock 的文件。
现在我们有一个大部分为空的目录,我们可以安全地rename() 那里的源。这样可以确保在我们unlink() 之前没有其他人更改源。 (嗯,内容可以改变,这不是问题。)
现在可以应用link() 技巧来确保我们不会覆盖目标。
之后,unlink() 可以在剩余的源上免费完成竞争条件。剩下的就是清理了。
只剩下一个问题了:
如果link() 失败,我们已经移动了源。为了进行适当的清理,我们需要将其移回。这可以通过调用safe_rename(C(T,"tmp"),S) 来完成。如果这也失败了,我们所能做的就是尽可能多地清理(unlink(C(T,"lock"))、rmdir(T))并留下碎片以供管理员手动清理。
最后的笔记:
为了帮助清理碎片情况,您可以使用比tmp 更好的文件名。巧妙地选择名称也可以在一定程度上强化算法以抵御攻击。
如果您将大量文件移动到某个地方,您当然可以重用该目录。
但是,我同意,这种算法显然是矫枉过正,并且缺少像 rename() 上的 O_EXCL 这样的东西。