How to delete parent of child being monitored by ReadDirectoryChangesW

337 views Asked by At

Monitoring a folder with ReadDirectoryChangesW causes its parent to be locked and can't be deleted.

There is a post about this here:

FindFirstChangeNotification locks parent folder

but the only solution mentioned in it is that we should always listen at the top level.

Has anyone found out a better way to do this instead of watching at the top level?

Sometimes this can go all the way to watching the Drive and that can't take a lot of process time on a machine.

Thanks!

2

There are 2 answers

1
RbMm On

folder can be deleted only in case it empty, otherwise we got error STATUS_DIRECTORY_NOT_EMPTY - Indicates that the directory trying to be deleted is not empty.

from another side - if you have opening handle for file - it can not be deleted until you not close it handle (something here changed begin from win10 rs1)

so if you monitor some child sub-folder with ReadDirectoryChangesW you have opened handle to it, and parent can not (before WIN10_RS1) be deleted until you not close this handle.

in general process look like - when somebody try delete folder - it must enumerate all files(sub-folders) inside it and delete it first. when delete operation will be apply on folder for which ReadDirectoryChangesW called - the io request will be completed with status STATUS_DELETE_PENDING - A non close operation has been requested of a file object with a delete pending. (it converted to win32 error code ERROR_ACCESS_DENIED - Access is denied.). when you got this error from ReadDirectoryChangesW you must close your directory handle used in this call. then is raise - who is first - you close directory handle or another code try delete parent folder...


begin from win10 rs1 possible delete parent, even if somebody hold open handle to it child file(folder) by calling NtSetInformationFile with FileDispositionInformationEx or SetFileInformationByHandle with FileDispositionInfoEx.

the magic here in new flag FILE_DISPOSITION_POSIX_SEMANTICS (Specifies the system should perform a POSIX-style delete)

Normally a file marked for deletion is not actually deleted until all open handles for the file have been closed and the link count for the file is zero. When marking a file for deletion using FILE_DISPOSITION_POSIX_SEMANTICS, the link gets removed from the visible namespace as soon as the POSIX delete handle has been closed, but the file’s data streams remain accessible by other existing handles until the last handle has been closed.

so when we use this - file itself of course will be not deleted, until caller of ReadDirectoryChangesW not close self handle, but file will be removed from the parent folder. as result parent folder can became empty, after which we can delete it.

note that DeleteFileW and RemoveDirectoryW here will be not work here, because they used old information class FileDispositionInformation with FILE_DISPOSITION_INFORMATION

ULONG DeletePosix(PCWSTR lpFileName)
{
    HANDLE hFile = CreateFileW(lpFileName, DELETE, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 
        FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT, 0);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        return GetLastError();
    }

    static FILE_DISPOSITION_INFO_EX fdi = { FILE_DISPOSITION_DELETE| FILE_DISPOSITION_POSIX_SEMANTICS };

    ULONG dwError = SetFileInformationByHandle(hFile, FileDispositionInfoEx, &fdi, sizeof(fdi)) 
        ? NOERROR : GetLastError();

    // win10 rs1: file removed from parent folder here
    CloseHandle(hFile);

    return dwError;
}

and of course child must be open with FILE_SHARE_DELETE in other calls, otherwise we simply can not open it with DELETE access later

0
Laszlo On

It is important to specify the right attributes to CreateFile() when you obtain the directory handle. Try this:

HANDLE hDir = ::CreateFile(
    strDirectoryName,
    FILE_LIST_DIRECTORY,
    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
    NULL, // security descriptor
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
    NULL);

It is important to specify FILE_SHARE_DELETE as well for the share mode.