C ++ / Win32:如何等待挂起删除完成?

时间:2010-09-21 20:10:04

标签: c++ winapi file-io ntfs

解决:
*可行的解决方案:@sbi
*解释真实情况:@Hans
*解释为什么OpenFile不通过“DELETE PENDING”:@ Benjamin

问题:
我们的软件在很大程度上是专有脚本语言的解释器引擎。该脚本语言能够创建文件,处理文件,然后删除文件。这些都是单独的操作,并且在这些操作之间没有保持打开文件句柄。 (即在文件创建过程中创建一个句柄,用于写入,然后关闭。在文件处理部分,一个单独的文件句柄打开文件,从中读取,并在EOF关闭。最后,删除使用:: DeleteFile只使用文件名,而不是文件句柄。

最近我们开始意识到特定的宏(脚本)有时无法在随后的某个随机时间创建文件(即它在“创建,处理,删除”的前100次迭代中成功,但是当它回到创建它一百次,第一次,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上使用W​​aitForSingleObject()。但是在WaitForSingleObject执行之前,HFILE始终处于关闭状态,因此WaitForSingleObject始终返回WAIT_FAILED。显然,试图等待关闭的句柄不起作用。

我可以等待该文件所在文件夹的更改通知。但是,这似乎只是偶尔会出现问题的极其开销密集的问题(也就是说:在我的Win7 x64 E6600 PC上的测试中)它通常在迭代12000+时失败 - 在其他机器上,它可以在迭代7或15或56或从不发生时发生。

我无法识别任何明确允许此以太的CreateFile()参数。无论CreateFile有什么参数,当文件正在等待删除时,打开任何访问的文件实际上是不行的。由于我可以在XP盒子和x64 Win7盒子上看到这种行为,我很确定这是微软的“按照预期”的核心NTFS行为。所以我需要一个允许操作系统在我尝试继续之前完成删除的解决方案,最好不要不必要地占用CPU周期,并且没有观察该文件所在文件夹的极端开销(如果可能的话)。

感谢您花时间阅读并发布回复。澄清问题欢迎!

[1]是的,这个循环在写入失败或无法关闭哪个漏洞时返回,但由于这是一个简单的控制台测试应用程序,应用程序本身退出,Windows保证所有句柄都被OS关闭当一个过程完成时所以这里没有泄漏。

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 个答案:

答案 0 :(得分:20)

Windows中还有其他进程需要该文件的一部分。搜索索引器是一个明显的候选者。或者病毒扫描程序。他们将打开文件进行完全共享,包括FILE_SHARE_DELETE,这样其他进程就不会受到打开文件的严重影响。

除非您以高比率创建/写入/删除,否则通常效果不错。删除将成功,但文件系统不能从文件系统中消失,直到它的最后一个句柄关闭。由搜索索引器持有的句柄。尝试打开该挂起删除文件的任何程序都将被错误5打耳机。

这是多任务操作系统上的一般问题,您无法知道其他进程可能想要弄乱您的文件。您的使用模式似乎不寻常,请先查看。解决方法是捕获错误,睡眠并再试一次。或者使用SHFileOperation()将文件移动到回收站。

答案 1 :(得分:15)

为什么不首先重命名要删除的文件,然后将其删除?

使用GetTempFileName()获取唯一名称,然后使用MoveFile()重命名该文件。然后删除重命名的文件。如果实际的删除确实是异步的,并且可能与创建相同的文件冲突(正如您的测试似乎表明的那样),这应该可以解决问题。

编辑: 当然,如果您的分析是正确的并且文件操作发生了某种程度的异步,这可能会导致您在重命名之前尝试删除文件的问题完成。但是你可以随时继续尝试在后台线程中删除。

编辑#2: 如果汉斯是对的(而且我倾向于相信他的分析),那么搬家可能并没有真正帮助,因为你可能无法做到实际上重命名由另一个进程打开的文件。 (但是你可能,我不知道这一点。)如果确实如此,我唯一能想到的另一种方法就是“继续努力”。你必须等待几毫秒然后重试。如果没有帮助,请暂停超时。

答案 2 :(得分:5)

愚蠢的建议 - 因为它很少失败,如果只是等待几毫秒失败并再次尝试?或者,如果延迟很重要,请切换到另一个文件名,以便稍后删除旧文件。

答案 3 :(得分:4)

  • 有没有办法检测文件是否有待删除?

使用GetFileInformationByHandleEx结构FILE_STANDARD_INFO功能。

但功能无法解决您的问题。 @ sbi的解决方案。

答案 4 :(得分:4)

这可能不是你的特殊问题,但我可能会建议你离开Process Monitor(Sysinternals)看看。

我遇到了完全相同的问题,发现Comodo Internet Securitycmdagent.exe)导致了这个问题。以前我有一台双核机器,但是当我升级到英特尔i7时,我的工作软件(Perfore软件jam.exe)不再有效,因为它有相同的模式(删除然后创建,但没有检查) 。调试问题后,我发现GetLastError()返回访问被拒绝,但进程监视器显示“删除暂挂”。这是跟踪:

10:39:10.1738151 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Read Attributes, Delete, Disposition: Open, Options: Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
10:39:10.1738581 AM jam.exe 5032    QueryAttributeTagFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Attributes: ANCI, ReparseTag: 0x0
10:39:10.1738830 AM jam.exe 5032    SetDispositionInformationFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Delete: True
10:39:10.1739216 AM jam.exe 5032    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1739438 AM jam.exe 5032    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1744837 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1788811 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1838276 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1888407 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1936323 AM System  4   FASTIO_ACQUIRE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS SyncType: SyncTypeOther
10:39:10.1936531 AM System  4   FASTIO_RELEASE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1936647 AM System  4   IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1939064 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1945733 AM cmdagent.exe    1188    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1946532 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1947020 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1948945 AM cfp.exe 1832    QueryOpen   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   FAST IO DISALLOWED  
10:39:10.1949781 AM cfp.exe 1832    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   NAME NOT FOUND  Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a
10:39:10.1989720 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0, OpenResult: Created

正如您所看到的,有一个删除请求,然后是jam.exe再次打开文件的几次尝试(它在循环中是fopen)。您可以看到cmdagent.exe文件打开时关闭其句柄,然后突然jam.exe能够打开文件。

当然,建议的解决方案是等待并再试一次,它运行得很好。

答案 5 :(得分:3)

由于你正在创建一个新文件,处理它,然后删除它,听起来你并不真正关心文件名是什么。如果确实如此,则应考虑始终创建临时文件。这样,每次完成整个过程,您都不必关注该文件尚未被删除。

答案 6 :(得分:2)

我在使用LoadLibrary(路径)时遇到了同样的问题,我无法删除路径中的文件。

解决方案是关闭手柄"或使用FreeLibrary(路径)方法。

注意:请阅读"备注"关于FreeLibrary()的MSDN

答案 7 :(得分:1)

如果CreateFile返回INVALID_HANDLE_VALUE,那么您应该确定GetLastError在您的特定情况下返回的内容(挂起删除),并仅基于该错误代码循环回CreateFile。

<强> 修改

FILE_FLAG_DELETE_ON_CLOSE标志会给你买什么吗?

答案 8 :(得分:1)

我可能迟到了派对,但在Vista / Win7上有DeleteFileTransacted,它使用事务删除文件,确保删除文件(刷新文件缓冲区等)。对于XP兼容性,这不是一个选项。

另一个想法是如何使用标志OF_CREATE的OpenFile,如果文件存在则将长度设置为零,如果不存在则创建它,然后在文件句柄上调用FlushFileBuffers以等待此操作(使文件为零长度) ) 去完成。完成后,文件的大小为0,然后只需调用DeleteFile。

您可以稍后测试该文件是否存在,或者它是否具有零长度,以同样的方式处理它。

答案 9 :(得分:1)

根据[1],您可以使用NtDeleteFile来避免DeleteFile的异步性质。 [1]还提供了有关DeleteFile如何工作的一些细节。

不幸的是,关于NtDeleteFile [2]的官方文档没有提及有关此问题的任何具体细节。

[1] http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FFile%2FNtDeleteFile.html [2] https://msdn.microsoft.com/en-us/library/windows/hardware/ff566435(v=vs.85).aspx

答案 10 :(得分:1)

sbi给出了最佳答案,但是出于完整性考虑,有些人可能还想了解Windows 10 RS1 / 1603现在提供的新方法。它涉及使用类FileDispositionInfoEx调用SetFileInformationByHandle API,并设置标志FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS。参见full answer by RbMm

答案 11 :(得分:1)

我刚遇到这个 exact 问题,并采取了两个步骤来解决它; 我停止使用C / C ++ stdlib api和::DeleteFile(..),并切换到:

  1. ::MoveFileEx(src,dest,MOVEFILE_WRITE_THROUGH);See: MOVEFILE_WRITE_THROUGH

  2. h = ::CreateFile(DELETE | SYNCHRONIZE,OPEN_EXISTING,FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_OPEN_REPARSE_POINT); ::CloseHandle(h);

上面是显示相关标志的伪调用,尤其要注意,用于实现删除的CreateFile调用没有共享。

它们在一起使我在重命名删除语义上有了更好的精确度。 它们现在正在我的代码中工作,并且由于其他重命名和/或延迟中的延迟(或共享),它们提高了精度和控制能力,使其不受其他线程/进程(监视文件系统的更改)对文件的干预行为删除API。如果没有该控件,则设置为在最后一个内核句柄关闭时将其删除的文件实际上可能无法打开,直到重新引导系统为止,并且您可能不知道。

希望这些反馈摘要可能对其他人有用。

附录:我碰巧使用硬链接来完成部分工作。事实证明,尽管可以在打开的文件上创建硬链接,但是在关闭该NTFS文件的任何基础数据流的任何句柄的所有句柄之前,都不能删除其中的任何一个。这很奇怪,因为:

这会导致您认为只有 last 硬链接应该是不可删除的,而内核具有一个或多个引用硬链接的NTFS文件的MFT-Entry / ATTR的打开文件句柄。 无论如何,还有另一件事要知道。

答案 12 :(得分:-2)

我认为这仅仅是因为文件系统设计不佳。当我使用通信端口打开/关闭它时,我看到了同样的问题。

不幸的是,我认为最简单的解决方案是,如果获得INVALID_HANDLE_VALUE,只需重试多次创建文件。 GetLastError()也可能为您提供更好的方法来检测此特定INVALID_HANDLE_VALUE

我希望重叠I / O,但CloseHandle()DeleteFile()不处理重叠操作:(