在Linux上原子地交换两个文件的内容

10
我有两个文件,AB,它们分别有自己的内容。
我想要交换这两个文件,所以A会变成BB会变成A。但是我希望在操作期间保证没有其他进程会发现这两个文件处于不一致状态,也不会发现这些文件中的任何一个文件短时间内消失。因此,作为一个附加操作,如果操作期间出现任何问题,我也希望保证不会更改任何内容(类似于事务)。
在OS X上有一个exchangedata()函数,所以我猜我正在寻找Linux的等效方法,或者至少是进行原子文件交换的等效方法。

@Frédéric Hamidi - 你发布的问题回答根本不符合我的标准,所以我猜它不是重复的。 - antonone
我认为那里的答案会适合你,但是我猜想如果出现问题,你不想留下临时文件吧? - Frédéric Hamidi
2
是的,我希望能够保证不会删除原始文件。如果第一次移动成功,我将有效地删除该文件。然后,如果第二次移动失败,我将不得不回滚。如果回滚也失败了,我将因破坏客户系统而陷入麻烦;) - antonone
2个回答

18
你可以使用(相对较新的)Linux系统调用renameat2
这是定义:
int renameat2(int olddir, const char *oldname, 
      int newdir, const char *newname, unsigned int flags);

如有需要,您可以在内核的Git仓库中找到其源代码:https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/namei.c?id=7bb91e06730140a693611e51a4a9636152448bd3#n4474

它基本上与renameat相同,但如果传递RENAME_EXCHANGE标志,它将交换两个文件而不是将一个重命名为另一个。

该操作是原子性的。


这个系统调用的内核文档可以在这里找到。 - senden9
顺便提一下,根据SourceDiggerRENAME_EXCHANGE在Linux内核v3.15中被添加。 - Wazzaps

2

这取决于你所说的“不一致状态”的含义。如果在两个文件相同的一段时间内是可以接受的,那么你可以简单地执行以下操作:

ln A C
ln B D
ln -f D A  
# now, A and B have the same content
ln -f C B

这还取决于您希望已经打开文件的进程的行为。请记住,路径并不是文件,而只是指向文件的链接,因此如果进程1通过路径“A”打开文件,然后交换名称A和B,进程1仍将打开由名称A引用的文件。


1
欢迎解释下投票反对的原因。这个回答解决了所提出的问题,只是在处理问题中存在一些歧义。 - William Pursell
5
我做了一些实验,似乎使用 -f 标志无法符合“原子性”约束条件,因为 ln 工具在使用 link 函数创建链接之前会使用 unlink 删除目标路径。因此,ln -f 并不真正是原子性的,存在一个(非常短暂,但仍然)文件不存在的时间窗口。考虑到这一点,简单地通过重命名使用临时文件名似乎可以以类似的方式完成相同的事情。 - antonone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接