已解决:
问题:
我们的软件在很大程度上是一个解释器专有脚本语言的引擎。该脚本语言能够创建文件、处理文件,然后删除文件。这些都是单独的操作,并且在这些操作之间没有文件句柄保持打开状态。
(即在文件创建期间,创建一个句柄,用于写入,然后关闭。在文件处理部分期间,一个单独的文件句柄打开文件,从中读取文件,并在 EOF 处关闭。最后,删除使用::DeleteFile,它只使用文件名,根本不使用文件句柄)。
最近我们开始意识到,特定的宏(脚本)有时无法在随后的某个随机时间创建文件(即,它在“创建、处理、删除”的前一百次迭代期间成功,但是当它出现时)回到创建它一百零一次,Windows 回复“访问被拒绝”)。
更深入地研究这个问题,我编写了一个非常简单的程序,它循环如下所示:
while (true) {
HANDLE hFile = CreateFileA(pszFilename, FILE_ALL_ACCESS, FILE_SHARE_READ,
NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return OpenFailed;
const DWORD dwWrite = strlen(pszFilename);
DWORD dwWritten;
if (!WriteFile(hFile, pszFilename, dwWrite, &dwWritten, NULL) || dwWritten != dwWrite)
return WriteFailed;
if (!CloseHandle(hFile))
return CloseFailed;
if (!DeleteFileA(pszFilename))
return DeleteFailed;
}
正如您所看到的,这是直接针对 Win32 API 的,并且非常简单。我创建一个文件,写入它,关闭句柄,删除它,冲洗,重复...
但是在该过程中的某个地方,我会在 CreateFile() 调用期间收到 Access Denied (5) 错误。查看 sysinternal 的 ProcessMonitor,我可以看到根本问题是当我尝试再次创建文件时,文件上有一个待删除的文件。
问题:
- 有没有办法等待删除完成?
- 有没有办法检测文件是否正在等待删除?
我们尝试了第一个选项,即在 HFILE 上简单地使用 WaitForSingleObject()。但 HFILE 始终在 WaitForSingleObject 执行之前关闭,因此 WaitForSingleObject 始终返回 WAIT_FAILED。显然,尝试等待关闭句柄是行不通的。
我可以等待文件所在文件夹的更改通知。但是,对于偶尔出现问题的情况来说,这似乎是一种极其耗费资源的拼凑(也就是说:在我的 Windows 7 x64 E6600 PC 上进行的测试中,它通常在迭代 12000+ 时失败——在其他机器上,它可能会在迭代 7、15、56 时发生,或者永远不会发生)。
我无法辨别任何明确允许这种以太的 CreateFile() 参数。无论 CreateFile 有什么参数,当文件待删除时打开文件进行任何访问确实是不行的。
由于我可以在 Windows XP 机器和 x64 Windows 7 机器上看到此行为,因此我非常确定这是 Microsoft“按照预期”的核心 NTFS 行为。因此,我需要一个解决方案,允许操作系统在我尝试继续操作之前完成删除,最好不要不必要地占用 CPU 周期,并且不会产生监视该文件所在文件夹的巨大开销(如果可能)。
1 是,此循环在写入失败或关闭泄漏失败时返回,但由于这是一个简单的控制台测试应用程序,因此应用程序本身会退出,并且 Windows 保证在进程完成时操作系统会关闭所有句柄。所以这里不存在泄漏。
bool DeleteFileNowA(const char * pszFilename)
{
// Determine the path in which to store the temp filename
char szPath[MAX_PATH];
strcpy(szPath, pszFilename);
PathRemoveFileSpecA(szPath);
// Generate a guaranteed to be unique temporary filename to house the pending delete
char szTempName[MAX_PATH];
if (!GetTempFileNameA(szPath, ".xX", 0, szTempName))
return false;
// Move the real file to the dummy filename
if (!MoveFileExA(pszFilename, szTempName, MOVEFILE_REPLACE_EXISTING))
return false;
// Queue the deletion (the OS will delete it when all handles (ours or other processes) close)
if (!DeleteFileA(szTempName))
return false;
return true;
}
Solved:
The Problem:
Our software is in large part an interpreter engine for a proprietary scripting language. That scripting language has the ability to create a file, process it, and then delete the file. These are all separate operations, and no file handles are kept open in between these operations.
(I.e. during the file creation, a handle is created, used for writing, then closed. During the file processing portion, a separate file handle opens the file, reads from it, and is closed at EOF. And finally, delete uses ::DeleteFile which only has use of a filename, not a file handle at all).
Recently we've come to realize that a particular macro (script) fails sometimes to be able to create the file at some random subsequent time (i.e. it succeeds during the first hundred iterations of "create, process, delete", but when it comes back to creating it a hundred and first time, Windows replies "Access Denied").
Looking deeper into the issue, I have written a very simple program that loops over something like this:
while (true) {
HANDLE hFile = CreateFileA(pszFilename, FILE_ALL_ACCESS, FILE_SHARE_READ,
NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return OpenFailed;
const DWORD dwWrite = strlen(pszFilename);
DWORD dwWritten;
if (!WriteFile(hFile, pszFilename, dwWrite, &dwWritten, NULL) || dwWritten != dwWrite)
return WriteFailed;
if (!CloseHandle(hFile))
return CloseFailed;
if (!DeleteFileA(pszFilename))
return DeleteFailed;
}
As you can see, this is direct to the Win32 API and is pretty darn simple. I create a file, write to it, close the handle, delete it, rinse, repeat...
But somewhere along the line, I'll get an Access Denied (5) error during the CreateFile() call. Looking at sysinternal's ProcessMonitor, I can see that the underlying issue is that there is a pending delete on the file while I'm trying to create it again.
Questions:
- Is there a way to wait for the delete to complete?
- Is there a way to detect that a file is pending deletion?
We have tried the first option, by simply WaitForSingleObject() on the HFILE. But the HFILE is always closed before the WaitForSingleObject executes, and so WaitForSingleObject always returns WAIT_FAILED. Clearly, trying to wait for the closed handle doesn't work.
I could wait on a change notification for the folder that the file exists in. However, that seems like an extremely overhead-intensive kludge to what is a problem only occasionally (to wit: in my tests on my Windows 7 x64 E6600 PC it typically fails on iteration 12000+ -- on other machines, it can happen as soon as iteration 7 or 15 or 56 or never).
I have been unable to discern any CreateFile() arguments that would explicitly allow for this ether. No matter what arguments CreateFile has, it really is not okay with opening a file for any access when the file is pending deletion.
And since I can see this behavior on both an Windows XP box and on an x64 Windows 7 box, I am quite certain that this is core NTFS behavior "as intended" by Microsoft. So I need a solution that allows the OS to complete the delete before I attempt to proceed, preferably without tying up CPU cycles needlessly, and without the extreme overhead of watching the folder that this file is in (if possible).
1 Yes, this loop returns on a failure to write or a failure to close which leaks, but since this is a simple console test application, the application itself exits, and Windows guarantees that all handles are closed by the OS when a process completes. So no leaks exist here.
bool DeleteFileNowA(const char * pszFilename)
{
// Determine the path in which to store the temp filename
char szPath[MAX_PATH];
strcpy(szPath, pszFilename);
PathRemoveFileSpecA(szPath);
// Generate a guaranteed to be unique temporary filename to house the pending delete
char szTempName[MAX_PATH];
if (!GetTempFileNameA(szPath, ".xX", 0, szTempName))
return false;
// Move the real file to the dummy filename
if (!MoveFileExA(pszFilename, szTempName, MOVEFILE_REPLACE_EXISTING))
return false;
// Queue the deletion (the OS will delete it when all handles (ours or other processes) close)
if (!DeleteFileA(szTempName))
return false;
return true;
}
发布评论
评论(13)
Windows 中还有其他进程需要该文件的一部分。搜索索引器是一个明显的候选者。或者病毒扫描程序。他们将打开文件以进行完全共享,包括 FILE_SHARE_DELETE,以便其他进程不会受到他们打开文件的严重影响。
这通常效果很好,除非您以很高的速度创建/写入/删除。删除将成功,但文件无法从文件系统中消失,直到该文件的最后一个句柄关闭为止。例如,搜索索引器所持有的句柄。任何尝试打开该待删除文件的程序都将被错误 5 处理。
这在多任务操作系统上是一个常见问题,您无法知道哪些其他进程可能想要弄乱您的文件。您的使用模式似乎不寻常,请先检查一下。解决方法是捕获错误、睡眠并重试。或者使用 SHFileOperation() 将文件移动到回收站。
There are other processes in Windows that want a piece of that file. The search indexer is an obvious candidate. Or a virus scanner. They'll open the file for full sharing, including FILE_SHARE_DELETE, so that other processes aren't heavily affected by them opening the file.
That usually works out well, unless you create/write/delete at a high rate. The delete will succeed but the file cannot disappear from the file system until the last handle to it got closed. The handle held by, say, the search indexer. Any program that tries to open that pending-delete file will be slapped by error 5.
This is otherwise a generic problem on a multitasking operating system, you cannot know what other process might want to mess with your files. Your usage pattern seems unusual, review that first. A workaround would be to catch the error, sleep and try again. Or moving the file into the recycle bin with SHFileOperation().
首先将要删除的文件重命名,然后将其删除。
使用
GetTempFileName()
获取唯一名称,然后使用MoveFile()
重命名文件。然后删除重命名的文件。如果实际删除确实是异步的,并且可能与同一文件的创建发生冲突(正如您的测试似乎表明的那样),那么这应该可以解决问题。当然,如果您的分析是正确的并且文件操作在某种程度上异步发生,则这可能会导致您尝试在重命名完成之前删除文件的问题。但是你可以一直尝试在后台线程中删除。
如果汉斯是对的(我倾向于相信他的分析),那么移动可能并没有真正的帮助,因为您可能无法实际重命名由另一个进程打开的文件。 (但是你可能会,我不知道这一点。)如果确实如此,我能想到的唯一其他方法就是“继续尝试”。您必须等待几毫秒然后重试。当这没有帮助时,请保持暂停以放弃。
First rename the file to be deleted, and then delete it.
Use
GetTempFileName()
to obtain a unique name, and then useMoveFile()
to rename the file. Then delete the renamed file. If the actual deletion is indeed asynchronous and might conflict with the creation of the same file (as your tests seems to indicate), this should solve the problem.Of course, if your analysis is right and file operations happen somewhat asynchronous, this might introduce the problem that you attempt to delete the file before the renaming is done. But then you could always keep trying to delete in a background thread.
If Hans is right (and I'm inclined to believe his analysis), then moving might not really help, because you might not be able to actually rename a file that's open by another process. (But then you might, I don't know this.) If that's indeed the case, the only other way I can come up with is "keep trying". You would have to wait for a few milliseconds and retry. Keep a timeout to give up when this doesn't help.
愚蠢的建议 - 由于它很少失败,因此只需在失败时等待几毫秒,然后重试即可。
或者,如果延迟很重要,请切换到另一个文件名,让旧文件稍后删除。
Silly suggestion - since it fails so infrequently, simply wait some milliseconds on failure and try again.
Or, if latency is important, switch to another file name, leaving the old file to be deleted later.
这可能不是您的特定问题,但有可能,所以我建议您使用进程监视器( Sysinternals)并查看。
我遇到了完全相同的问题,并发现 Comodo Internet Security (
cmdagent.exe< /code>) 导致了这个问题。以前我有一台双核机器,但是当我升级到 Intel i7 时,我的工作软件(Perfore 软件的
jam.exe
)突然不再工作,因为它具有相同的模式(删除然后创建,但没有检查)。调试问题后,我发现 GetLastError() 返回访问被拒绝,但进程监视器显示“删除挂起”。跟踪如下:如您所见,有一个删除请求,随后
jam.exe
多次尝试再次打开该文件(它是一个循环中的fopen
)。您可以看到,cmdagent.exe
可能在关闭其句柄时打开了该文件,然后突然jam.exe
现在能够打开该文件。当然,建议的解决方案是等待并重试,并且效果很好。
This is maybe not your particular issue, but it's possible so I suggest you get out Process Monitor (Sysinternals) and see.
I had exactly the same problem and discovered that Comodo Internet Security (
cmdagent.exe
) was contributing to the problem. Previously I had a dual-core machine, but when I upgraded to an Intel i7 suddenly my working software (jam.exe
by Perfore software) no longer worked because it had the same pattern (a delete then create, but no check). After debugging the problem I found GetLastError() was returning access denied, but Process Monitor reveals a 'delete pending'. Here is the trace:As you can see, there is a request to delete followed by several attempts to open the file again by
jam.exe
(it's anfopen
in a loop). You can seecmdagent.exe
presumably had the file open as it closes its handle and then suddenlyjam.exe
is able to now open the file.Of course, the suggested solution to wait and try again, and it works just fine.
将 GetFileInformationByHandleEx 函数与 < href="http://msdn.microsoft.com/en-us/library/aa364401(v=VS.85).aspx" rel="nofollow noreferrer">FILE_STANDARD_INFO 结构。
但这个功能并不能解决你的问题。 sbi的解决方案两者都不。
Use the GetFileInformationByHandleEx function with the FILE_STANDARD_INFO structure.
But the function can't solve your problem. sbi's solution neither.
由于您正在创建一个新文件,处理它,然后删除它,听起来您并不真正关心文件名是什么。如果确实如此,您应该考虑始终创建一个临时文件。这样,每次完成该过程时,您都不必担心该文件尚未被删除。
Since you're creating a new file, processing it, then deleting it, it sounds like you don't really care about what the file name is. If that's truly the case, you should consider always creating a temporary file. That way, each time through the process, you don't have to care that the file didn't yet get deleted.
实际上,我在使用 LoadLibrary(path) 时遇到了同样的问题。我无法删除路径中的文件。
解决方案是“关闭句柄”或使用 FreeLibrary(path) 方法。
注意:请阅读 MSDN 关于 FreeLibrary() 的内容。
I actually had the same issue while using the LoadLibrary(path). I couldn't delete the file in path.
The solution was to "close the handle" or use the FreeLibrary(path) method.
NOTE: Please read the "Remarks" on MSDN regarding the FreeLibrary().
在 Windows Vista/Windows 7 上有 DeleteFileTransacted 使用事务删除文件,确保文件被删除(刷新文件缓冲区等)。但对于 Windows XP 兼容性,这不是一个选项。
另一个想法是使用 OpenFile() 和 OF_CREATE 标志,如果文件存在则将长度设置为零,如果不存在则创建它,然后在文件句柄上调用 FlushFileBuffers 来等待此操作(使得文件长度为零)来完成。完成后,文件大小为 0,然后只需调用 DeleteFile。
您可以稍后测试该文件是否存在,或者它的长度是否为零,以同样的方式处理它。
On Windows Vista/Windows 7 there is DeleteFileTransacted which deletes a file using transactions which ensures they are deleted (flushes file buffers, etc.). For Windows XP compatibility this is not an option though.
Another idea how this might be done is use OpenFile() with the flag OF_CREATE which sets the length to zero if the file exists or creates it if it doesn't and then to call FlushFileBuffers on the file handle to wait for this operation (making the file zero length) to complete. On completion, the file is of size 0 and then simply call DeleteFile.
You can later test if the file exists or if it has zero-length to treat it the same way.
根据[1],您可以使用
NtDeleteFile
来避免DeleteFile的异步特性。 [1] 还提供了有关删除文件如何工作的一些详细信息。不幸的是,
NtDeleteFile
[2] 的官方文档没有提及此问题的任何具体细节。[1] NTDLL 未记录的函数
[2] ZwDeleteFile函数
According to [1], you could use
NtDeleteFile
to avoid the asynchronous nature of DeleteFile. Also [1] gives some details on how DeleteFile works.Unfortunately the official documentation on
NtDeleteFile
[2] doesn't mention any particular details on this issue.[1] Undocumented functions of NTDLL
[2] ZwDeleteFile function
最佳答案是给出sbi,但为了完整起见,有些人可能还想了解 Windows 10 RS1/1603 现在提供的一种新方法。
它涉及使用 FileDispositionInfoEx 类调用 SetFileInformationByHandle API,并设置标志 FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS。请参阅 RbMm 的完整答案。
The best answer was given by sbi, but in the interest of completeness, some people might also want to know about a new way now available from Windows 10 RS1/1603.
It involves calling the SetFileInformationByHandle API with class FileDispositionInfoEx, and setting flags
FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS
. See the full answer by RbMm.如果 CreateFile 返回 INVALID_HANDLE_VALUE,那么您应该确定 GetLastError 在您的特定情况下返回什么(待删除),并仅根据该错误代码循环回 CreateFile。
FILE_FLAG_DELETE_ON_CLOSE 标志可能会给你带来一些东西。
If CreateFile returns INVALID_HANDLE_VALUE then you should determine what GetLastError returns in your particular situation (pending delete) and loop back to CreateFile based on that error code only.
The FILE_FLAG_DELETE_ON_CLOSE flag might buy you something.
我刚刚遇到了这个确切问题,并采取了两个步骤来解决它; 我停止使用 C/C++ stdlib api 和
::DeleteFile(..)
,并切换到:::MoveFileEx(src,dest,MOVEFILE_WRITE_THROUGH );
。 请参阅:MOVEFILE_WRITE_THROUGH< /p>h = ::CreateFile(DELETE | SYNCHRONIZE,OPEN_EXISTING,FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_OPEN_REPARSE_POINT); ::CloseHandle(h);
以上是显示相关标志的伪调用,特别注意用于实现删除的 CreateFile 调用没有共享。
它们共同为我提供了重命名和删除语义的更精确度。 他们现在正在我的代码中工作,并且提高了其他线程/进程(观察文件系统的变化)由于其他重命名和/或中的延迟[或共享]而在文件上插入操作的精度和控制删除 API。如果没有这种控制,设置为在最后一个内核句柄关闭时删除的文件实际上可能会在系统重新启动之前一直处于打开状态,而您可能不知道。
希望这些反馈片段可能对其他人有用。
附录:我碰巧在我所做的部分工作中使用了硬链接。事实证明,虽然您可以在打开的文件上创建硬链接,但在关闭该 NTFS 文件的任何基础数据流的所有句柄之前,您无法删除其中的任何一个。这很奇怪,因为:
这会让你认为只有最后硬链接应该是不可删除的,而内核有一个或多个引用硬链接的打开文件句柄NTFS 文件的 MFT 条目/ATTR。 无论如何,还有一件事需要知道。
I just had this exact issue and took TWO steps to address it; I stopped using C/C++ stdlib apis and
::DeleteFile(..)
, and switched to:::MoveFileEx(src,dest,MOVEFILE_WRITE_THROUGH);
. See: MOVEFILE_WRITE_THROUGHh = ::CreateFile(DELETE | SYNCHRONIZE,OPEN_EXISTING,FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_OPEN_REPARSE_POINT); ::CloseHandle(h);
The above are pseudo calls showing the relevant flags, specifically note that there is NO sharing on the CreateFile call used to achieve delete.
Together they gave me better precision on the rename and delete semantics. They are working in my code now and have improved the precision and control from other threads/processes (watching the file-system for changes) interjecting actions on the file due to latencies [or sharing] in the other rename and/or delete APIs. Without that control, a file set to delete when its last kernel-handle was closed might actually languish open until the system was rebooted, and you might not know.
Hopefully those feedback snippets might prove useful to others.
Addendum: I happen to use hardlinks for a portion of the work I do. It turns out that although you can create hardlinks on a file that is OPEN, you cannot delete ANY of them until all handles to ANY of the underlying data-stream(s) to that NTFS file are closed. That is weird since:
Which would lead you to think that only the last hardlink should be non-deletable while the kernel has one or more open-file handles referring to the hardlinked NTFS File's MFT-Entry/ATTRs. anyway, just another thing to know.
我认为这只是文件系统设计不当造成的。当我使用通信端口打开/关闭它们时,我也遇到了同样的问题。
不幸的是,我认为最简单的解决方案是,如果您收到
INVALID_HANDLE_VALUE
,则多次重试创建文件。GetLastError()
还可能为您提供更好的方法来检测此特定INVALID_HANDLE_VALUE
。我更喜欢重叠 I/O,但它们的
CloseHandle()
和DeleteFile()
不处理重叠操作:(I think this is just by poor design in the file system. I have seen the same problem when I worked with communication ports, opening/closing them.
Unfortunately I think the simplest solution would be to just retry to create the file a number of times if you get an
INVALID_HANDLE_VALUE
.GetLastError()
might also give you a better way of detecting this particularINVALID_HANDLE_VALUE
.I would have preferred overlapped I/O, but their
CloseHandle()
andDeleteFile()
don't handle overlapped operations :(