代码之家  ›  专栏  ›  技术社区  ›  Mordachai

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

  •  31
  • Mordachai  · 技术社区  · 16 年前

    解决了的:

    问题是:

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

    • 有没有办法等待删除完成?

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

    我一直无法识别任何CreateFile()参数,这些参数将显式地允许此操作。不管CreateFile有什么参数,打开一个 任何

    由于我在WindowsXP和x64Windows7上都能看到这种行为,我很确定这是微软的核心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;
    }
    
    11 回复  |  直到 7 年前
        1
  •  18
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

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

    使用 GetTempFileName() 获取唯一的名称,然后使用 MoveFile()

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

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

        2
  •  22
  •   Hans Passant    8 年前

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

    否则,这是多任务操作系统上的一个常见问题,您无法知道其他进程可能会对您的文件造成什么影响。你的使用模式似乎不寻常,先回顾一下。一个解决方法是捕捉错误,睡眠,然后再试一次。或者使用SHFileOperation()将文件移动到回收站。

        3
  •  5
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

    愚蠢的建议-因为它很少失败,所以只需在失败时等待几毫秒,然后再试一次。

    或者,如果延迟很重要,请切换到另一个文件名,让旧文件稍后删除。

        4
  •  4
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

    这也许不是你的问题,但有可能,所以我建议你离开 Process Monitor (Sysinternals)并参见。

    我也有同样的问题 Comodo Internet Security ( cmdagent.exe )是问题的根源。以前我有一个双核机器,但当我升级到一个英特尔i7突然我的工作软件( jam.exe

    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
  •  4
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

    有没有办法检测到文件正在等待删除?

    使用 GetFileInformationByHandleEx FILE_STANDARD_INFO 结构。

    sbi's solution

        6
  •  3
  •   Jacob    16 年前

    照顾 关于文件名是什么。如果真是这样,你应该考虑创建一个 照顾 文件还没有被删除。

        7
  •  2
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

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

    解决方法是“关闭句柄”或使用FreeLibrary( 路径

    注:请阅读 MSDN 关于FreeLibrary()。

        8
  •  1
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

    在WindowsVista/Windows7上 DeleteFileTransacted

    另一种方法是使用带有\u CREATE标志的OpenFile(),如果文件存在,则将长度设置为零;如果文件不存在,则创建长度;然后调用文件句柄上的FlushFileBuffers,等待此操作(使文件长度为零)完成。完成时,文件大小为0,然后只需调用DeleteFile。

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

        9
  •  1
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

    NtDeleteFile

    不幸的是 NtDeleteFile文件 [2] 没有提到这个问题的任何细节。

    [1] Undocumented functions of NTDLL

    [2] ZwDeleteFile function

        10
  •  1
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

    given by sbi ,但出于完整性的考虑,有些人可能还想知道Windows10RS1/1603中提供的一种新方法。

    FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS . 看到了吗 full answer by RbMm .

        11
  •  1
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

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

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

        12
  •  1
  •   smallscript    6 年前

    我刚吃了这个 准确的 采取了两个步骤来解决这个问题; 我停止使用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文件的任何底层数据流的所有句柄之前,不能删除任何硬链接。这很奇怪,因为:

    这会让你觉得只有 最后的 当内核有一个或多个引用硬链接NTFS文件的MFT条目/ATTRs的打开文件句柄时,硬链接应该是不可删除的。 总之,还有一件事要知道。

        13
  •  -2
  •   Peter Mortensen Pieter Jan Bonestroo    7 年前

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

    INVALID_HANDLE_VALUE GetLastError() 也可能给你一个更好的方法来检测这个特殊的 .

    我更喜欢重叠I/O,但是 CloseHandle() DeleteFile() 不处理重叠操作:(