应用程序保持文件锁定时的 ReplaceFile 替代方案
Posted
技术标签:
【中文标题】应用程序保持文件锁定时的 ReplaceFile 替代方案【英文标题】:ReplaceFile alternative when application keeps file locked 【发布时间】:2015-09-30 07:33:35 【问题描述】:编辑器FooEdit(我们称之为)在保存时使用ReplaceFile() 以确保保存操作是有效的原子操作,并且如果出现任何问题,则保留磁盘上的原始文件。 (ReplaceFile() 的另一个重要好处是文件身份的连续性 - 创建日期和其他元数据。)
FooEdit 还保持打开文件的句柄,共享模式仅为 FILE_SHARE_READ,以便其他进程可以打开文件,但在 FooEdit 已打开文件进行写入时无法写入。
“显然”,当 ReplaceFile 操作发生时,这个句柄必须短暂关闭,这允许在 FooEdit 重新建立它的 FILE_SHARE_READ 锁定句柄之前另一个进程可能以写访问权限打开文件的竞争。
(如果 FooEdit 在调用 ReplaceFile() 之前没有关闭其 FILE_SHARE_READ 句柄,则 ReplaceFile() 会因共享冲突而失败。)
我想知道解决这场比赛的最简单方法是什么。选项似乎是找到另一种方法来锁定与 ReplaceFile() 兼容的文件(我不明白这是怎么可能的)或复制 ReplaceFile() 的所有行为,但使用现有文件句柄访问目标文件而不是路径。我对如何从用户代码以原子方式执行 ReplaceFile() 的所有操作有些困惑(无论如何,重新实现 ReplaceFile() 似乎是个坏主意)。
这一定是一个常见问题,所以我可能错过了一个明显的解决方案。
(这个问题似乎相关但没有答案:Transactionally write a file change on Windows。)
这是一个最小的可验证示例,显示了我正在努力实现的目标(更新时间 13:18 30/9/2015 UTC)。您必须提供三个文件名作为命令行参数,都在同一个卷上。第一个必须已经存在。
我总是从 ReplaceFile() 收到共享冲突。
#include <Windows.h>
#include <stdio.h>
#include <assert.h>
int main(int argc, char *argv[])
HANDLE lock;
HANDLE temp;
DWORD bytes;
if (argc != 4)
puts("First argument is the project file. Second argument is the temporary file.");
puts("The third argument is the backup file.");
/* Open and lock the project file to make sure no one else can modify it */
lock = CreateFile(argv[1], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, 0);
assert(lock != INVALID_HANDLE_VALUE);
/* Save to the temporary file. */
temp = CreateFile(argv[2], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, 0, 0);
assert(temp != INVALID_HANDLE_VALUE);
WriteFile(temp, "test", 4, &bytes, NULL);
/* Keep temp open so that another process can't modify the file. */
if (!ReplaceFile(argv[1], argv[2], argv[3], 0, NULL, NULL))
if (GetLastError() == ERROR_SHARING_VIOLATION)
puts("Sharing violation as I expected");
else
puts("Something went wrong");
else
puts("ReplaceFile worked - not what I expected");
/* If it worked the file referenced by temp would now be called argv[1]. */
CloseHandle(lock);
lock = temp;
return EXIT_SUCCESS;
感谢 Hans Passant,他在现在已删除的答案中提供了一些有价值的澄清想法。以下是我在跟进他的建议时发现的:
ReplaceFile() 似乎允许打开 lpReplacedFileName FILE_SHARE_READ | FILE_SHARE_DELETE,但 lpReplacementFileName 不能。 (而且这种行为似乎并不取决于是否提供了 lpBackupFileName。)因此,即使其他进程不允许 FILE_SHARE_WRITE,也完全有可能替换另一个进程打开的文件,这是汉斯的观点。
但 FooEdit 正在尝试确保没有其他进程可以首先使用 GENERIC_WRITE打开文件。为了确保在 FooEdit 中没有其他进程可以使用 GENERIC_WRITE 打开替换文件的竞争,FooEdit 似乎必须保持 持续 的 FILE_SHARE_READ | lpReplacementFileName 的 FILE_SHARE_DELETE 句柄,从而排除了 ReplaceFile() 的使用。
【问题讨论】:
“传统”安全保存涉及使用临时名称保存到新文件;写入成功后,删除旧文件并重命名新文件。 这就是 ReplaceFile 的用途,但至关重要的是 ReplaceFile 将删除和重命名作为原子操作,并且还保留了原始文件的元数据。 为什么需要它是原子的?元数据(至少时间戳)可以轻松保存。 @JonathanPotter 查看 ReplaceFile 的文档。它做了很多工作,并保证在未来版本的文件系统中添加的新元数据也被保留。我的部分问题是,是的,我可以复制 ReplaceFile,但这几乎可以肯定是一个非常糟糕的解决方案。 ReplaceFile 在文件系统级别无论如何都不是原子的,如果这是您所依赖的。 【参考方案1】:实际上,我认为可能存在不涉及事务的解决方案(尽管据我所知,事务仍然可用)。我自己没有尝试过,但我认为在 NTFS 上应该可以创建一个新的文件流(使用一个长的随机名称以确保没有冲突),写入你的数据,然后将该流重命名为你的流真的想写信给。
FILE_RENAME_INFORMATION
建议这应该是可能的,因为它谈到了重命名数据流。
但是,这仅适用于 NTFS。对于其他文件系统,我认为您别无选择。
【讨论】:
我知道你从哪里来。值得注意的是,这是一个内核 API - 如果不直接调用内核 API,似乎无法重命名备用数据流。 @IanGoldby:不,你绝对可以从用户模式调用它;它恰好在此链接的内核 API 文档中进行了描述。使用来自ntdll.dll
的NtSetInformationFile
。事实上,我只是通过FileTest
确认它可以工作。您需要将RootDirectory
设置为NULL
,并将FileName
设置为:YourNewStreamName
,省略文件名部分。但是,我会尽可能使用事务,因为它们在设计上更加健壮。
我知道您可以从用户模式调用 NT(我应该说,不是内核,抱歉)API,我过去自己也这样做过。但它通常被视为最后的手段,并没有受到微软的鼓励。无论如何,这是一个非常有趣的想法。我从未听说过 FileTest。感谢您的链接。
@IanGoldby:我的意思是,这是最后的手段。是否受到 MS 的鼓励并不重要,这种恐惧是人为的。这些 API 坚如磐石。随着时间的流逝,Microsoft 逐渐记录了其中的更多内容。我记得很多知名程序(例如 Chrome)都使用它们,而且我也不相信有人曾经被它们咬过。如果您认为远离他们对任何人都有好处,那您只是在自欺欺人。【参考方案2】:
我想知道解决这场比赛的最简单方法是什么。
没有简单的方法可以解决这场比赛。它是文件系统的固有部分,不是事务性的。 MS 在 Vista 中引入了事务文件 API,但现在强烈建议开发人员不要使用它,因为它可能会在未来的版本中被删除。
我对@987654321@ 有过一些经验,但我认为它带来的麻烦多于其价值。我的回忆是,在保留元数据的同时,创建了一个新文件。这样做的结果是保存在桌面上的文件的行为非常烦人。由于此类文件的位置保留,因此创建新文件会导致使用默认位置。所以你要保存一个文件,你把它拖到桌面上你想保存它的地方,然后当你再次保存文件时,它又移回了默认位置。
【讨论】:
您是否放弃了原子保存和保留文件身份的目标,还是找到了另一种方法? 是的,我刚刚放弃了。我决定这不是一个压倒一切的目标。丢失文件的风险是如此之低,丢失文件的后果通常不会那么严重,复杂性和不便让整个该死的事情看起来对我来说是一个巨大的损失。 @Mehrdad 你是对的。它仍然存在,但他们建议不要使用它。以上是关于应用程序保持文件锁定时的 ReplaceFile 替代方案的主要内容,如果未能解决你的问题,请参考以下文章