How to delete parent of child being monitored by ReadDirectoryChangesW - c++

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!

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

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.

Related

Checking for open files in a given directory tree

Is there a way to determine with C+++ if any file is open in a given directory tree?
Cmd.exe knows instantly if I attempt to rename a folder and a file within that directory tree is currently open. I've used API Monitor to determine that cmd.exe uses NtOpenFile() followed by NtSetInformationFile with FileRenameInformation and that returns STATUS_ACCESS_DENIED when a file is open but I've not been able to determine what's happening at a lower level.
I'm trying to ensure no files are open before beginning a batch process without having to check each file individually as there could be hundreds of thousands of files in the directory tree.
Can anyone expand on this?
Thanks,
Steve Thresher.
You will have to check each file individually, I think. That said, this should do it:
HANDLE hFile = CreateFile
(my_filename, GENERIC_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
DWORD err = GetLastError ();
if (err == ERROR_SHARING_VIOLATION)
{
// File is already open
}
}
else
CloseHandle (hFile);
Note that dwShareMode is passed as 0, which prevents Windows opening the file if it is already opened elsewhere.

Could DropBox interfere with DeleteFile()/rename()

I had the following code which got executed every two
minutes all day long:
int sucessfully_deleted = DeleteFile(dest_filename);
if (!sucessfully_deleted)
{
// this never happens
}
rename(source_filename,dest_filename);
Once every several hours the rename() would fail with errno=13 (EACCES). The files involved were all sitting on a DropBox directory and I had a hunch that DropBox could be the cause. I figured that it might just be possible that the DeleteFile() function may return with a non-zero successfully_deleted but actually DropBox could still be busy doing some stuff in relation to the deletion that prevented rename() from succeeding. What I did next was to change rename() to my_rename() which would attempt a rename() and upon any failure would Sleep() for one second and try a second time. Sure enough that has worked perfectly ever since. What's more, I get a diagnostic message displaying first-attempt-failures every several hours. It has never failed on the second attempt.
So you could say that the problem is entirely solved... but I would like to understand what might be going on so as to better defend myself against any related DropBox issues in the future...
Really I would like to have a new super_delete() function which does not return until the file is properly deleted and finished with in all respects.
under windows request to delete file really never delete file just. it mark it FCB (File Control Block) with special flag (FCB_STATE_DELETE_ON_CLOSE). real deletion will be only when the last file handle will be closed.
The DeleteFile function marks a file for deletion on close. Therefore,
the file deletion does not occur until the last handle to the file is
closed. Subsequent calls to CreateFile to open the file fail with
ERROR_ACCESS_DENIED.
also if exist section ( memory-mapped file ) open on file - file even can not be marked for delete. api call fail with STATUS_CANNOT_DELETE. so in general impossible always delete file.
in case exist another open handles for file (but not section !) begin from windows 10 rs1 exist new functional for delete - FileDispositionInformationEx with FILE_DISPOSITION_POSIX_SEMANTICS. in this case:
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.
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;
}
Update
Sorry i didn't get the question correctly the first time. I thought DeleteFile returned error 13.
Now I understand that DeleteFile succeeds but rename fails immediatlely after.
It could be just a sync issue with the filesystem. After calling DeleteFile the file will be deleted when the OS commits the changes to the filesystem. That may not appen immediately.
If you need to perform multiple operations to the same path, you should have a look at transactions https://learn.microsoft.com/it-it/windows/desktop/api/winbase/nf-winbase-deletefiletransacteda.
-- OLD ANSWER --
That is correct. If the another application handles to that file, DeleteFile will fail.
Citing MSDN docs https://learn.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-deletefile :
The DeleteFile function fails if an application attempts to delete a file that has other handles open for normal I/O or as a memory-mapped file (FILE_SHARE_DELETE must have been specified when other handles were opened).
This applies to dropbox, the antivirus, or in general, any other application that may open those files.
Dropbox may open the file to compute its hash (to look for changes) at any moment. Same goes with the antivirus.

Cygwin: Deleting file when handle is opened

I have a file which I want to delete, it's handle is held by system process so everytime I try to delete it it gives Access denied but for some reason cygwin is able to delete it.
I've downloaded the coreutils and investigated the source code of rm executable and found that it uses unlink function to achieve it. I've created a little test program which uses same function but it gives me Access denied anyway.
Then I found this article and guy describes how cygwin is able to delete a file which is the following:
Cygwin opens files always with all sharing flags
set, so a file opened by a Cygwin process should not result in a sharing
violation in another open call. The exception is the first NtOpenFile
in unlink_nt, which opens the file with FILE_SHARE_DELETE only to find
out if the file has an open handle somewhere else. In that case it gets
a STATUS_SHARING_VIOLATION, the next NtOpenFile will open the file with
all sharing flags set, and unlink_nt will try to delete the file or to
rename it, or to move it to the recycle bin, dependent on its path.
Which makes sense, So I started to implement same thing. Here is my code:
HANDLE file;
PIO_STATUS_BLOCK stat;
UNICODE_STRING myUnicodeStr;
RtlInitUnicodeString(&myUnicodeStr, L"C:\\Program Files (x86)\\TSU\\bin\\TSU.sys");
POBJECT_ATTRIBUTES attr;
InitializeObjectAttributes (attr, &myUnicodeStr, OBJ_OPENIF, NULL, NULL);
NtOpenFile(&file, MAXIMUM_ALLOWED, attr, NULL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_DELETE_ON_CLOSE);
NtClose(file);
As you can see I'm trying to open file with sharing flags set and also used FILE_DELETE_ON_CLOSE since I close the handle and I want it to be deleted afterwards.
The problem I have is Segmentation Fault(I'm using cygwin win10) for some reason. Little debugging showed that problem is in InitializeObjectAttributes function for some reason.
P.S
I know it's not the best solution to delete file when it's handle is held by some other process but the main goal is to mimic the rm.exe's behaviour in this way. Hope you can help. Thanks.
under windows not always possible delete file. for example when some process mapping this file as an image
if try delete running EXE file with rm.exe - it first call ZwOpenFile with DesiredAccess = DELETE, ShareAccess = FILE_SHARE_DELETE and OpenOptions = FILE_OPEN_FOR_BACKUP_INTENT. this is ok. than called ZwSetInformationFile with FileDispositionInformation - the DeleteFile from FILE_DISPOSITION_INFORMATION set to TRUE. this call failed with status STATUS_CANNOT_DELETE.
filesystem return STATUS_CANNOT_DELETE exactly from this place:
// Make sure there is no process mapping this file as an image.
if (!MmFlushImageSection( &Fcb->NonPaged->SectionObjectPointers,
MmFlushForDelete )) {
DebugTrace(-1, Dbg, "Cannot delete user mapped image\n", 0);
return STATUS_CANNOT_DELETE;
}
than rm.exe again try open file, already with OpenOptions = FILE_OPEN_FOR_BACKUP_INTENT | FILE_DELETE_ON_CLOSE options. but this call of course fail with the same STATUS_CANNOT_DELETE. now error from this point:
// If the user wants to delete on close, we must check at this
// point though.
//
if (FlagOn(*DesiredAccess, FILE_WRITE_DATA) || DeleteOnClose) {
Fcb->OpenCount += 1;
DecrementFcbOpenCount = TRUE;
if (!MmFlushImageSection( &Fcb->NonPaged->SectionObjectPointers,
MmFlushForWrite )) {
Iosb.Status = DeleteOnClose ? STATUS_CANNOT_DELETE :
STATUS_SHARING_VIOLATION;
try_return( Iosb );
}
}
after this rm.exe again call ZwSetInformationFile already with FileRenameInformation - where RootDirectory from FILE_RENAME_INFORMATION point to volume (on which file located) root (so like \Device\HarddiskVolume<N>\ and FileName point to some path in recycle bin. as result file actually moved but not deleted. rm.exe deceives you

I'm using QDir().isReadable to check if a drive is readable. In the Qt Creator it runs fine, but when I run the exe it keeps giving me errors

I'm using it like this:
if(QDir("G:/").isReadable()){
qDebug("G Is readable!"); //Do something with G:/
}
As I said, in Qt Creator it runs fine without a problem, it checks if the drive is readable, and if so it prints it to the console, if not, it does nothing.
But when I run the .exe file, it keeps giving me errors each time it does the check (every 2 seconds) if the drive isn't readable.
"There is no disk in the drive. Please insert a disk into drive G:."
I don't want this error to keep appearing, what do I do?
Edit: I think it's the isReadable function that causes the problem, is there any other way to do what I want to do? Or maybe should I write the code myself?
This message is generated by Windows.
There is a workaround for users that have applications that cannot be fixed. The error messages may be suppressed by setting 2 to registry key ErrorMode in:
Computer\HKEY_LOCAL\MACHINE\SYSTEM\CurrentControlSet\Control\Windows
It looks that if QDir::isReadable() is called after removing the media it triggers that error. QDir::exists() always returns true if the drive letter is present in the system, so it cannot be used here.
For now I see that it is possible to check removable media using native Windows API, see the answer to How to detect if media is inserted into a removable drive/card reader
The following code is able to detect that the media is removed without triggering the error:
#include <windows.h>
HANDLE hDevice = CreateFile (L"\\\\.\\G:", // like "\\.\G:"
FILE_READ_ATTRIBUTES, // read access to the attributes
FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode
NULL, OPEN_EXISTING, 0, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
// not valid device
return;
}
WORD cbBytesReturned;
bool bSuccess = DeviceIoControl (hDevice, // device to be queried
IOCTL_STORAGE_CHECK_VERIFY2,
NULL, 0, // no input buffer
NULL, 0, // no output buffer
(LPDWORD)&cbBytesReturned, // # bytes returned
NULL); // synchronous I/O
CloseHandle(hDevice); // close handle
if (bSuccess && QDir("G:/").isReadable()) {
// G is readable
}

Deleting a file with an open Handle

I shouldnt be able to delete a file with an open handle, correct? So i create a file, then i straight away try to delete it, expecting this to fail. Or am i wrong and the handle doesnt have to be closed before deleting the file?
HANDLE hFile = CreateFile (TEXT(file),
GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
//FAIL
}
if(DeleteFile(file))
{
//Should it ever get here cos i dont close the handle?
}
It depends on how the file has been opened. If the share mode has FILE_SHARE_DELETE specified, then it may be deleted by others.
Even if you memory map the file, and it has been opened with this flag (and read/write sharing), then it can still be deleted by the shell (at least I've tried this and it happens, but perhaps the file has simply been renamed and moved to the recycle bin). In such cases, subsequently accessing the memory will result in an 'InPageError' C-style exception.
Yes, it would fail.
The DeleteFile function fails if an application attempts to delete a
file that is open for normal I/O or as a memory-mapped file.
Have you tried this? MS documentation states that:
The DeleteFile function fails if an application attempts to delete a file that is open for normal I/O or as a memory-mapped file.
So if you're not getting that behaviour I'd suggest it's down to the way you've opened the file. Are you sure that your check on whether the file is open is completely comprehensive?Have you tried writing to the file first? Can you see the file outside of your own code? (i.e. from Explorer) Look here for more details.