根据磁盘ID删除文件

时间:2016-03-25 09:16:16

标签: c winapi ntfs createfile

正如here所述,SetFileInformationByHandleFILE_DISPOSITION_INFO一起使用 允许用户设置一个打开句柄的文件,在所有句柄关闭时删除。

但是,我正在尝试根据FILE_DISPOSITION_INFO检索到的文件索引(磁盘ID)删除文件 OpenFileById以便安全地删除目录中的文件/目录,这些文件/目录仅在大小写上有所不同。 这在我的用例中是安全的,因为在NTFS系统上,文件索引是persistent until deletion, 否定当前代码库处理的ReplaceFile的使用。

但是,在尝试删除句柄时,我收到错误87(ERROR_INVALID_PARAMETER)。 如果我使用CreateFileW创建的句柄删除,我会遇到任何问题。 但是,我无法做到这一点,因为Windows无法区分同一案例的两个文件/文件夹,即使NTFS可以。

我也知道用OpenFileById打开的硬链接文件存在歧义, 由于硬链接文件共享相同的磁盘ID。 可以认为硬链接文件的问题与此方案无关。 我只会按ID删除目录,不能硬链接。

我的OpenFileById电话中是否缺少参数或设置? 不知何故,在SetFileInformationByHandle电话中?

我尝试过的其他方法:

  • 使用DuplicateHandle句柄调用OpenFileById,为DELETE提供dwDesiredAccess,并使用该句柄。 相同ERROR_INVALID_PARAMETER结果。
  • ReOpenFileOpenFileById句柄一起使用,为DELETE提供dwDesiredAccess,并使用该句柄。 相同ERROR_INVALID_PARAMETER结果。
  • ReOpenFileOpenFileById句柄一起使用,为DELETE提供dwDesiredAccess,并提供FILE_FLAG_DELETE_ON_CLOSE标记。 没有给出错误,但在关闭所有句柄后文件仍然存在。

这是一个最小但又完整的例子,可以重现这个问题:

#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;
}

任何解决方案都必须适用于Vista及更高版本。也欢迎提出改进代码的建议。

4 个答案:

答案 0 :(得分:1)

要使FILE_DISPOSITION_INFO工作,您需要在https://msdn.microsoft.com/en-us/library/windows/desktop/aa365539(v=VS.85).aspx中报告的CreateFile函数中指定 DELETE 访问权限:

  

创建文件时,您必须指定适当的访问标记   用于SetFileInformationByHandle的句柄。例如,如果   应用程序正在使用FILE_DISPOSITION_INFO和DeleteFile成员   设置为TRUE,该文件将需要在调用中请求DELETE访问权限   到CreateFile函数。要查看此示例,请参阅示例   代码部分。有关文件权限的详细信息,请参阅文件   安全和访问权限。   即。

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

但似乎无法使用使用OpenFileById()创建的句柄,因为该函数无法接受 DELETE 标记。
OpenFileById() DELETE开始,可以阅读: dwDesired

  

访问[in]
     访问对象。可以读取,写入或同时访问。

即使设置 GENERIC_ALL SetFileInformationByHandle ,该功能也会失败。
如果将传递给CreateFile的句柄替换为使用设置了 DELETE 标记的{{1}}函数创建的句柄,如上所述,则可以正常工作。

答案 1 :(得分:1)

有一个名为ZwCreateFile的内核模式NTCreteFile的用户模式版本,除了其他功能之外,还会为您提供OpenFileById无法获得的所有访问权限(但是您可以使用CreateFile)。它可以完成CreateFile可以做的所有事情。例如,它甚至可以创建目录。

好的部分是,在POBJECT_ATTRIBUTES参数中指定文件ID的方式非常hacky(但很有趣),所以你可以获得所有世界中最好的...除了它是更多比一般的笨拙的Windows API更难以调用的API。

该文档有两个版本。一个在:

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

和一个在:

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

...链接到ZwCreateFile文档:

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

我指出这一点的原因是第一篇文章省略了上一篇文章中记载的一些好东西(比如按ID打开文件)。我发现这很常见,并且还发现大多数记录的Zw xxx 功能确实存在于等效但未完全记录的NT xxx 功能中。所以你必须正确地抓住你的嘴,以获得必要的功能。

答案 2 :(得分:0)

你有没有看过FILE_FLAG_POSIX_SEMANTICS?它允许您打开仅在使用CreateFile时才有区别的文件。

编辑:我想我应该先阅读你的代码,因为我看到你正在使用这个标志。

答案 3 :(得分:0)

假设文件是​​XXX和xxx,并且您要删除XXX。

  1. MoveFile(“XXX”,“我认为是XXX”)
  2. 如果XXX已重命名,则DeleteFile(“我认为它是XXX”)
  3. 否则,DeleteFile(“XXX”); MoveFile(“我认为它是XXX”,“xxx”)
  4. 对于OpenFileById,正如您所指出的,对于具有多个名称(即硬链接)的文件存在潜在的歧义。允许DELETE访问可能会导致严重破坏,删除意外名称(如果留给文件系统选择哪一个)。我怀疑他们选择了永远不允许DELETE访问的简单案例。

    可以使用类似的参数来允许指向目录的硬链接。当然,你可以在某些时候正确地做到这一点,但是一旦你创造了一个周期,事情变得更加艰难......