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

C++ C++/Win32:如何等待挂起的删除完成,c++,winapi,file-io,ntfs,C++,Winapi,File Io,Ntfs,已解决: 可行的解决方案: 对实际发生的情况的解释: 解释OpenFile为什么不通过“删除挂起”: 问题: 我们的软件在很大程度上是专用脚本语言的解释器引擎。该脚本语言能够创建文件、处理文件,然后删除文件。这些都是单独的操作,在这些操作之间没有文件句柄保持打开状态 (即,在文件创建过程中,创建一个句柄,用于写入,然后关闭。在文件处理部分,一个单独的文件句柄打开文件,从中读取,然后在EOF关闭。最后,delete使用::DeleteFile,它只使用文件名,根本不使用文件句柄) 最近,我们意

已解决:

  • 可行的解决方案:
  • 对实际发生的情况的解释:
  • 解释OpenFile为什么不通过“删除挂起”:
问题:

我们的软件在很大程度上是专用脚本语言的解释器引擎。该脚本语言能够创建文件、处理文件,然后删除文件。这些都是单独的操作,在这些操作之间没有文件句柄保持打开状态

(即,在文件创建过程中,创建一个句柄,用于写入,然后关闭。在文件处理部分,一个单独的文件句柄打开文件,从中读取,然后在EOF关闭。最后,delete使用::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()的过程中,我会遇到一个访问被拒绝(5)错误。查看sysinternal的ProcessMonitor,我可以看到潜在的问题是,当我再次尝试创建文件时,文件上有一个挂起的删除

问题:

  • 是否有办法等待删除完成
  • 有没有办法检测到文件正在等待删除
我们已经尝试了第一个选项,只需在HFILE上WaitForSingleObject()。但是在WaitForSingleObject执行之前,HFILE总是关闭的,因此WaitForSingleObject总是返回WAIT_FAILED。显然,试图等待关闭的句柄是行不通的

我可以等待文件所在文件夹的更改通知。然而,对于偶尔出现的问题来说,这似乎是一个开销非常大的难题(也就是说:在我的Windows7 x64 E6600 PC上的测试中,它通常在迭代12000+时失败——在其他机器上,它可能在迭代7、15、56或从不发生)

我一直无法识别任何CreateFile()参数,这些参数会显式地允许使用此以太网。无论CreateFile有什么参数,当文件被挂起删除时,打开一个文件进行任何访问都是不合适的

由于我在WindowsXP和x64 Windows7上都能看到这种行为,我非常确定这是微软“有意”的NTFS核心行为。因此,我需要一个解决方案,允许操作系统在我尝试继续之前完成删除,最好不要不必要地占用CPU周期,也不要查看该文件所在的文件夹(如果可能的话)

是的,此循环在写入失败或关闭泄漏失败时返回,但由于这是一个简单的控制台测试应用程序,应用程序本身退出,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;
}

首先重命名要删除的文件,然后将其删除

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

当然,如果您的分析是正确的,并且文件操作在某种程度上是异步的,这可能会导致您在重命名之前尝试删除文件的问题。但是你可以一直尝试在后台线程中删除


如果Hans是对的(我倾向于相信他的分析),那么移动可能没有真正的帮助,因为您可能无法实际重命名由另一个进程打开的文件。(但你可能会,我不知道这一点。)如果真是这样,我唯一能想到的办法就是“继续努力”。您必须等待几毫秒,然后重试。保留一个超时时间以在没有帮助时放弃。

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


文件标志\u DELETE\u ON\u CLOSE标志可能会为您购买一些东西。

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

这通常效果很好,除非您以高速率创建/写入/删除。删除将成功,但在关闭文件的最后一个句柄之前,该文件无法从文件系统中消失。例如,由搜索索引器控制的句柄。任何试图打开挂起的删除文件的程序都将被错误5击打


否则,这是多任务操作系统上的一个常见问题,您无法知道其他进程可能会将文件弄乱。您的使用模式似乎不寻常,请先回顾一下。一种解决方法是捕获错误,休眠并重试。或者使用SHFileOperation()将文件移动到回收站。

我认为这是由于文件系统设计不当造成的。我在使用通信端口时也遇到过同样的问题,打开/关闭它们

不幸的是,我认为最简单的解决办法是
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