Deleting a file based on disk ID

2020-06-09 03:34发布

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 the OpenFileById handle, providing DELETE for dwDesiredAccess, and using that. Same ERROR_INVALID_PARAMETER result.
  • Using ReOpenFile with the OpenFileById handle, providing DELETE for dwDesiredAccess, and using that. Same ERROR_INVALID_PARAMETER result.
  • Using ReOpenFile with the OpenFileById handle, providing DELETE for dwDesiredAccess, and providing the FILE_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.

4条回答
来,给爷笑一个
2楼-- · 2020-06-09 04:16

Have you looked into FILE_FLAG_POSIX_SEMANTICS? It will allow you to open files that differ only in case using CreateFile.

Edit: I guess I should have read your code first as I see you are using said flag.

查看更多
3楼-- · 2020-06-09 04:19

Assume the files are XXX and xxx and you want to delete XXX.

  1. MoveFile("XXX", "I think it's XXX")
  2. If XXX got renamed, then DeleteFile("I think it's XXX")
  3. Otherwise, DeleteFile("XXX"); MoveFile("I think it's XXX", "xxx")

As to OpenFileById, as you noted, there is a potential ambiguity with a file with multiple names (aka hard links). Allowing DELETE access could cause havoc with this, with an unexpected name being deleted (if it were left to the file system to select which one). I suspect they opted for the simple case of never letting DELETE access be granted.

A similar argument could be made for allowing hard links to directories. Sure, you could do it some of the time correctly, but once you created a cycle, things get a lot tougher...

查看更多
我欲成王,谁敢阻挡
4楼-- · 2020-06-09 04:24

In order to make FILE_DISPOSITION_INFO work you need to specify the DELETE access in the CreateFile function as reported in https://msdn.microsoft.com/en-us/library/windows/desktop/aa365539(v=VS.85).aspx:

You must specify appropriate access flags when creating the file handle for use with SetFileInformationByHandle. For example, if the application is using FILE_DISPOSITION_INFO with the DeleteFile member set to TRUE, the file would need DELETE access requested in the call to the CreateFile function. To see an example of this, see the Example Code section. For more information about file permissions, see File Security and Access Rights. I.e.

//...
  HANDLE hFile = CreateFile( TEXT("tempfile"), 
                             GENERIC_READ | GENERIC_WRITE | DELETE,  //Specify DELETE access!
                             0 /* exclusive access */,
                             NULL, 
                             CREATE_ALWAYS,
                             0, 
                             NULL);

But it seems that an handle created with OpenFileById() cannot be used because the function cannot accept the DELETE flag.
From https://msdn.microsoft.com/en-us/library/windows/desktop/aa365432(v=vs.85).aspx on OpenFileById() it can be read: dwDesired

Access [in]
The access to the object. Access can be read, write, or both.

Even setting DELETE or GENERIC_ALL the function fails.
If you replace the handle passed to SetFileInformationByHandle with one created with the CreateFile function having the DELETE flag set, as above, it works.

查看更多
小情绪 Triste *
5楼-- · 2020-06-09 04:32

There's a user mode version of the kernel mode ZwCreateFile called NTCreteFile which, among other things will give you all of the access rights you can't get with OpenFileById (but you can get with CreateFile). It can do everything CreateFile can do and more. For example, it can even create directories.

The good part is, there's an immensely hacky (but entertaining) way of specifying a file ID in the POBJECT_ATTRIBUTES argument as well, so you get the best of all worlds...except that it's an even more awkward API to call than your run-of-the-mill awkward Windows APIs.

There are two versions of the documentation. One at:

https://msdn.microsoft.com/en-us/library/bb432380(v=vs.85).aspx

and one at:

https://msdn.microsoft.com/en-us/library/windows/hardware/ff556465(v=vs.85).aspx

...which links to the ZwCreateFile documentation at:

https://msdn.microsoft.com/en-us/library/windows/hardware/ff566424(v=vs.85).aspx

The reason I point this out is that the first article omits some of the goodies (like opening files by ID) that are documented in the last article. I have found this to be common and have also found that most of the documented Zwxxx functionality actually does exists in the equivalent, but incompletely documented NTxxx functions. So you gotta hold your mouth just right to get the requisite functionality.

查看更多
登录 后发表回答