As described here, using SetFileInformationByHandle
with FILE_DISPOSITION_INFO
allows one to set a file with an open handle to be deleted upon all handles being closed.
However, I am trying to delete a file based on its file index (disk ID) retrieved by FILE_DISPOSITION_INFO
and
OpenFileById
in order to safely delete files/directories in a directory which differ only in case.
This is safe to do in my use case, as on an NTFS system, file indexes are persistent until deletion,
negating the use of ReplaceFile
, which the current codebase handles.
However, when attempting to delete the handle, I get error 87 (ERROR_INVALID_PARAMETER
).
If I delete using a handle created with CreateFileW
, I run into no problems.
I can't do this, though, as Windows will not be able to distinguish between two file/folders of the same case, even though NTFS can.
I am also aware that there is an ambiguity with hardlinked files opened with OpenFileById
,
as hardlinked files share the same disk ID.
The issue of hardlinked files can be considered irrelevant for this scenario.
I will only be deleting directories by ID, which cannot be hardlinked.
Is there a parameter or setting I am missing in my OpenFileById
call?
Somehow, in my SetFileInformationByHandle
call?
Additional methods I have tried:
- Calling
DuplicateHandle
with theOpenFileById
handle, providingDELETE
fordwDesiredAccess
, and using that. SameERROR_INVALID_PARAMETER
result. - Using
ReOpenFile
with theOpenFileById
handle, providingDELETE
fordwDesiredAccess
, and using that. SameERROR_INVALID_PARAMETER
result. - Using
ReOpenFile
with theOpenFileById
handle, providingDELETE
fordwDesiredAccess
, and providing theFILE_FLAG_DELETE_ON_CLOSE
flag. No error is given, but the file remains after all handles are closed.
Here is a minimal, yet complete, example which reproduces the problem:
#include <stdio.h>
#include <sys/stat.h>
#include <Windows.h>
DWORD getFileID(LPCWSTR path, LARGE_INTEGER *id)
{
HANDLE h = CreateFileW(path, 0, 0, 0, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT |
FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_POSIX_SEMANTICS,
0);
if (h == INVALID_HANDLE_VALUE)
return GetLastError();
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(h, &info))
{
DWORD err = GetLastError();
CloseHandle(h);
return err;
}
id->HighPart = info.nFileIndexHigh;
id->LowPart = info.nFileIndexLow;
CloseHandle(h);
return ERROR_SUCCESS;
}
DWORD deleteFileHandle(HANDLE fileHandle)
{
FILE_DISPOSITION_INFO info;
info.DeleteFileW = TRUE;
if (!SetFileInformationByHandle(
fileHandle, FileDispositionInfo, &info, sizeof(info)))
{
return GetLastError();
}
return ERROR_SUCCESS;
}
int wmain(DWORD argc, LPWSTR argv[])
{
if (argc != 3)
{
fwprintf(stderr, L"Arguments: <rootpath> <path>\n");
return 1;
}
DWORD err;
HANDLE rootHandle = CreateFileW(
argv[1], 0, 0, 0, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT |
FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_POSIX_SEMANTICS,
0);
if (rootHandle == INVALID_HANDLE_VALUE)
{
err = GetLastError();
fwprintf(stderr,
L"Could not open root directory '%s', error code %d\n",
argv[1], err);
return err;
}
LARGE_INTEGER fileID;
err = getFileID(argv[2], &fileID);
if (err != ERROR_SUCCESS)
{
fwprintf(stderr,
L"Could not get file ID of file/directory '%s', error code %d\n",
argv[2], err);
CloseHandle(rootHandle);
return err;
}
fwprintf(stdout,
L"The file ID of '%s' is %lld\n",
argv[2], fileID.QuadPart);
FILE_ID_DESCRIPTOR idStruct;
idStruct.Type = FileIdType;
idStruct.FileId = fileID;
HANDLE fileHandle = OpenFileById(
rootHandle, &idStruct, DELETE, FILE_SHARE_DELETE, 0,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS);
if (fileHandle == INVALID_HANDLE_VALUE)
{
err = GetLastError();
CloseHandle(rootHandle);
fwprintf(stderr,
L"Could not open file by ID %lld, error code %d\n",
fileID.QuadPart, err);
return err;
}
err = deleteFileHandle(fileHandle);
if (err != ERROR_SUCCESS)
{
fwprintf(stderr,
L"Could not delete file by ID '%lld', error code %d\n",
fileID.QuadPart, err);
}
CloseHandle(fileHandle);
struct _stat _tmp;
fwprintf(stdout,
L"File was %ssuccessfully deleted\n",
(_wstat(argv[2], &_tmp) == 0) ? L"not " : L"");
CloseHandle(rootHandle);
return err;
}
Any solution must work with Vista and above. Suggestions for code improvement are also welcome.
dwDesiredAccess
set toDELETE
. – DisproofSetFileInformationByHandle
to returnERROR_INVALID_HANDLE
if the handle was incapable. It's also possible to find the first file path of aOpenFileById
handle. Lastly, renaming simply wouldn't work for paths with "invalid" characters, like\
or*
. – KokarasERROR_INVALID_HANDLE
is typically only used if the handle you pass isn't actually a handle or is a handle to the completely wrong kind of object, e.g., calling SetEvent on a file handle. Using a handle of the right sort but that lacks the necessary properties typically producesERROR_INVALID_PARAMETER
orERROR_ACCESS_DENIED
. For example, calling DeleteFile on a directory producesERROR_ACCESS_DENIED
.) – DisproofNUL
or/
. A music manager that edits paths would add:
to some song files. The paths allowed by Windows are a subset. Using a POSIX namespace (without installation) would fix this when files created in Linux are attempted to be deleted in Windows, but I don't see how. The peculiar thing is that you can useGetFinalPathNameByHandle
and still get the name of the first hardlink of a file opened by ID, even the path is invalid. – KokarasNTQueryInformationFile
querying forFILE_NAME_INFORMATION
, which takes the file handle and returns a full path to the file, which you can turn around and open withCreateFile()
. In such a case, you might still want to use POSIX_SEMANTICS to ensure you're getting compatible behavior by the underlying subsystem when you open the file w/ OpenFileById() – KoreOpenFileById
does not take theFILE_FLAG_POSIX_SEMANTICS
flag. Also, querying for the filename will still fail if the filename is invalid/differs only in case. – Kokaras