在Windows上可以重命名原子文件(带有覆盖)吗?


72

在POSIX系统上,rename(2)提供了原子重命名操作,包括覆盖目标文件(如果存在)以及是否允许权限。

有没有办法在Windows上获得相同的语义?我知道Vista和Server 2008上的MoveFileTransacted(),但是我需要它来支持Win2k及更高版本。

这里的关键词是原子的……解决方案一定不能以使操作处于不一致状态的任何方式失败。

我见过很多人说这在win32上是不可能的,但是我问你,真的吗?

如果可能,请提供可靠的引用。


@Adam Davis-如果您对阅读器程序和书写器都拥有控制权,则可以这样解决。Reader执行io.Directory(“ FileDone _ *。dat”)并选择最高的#代替*。Write创建名为“ FileWriting.dat”的文件,并将其重命名为“ FileDone_002.dat” .. 003、004等。这不仅解决了非原子删除/重命名的问题,而且仅单个重命名是原子的,并且,如果旧文件保持打开状态,则仍然可以进行更新。如果没有每次操作都重新打开,则阅读器可以根据计时器监视新文件。读者可以清理旧文件。
FastAl 2012年

Answers:


18

Win32不保证原子文件元数据操作。我会提供引用,但没有引用-没有书面或书面担保的事实就足够了。

您将必须编写自己的例程来支持此操作。不幸的是,但是您不能指望win32提供这种级别的服务-根本不是为它设计的。


6
我觉得这很难相信。这意味着即使我们使用的是可靠的系统(例如NTFS),停电也很容易损坏文件系统。
mafu 2012年

4
@mafutrct请记住,问题不在于破坏文件系统,而是关于确保重命名成功完成或根本不发生。文件系统不会被破坏,但是文件名可能不会保持原始状态或最终状态。NTFS是一个日记文件系统,因此不会(很容易)损坏,但是根据文件重命名的复杂性或操作顺序,有可能不会将其保留在原始状态或所需的最终状态。
亚当·戴维斯

5
这是有道理的,但也确实令人恐惧。以一个既不是原始的也不是最终的文件名结尾几乎是灾难的根源。特别是由于(iirc)POSIX标准已经需要原子元文件操作。
mafu 2012年

1
@mafutrct我怀疑使用简单的文件重命名不是问题,但是正如操作说明所建议的,存在更复杂的重命名操作,例如将文件重命名为已经存在的文件名。如果您有LOGFILE并且LOGBACKUP要定期将日志文件移至备份并启动新的日志文件,则可以将日志文件重命名为logbackup。操作系统必须先删除日志备份,然后重命名日志文件-可能会发生删除,但不会重命名,然后丢失两个日志文件,这在软件中解决起来并不是一个小问题。
亚当·戴维斯

10
@AdamDavis仍然很遗憾。原子覆盖是至关重要的功能。在文件系统上,这是知道具有旧版本或新版本的命名Blob的唯一方法。
Adrian Ratnapala 2014年

36

5
如果您阅读msdn.microsoft.com/en-us/library/aa365512(VS.85).aspx,您会发现这ReplaceFile是一个复杂的合并操作,没有迹象表明它是原子的。
加布

21
该MS研究论文的相关文章:“在UNIX下,可以保证named()原子地覆盖文件的旧版本。在Windows下,ReplaceFile()调用用于将一个文件原子替换为另一个文件。”
Matt Z

7
msdn.microsoft.com/zh-cn/library/windows/desktop/…表示可以原子地使用ReplaceFile:“许多处理“类文档”数据的应用程序倾向于将整个文档加载到内存中,对其进行操作,然后将其写回以保存更改。这里需要的原子性是更改是完全应用还是根本没有应用,因为不一致的状态会导致文件损坏。一种常见的方法是将文档写入新文件,然后使用新文件替换原始文件。一种方法是使用ReplaceFile API。”
内森

3
请特别注意为ReplaceFile记录的各种返回码,它们都对应于操作的部分(即非原子)完成程度不同。
2013年

37
微软实习生在这里。我遇到了这个问题,所以我问了一个从事NTFS工作的人。数据移动的部分是原子的,因此虽然在修改文件属性时可以中断数据,但数据本身的移动部分是原子的。
zneak

16

在Windows Vista和Windows Server 2008中,已添加了原子移动功能-MoveFileTransacted()

不幸的是,这对于旧版本的Windows没有帮助。

有关MSDN的有趣文章


6
隐藏在评论中:这不适用于网络共享
索林

@sorin:这个问题要求一个等效于POSIX的调用,该调用在网络共享上也不是原子的。
Ben Voigt

但是,问题中已经提到了此解决方案(及其对某些Windows版本的限制),因此将其作为答案来写是没有用的。
Ben Voigt

1
实际上,POSIC调用在NFS上原子的。
乔恩·瓦特

1
看来现在即将弃用。
法肯教授

11

从Windows 10 1607开始,NTFS确实支持原子取代重命名操作。为此,请NtSetInformationFile(..., FileRenameInformationEx, ...)指定FILE_RENAME_POSIX_SEMANTICS标志。

或等效地在Win32中调用SetFileInformationByHandle(..., FileRenameInfoEx, ...)并指定FILE_RENAME_FLAG_POSIX_SEMANTICS标志。


1
是否有一个原因,尽管DeleteFile现在使用POSIX删除,而ReplaceFile现在使用POSIX重命名(但仍然需要两个步骤),但是具有MOVEFILE_REPLACE_EXISTING的MoveFileEx仍然执行旧的重命名?
KK。

9

在Windows上,您仍然可以使用rename()调用,尽管我想您无法在不知道所使用文件系统的情况下做出所需的保证,例如,如果您使用的是FAT,则无法保证。

但是,您可以使用MoveFileEx并使用MOVEFILE_REPLACE_EXISTING和MOVEFILE_WRITE_THROUGH选项。后者在MSDN中具有以下描述:

设置此值可确保在函数返回之前将作为复制和删除操作执行的移动刷新到磁盘。刷新在复制操作结束时发生。

我知道这不一定与重命名操作相同,但是我认为这可能是您获得的最好保证-如果这样做是为了移动文件,则应该进行更简单的重命名。


6
据我所知,如果目标存在,并且在数据复制步骤期间发生I / O错误,则此“原始”目标将丢失,因此MoveFileEx不是您所需要的原子。这就是后来添加MoveFileTransacted的原因。
Martin Plante

3
MoveFileEx应该很好。它具有一个称为MOVEFILE_COPY_ALLOWED的标志,该标志表示:“如果要将文件移动到另一个卷,则该函数通过使用CopyFile和DeleteFile函数来模拟移动。” 因此,不要传递此标志,您应该拥有与POSIX重命名等效的东西,是吗?
andrewrk

2
如果Windows下已经存在新文件,重命名将失败。除了原子性之外,Windows版本在语义上甚至与Unix版本不兼容。
wcochran

6

MSDN文档避免明确说明哪些API是原子的,哪些不是原子的,但是Niall Douglas在他的Cppcon 2015演讲中指出,唯一的原子函数是

SetFileInformationByHandle

FILE_RENAME_INFO.ReplaceIfExists设置为true。从Windows Vista / 2008 Server开始可用。

Niall是一个高度复杂的LLFIO库的作者,并且是文件系统争用条件方面的专家,所以我相信,如果您正在编写原子性至关重要的算法,那么最好不要担心,并且即使在ReplaceFiles中没有任何内容的情况下也使用建议的功能描述指出它不是原子的。


3
实际上,取代重命名是唯一不能保证在NTFS上是原子的重命名类型。它可能非原子的原因是NTFS必须删除所有目标分配,并记录删除分配。如果被取代的目标非常大,则所有删除的分配将无法容纳在单个NTFS事务中,因此NTFS会将其拆分为多个事务。如果机器崩溃,您可能最终会陷入一个状态,即源和目标都仍然存在,但是目标已被部分截断(沿着事务边界)。
Craig Barkhouse

@CraigBarkhouse:非常有趣,您能否阐明“取代重命名”一词的含义?哪些API功能?
紫罗兰色长颈鹿

3
您必须了解的第一件事是文件系统操作本身就是原子的,而不是API。文件系统操作是否为原子操作取决于您正在谈论哪个文件系统以及哪个操作。通常,我一直以为您在谈论NTFS作为文件系统。在FAT上,根本没有原子,因此在FAT上没有更高级别的文件相关API是原子的。在NTFS上,如果API将自身限制为单个文件系统操作(为什么ReplaceFile不是原子的),并且该文件系统操作是原子的(为什么MoveFileEx不是原子的),则可以认为该API是原子的。
Craig Barkhouse

2
以MoveFileEx为例,它很复杂,因为根据调用方式的不同,它可能最终会进行1)简单的重命名;或2)取代的重命名(MOVEFILE_REPLACE_EXISTING事物);或3)复制并删除。第一种情况实际上在NTFS上是原子的。第二种情况是99.99999%的时间是原子,唯一的例外是当被取代的目标很大时,如我之前所述。第三种情况绝对不是原子的,因为“复制”是一连串的操作。因此,您必须先了解特定的情况,然后才能尝试回答是否是原子性的。
Craig Barkhouse

4
Linux根本没有不同。例如,在ext2文件系统上,几乎没有文件系统操作可被视为原子操作,因为(例如FAT)该文件系统不支持事务。因此,实际上没有Linux文件相关的API本身就是原子的。
Craig Barkhouse

3

相当多的答案,但不是我期望的。。。我了解(也许不正确)MoveFile可以是原子的,前提是适当的星形对齐,使用了标记并且源上的文件系统与目标相同。否则,该操作将退回到[Copy-> Delete] File。

鉴于; 我还了解到MoveFile-当它是原子时-只是设置文件信息,这也可以在这里完成:setfileinfobyhandle

有人做了一个名为“竞速文件系统”的演讲,对此进行了更深入的介绍。(大约2 / 3rds以下他们谈论原子重命名)


0

std :: rename,以C ++ 17 std :: filesystem :: rename开头。尚不明确如果目的地存在std::rename

如果new_filename存在,则行为是实现定义的。

但是,需要使用POSIX重命名来原子地替换现有文件

对于常规文件,此named()函数等效于ISO C标准定义的文件。它的包含将扩展该定义,以包括对目录的操作,并在新参数命名已存在的文件时指定行为。该规范要求函数的动作是原子的。

幸运的是,std::filesystem::rename要求它的行为类似于POSIX:

将由old_p标识的文件系统对象移动或重命名为new_p,就像通过POSIX重命名一样

但是,当我尝试调试时,似乎std::filesystem::rename由VS2019实现(截至2020年3月)只是调用MoveFileEx,在某些情况下这不是原子的。因此,可能在修复了其实现中的所有错误之后,我们将看到可移植的atomic std::filesystem::rename

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.