很抱歉在旧线程中添加了一些东西。并且做了这么长的帖子。
我只知道一种方法可以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,“锁定”))
- 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
. 巧妙地选择名称也可以在一定程度上强化算法以抵御攻击。
如果您将大量文件移动到某个地方,您当然可以重用该目录。
但是,我同意,这种算法显然是矫枉过正,并且缺少类似O_EXCL
on的东西。rename()