web-dev-qa-db-ja.com

C ++ / Win32:保留中の削除が完了するのを待つ方法

解決済み:

問題:

当社のソフトウェアは、大部分がプロプライエタリスクリプト言語のインタプリタエンジンです。そのスクリプト言語には、ファイルを作成して処理し、ファイルを削除する機能があります。これらはすべて別個の操作であり、これらの操作の間にファイルハンドルが開いたままになることはありません。

(つまり、ファイルの作成中に、ハンドルが作成され、書き込みに使用されてから閉じられます。ファイル処理部分では、別のファイルハンドルがファイルを開き、ファイルから読み取り、EOFで閉じられます。そして最後に、deleteは:: DeleteFileを使用します。これはファイル名のみを使用し、ファイルハンドルは使用しません)。

最近、特定のマクロ(スクリプト)がランダムな後続の時間にファイルを作成できない場合があることに気付きました(つまり、「作成、処理、削除」の最初の100回の反復中に成功しますが、 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でWaitForSingleObject()を実行するだけで、最初のオプションを試しました。ただし、HFILEはWaitForSingleObjectが実行される前に常に閉じられるため、WaitForSingleObjectは常にWAIT_FAILEDを返します。明らかに、閉じたハンドルを待とうとしても機能しません。

ファイルが存在するフォルダの変更通知を待つこともできます。ただし、これは、問題がたまにしか発生しないため、非常にオーバーヘッドがかかるようです(つまり、Windows 7 x64 E6600 PCでのテストでは、通常、反復12000+で失敗します-他のマシンでは、反復7、15、56、またはまったく発生しないとすぐに発生する可能性があります)。

このエーテルを明示的に許可するCreateFile()引数を識別できませんでした。 CreateFileの引数に関係なく、ファイルが削除を保留しているときにanyアクセス用にファイルを開くことは実際には問題ありません。

また、この動作はWindowsXPボックスとx64Windows 7ボックスの両方で確認できるため、これがMicrosoftの「意図したとおり」のコアNTFS動作であると確信しています。したがって、続行する前に、できればCPUサイクルを不必要に拘束することなく、またこのファイルが含まれるフォルダーを監視するという極端なオーバーヘッドなしに、OSが削除を完了できるソリューションが必要です(可能な場合)。

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;
}
32
Mordachai

最初に削除するファイルの名前を変更してから、削除します。

GetTempFileName()を使用して一意の名前を取得してから、MoveFile()を使用してファイルの名前を変更します。次に、名前を変更したファイルを削除します。実際の削除が実際に非同期であり、同じファイルの作成と競合する可能性がある場合(テストで示されているように)、これで問題が解決するはずです。

もちろん、分析が正しく、ファイル操作がある程度非同期で行われる場合、名前の変更が行われる前にファイルを削除しようとするという問題が発生する可能性があります。ただし、バックグラウンドスレッドでいつでも削除を試みることができます。

ハンスが正しい場合(そして私は彼の分析を信じる傾向があります)、別のプロセスによって開かれているファイルの名前を実際に変更できない可能性があるため、移動は実際には役に立たない可能性があります。 (しかし、あなたはそうかもしれません、私はこれを知りません。)それが実際にそうであるならば、私が思いつくことができる唯一の他の方法は「試み続ける」ことです。数ミリ秒待ってから再試行する必要があります。これが役に立たないときはあきらめるためにタイムアウトを維持します。

15
sbi

そのファイルの一部を必要とするWindowsの他のプロセスがあります。検索インデクサーは明らかな候補です。またはウイルススキャナー。彼らは、FILE_SHARE_DELETEを含む完全な共有のためにファイルを開くので、他のプロセスはファイルを開くことによって大きな影響を受けません。

高い割合で作成/書き込み/削除しない限り、これは通常うまくいきます。削除は成功しますが、ファイルの最後のハンドルが閉じられるまで、ファイルはファイルシステムから消えることはありません。たとえば、検索インデクサーが保持するハンドル。その保留中の削除ファイルを開こうとするプログラムは、エラー5によって平手打ちされます。

それ以外の場合、これはマルチタスクオペレーティングシステムの一般的な問題であり、他のプロセスがファイルを台無しにする可能性があるかどうかを知ることはできません。あなたの使用パターンは異常に思えます、最初にそれを見直してください。回避策は、エラーをキャッチし、スリープしてから再試行することです。または、SHFileOperation()を使用してファイルをごみ箱に移動します。

20
Hans Passant

愚かな提案-失敗することはめったにないので、失敗するまで数ミリ秒待ってから再試行してください。

または、待ち時間が重要な場合は、別のファイル名に切り替えて、古いファイルを後で削除するようにします。

5
Arkadiy

これはあなたの特定の問題ではないかもしれませんが、可能性があるので、外に出て Process Monitor (Sysinternals)を参照することをお勧めします。

私はまったく同じ問題を抱えていて、 Comodo Internet Securitycmdagent.exe)が問題の原因になっていることを発見しました。以前はデュアルコアマシンを使用していましたが、Intel 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がファイルを開くことができるようになりました。

もちろん、待ってから再試行するための推奨される解決策であり、問​​題なく機能します。

4
Craig

ファイルが削除を保留していることを検出する方法はありますか?

GetFileInformationByHandleEx 関数を FILE_STANDARD_INFO 構造で使用します。

しかし、この関数では問題を解決できません。 sbiのソリューション どちらでもない。

4
Benjamin

新しいファイルを作成し、処理してから削除しているので、ファイル名が何であるかについては実際にはcareではないようです。それが本当の場合は、常に一時ファイルを作成することを検討する必要があります。そうすれば、プロセスを実行するたびに、ファイルがまだ削除されていないことをcareする必要はありません。

3
Jacob

LoadLibrary(path)を使用しているときに、実際に同じ問題が発生しました。 pathのファイルを削除できませんでした。

解決策は、「ハンドルを閉じる」か、FreeLibrary(path)メソッドを使用することでした。

注:FreeLibrary()に関する [〜#〜] msdn [〜#〜] の「備考」をお読みください。

2
OhadM

私はちょうどこのexactの問題を抱えており、それに対処するために2つのステップを踏みました。 C/C++ stdlibapisと::DeleteFile(..)の使用を停止し、次のように切り替えました:

  1. ::MoveFileEx(src,dest,MOVEFILE_WRITE_THROUGH);参照:MOVEFILE_WRITE_THROUGH

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

上記は関連するフラグを示す疑似呼び出しです。特に、deleteを達成するために使用されるCreateFile呼び出しには共有がないことに注意してください。 。

一緒になって、renameおよびdeleteセマンティクスの精度が向上しました。 彼らは今私のコードで働いており、他のスレッド/プロセス(ファイルシステムの変更を監視)からの精度と制御を改善し、他のスレッド/プロセスの待ち時間[または共有]のためにファイルにアクションを挿入しますAPIの名前を変更したり削除したりします。その制御がないと、最後のカーネルハンドルが閉じられたときに削除するように設定されたファイルは、システムが再起動されるまで実際には開いていない可能性があり、わからない可能性があります。

うまくいけば、それらのフィードバックスニペットが他の人に役立つかもしれません。

補遺:私が行う作業の一部にハードリンクを使用することがあります。 OPENのファイルにハードリンクを作成することはできますが、そのNTFSファイルの基になるデータストリームのいずれかへのすべてのハンドルが閉じられるまで、ハードリンクを削除することはできません。それ以来、それは奇妙です:

これにより、カーネルにハードリンクされたNTFSを参照する1つ以上のオープンファイルハンドルがある間は、lastハードリンクのみを削除できないようにする必要があると考えるようになります。ファイルのMFTエントリ/ ATTR。 とにかく、知っておくべきもう1つのこと。

1
smallscript

CreateFileがINVALID_HANDLE_VALUEを返す場合は、特定の状況(削除待ち)でGetLastErrorが返すものを判別し、そのエラーコードのみに基づいてCreateFileにループバックする必要があります。

FILE_FLAG_DELETE_ON_CLOSEフラグはあなたに何かを買うかもしれません。

1
Pineapple

最良の答えは sbiによる でしたが、完全を期すために、Windows 10 RS1/1603から利用できるようになった新しい方法について知りたいと思う人もいるかもしれません。

これには、クラスFileDispositionInfoExを使用してSetFileInformationByHandle APIを呼び出し、フラグを設定することが含まれますFILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICSRbMmによる完全な回答 を参照してください。

1
Djof

Windows Vista/Windows 7には、 DeleteFileTransacted があります。これは、トランザクションを使用してファイルを削除し、確実に削除されるようにします(ファイルバッファのフラッシュなど)。ただし、Windows XPとの互換性のため、これはオプションではありません。

これを行う別のアイデアは、フラグOF_CREATEを指定してOpenFile()を使用し、ファイルが存在する場合は長さをゼロに設定し、存在しない場合は長さを作成してから、ファイルハンドルでFlushFileBuffersを呼び出してこの操作を待機することです(作成ファイルの長さがゼロ)を完了します。完了すると、ファイルのサイズは0になり、DeleteFileを呼び出すだけです。

ファイルが存在するかどうか、またはファイルの長さがゼロかどうかを後でテストして、同じように扱うことができます。

1
Coder12345

[1]によると、DeleteFileの非同期性を回避するためにNtDeleteFileを使用できます。また、[1]には、DeleteFileの動作に関する詳細が記載されています。

残念ながら、NtDeleteFile [2]の公式ドキュメントには、この問題に関する特定の詳細は記載されていません。

[1]NTDLLの文書化されていない機能

[2]ZwDeleteFile関数

1
Christian K.