【问题标题】:Deleting a file based on disk ID根据磁盘 ID 删除文件
【发布时间】:2016-07-13 01:04:33
【问题描述】:

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 电话中?

我尝试过的其他方法:

  • 使用OpenFileById 句柄调用DuplicateHandle,为dwDesiredAccess 提供DELETE,并使用它。 相同的ERROR_INVALID_PARAMETER 结果。
  • 使用ReOpenFileOpenFileById 句柄,为dwDesiredAccess 提供DELETE,并使用它。 相同的ERROR_INVALID_PARAMETER 结果。
  • ReOpenFileOpenFileById 句柄一起使用,为dwDesiredAccess 提供DELETE,并提供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 及更高版本。也欢迎提出代码改进建议。

【问题讨论】:

  • 尝试在从 OpenFileById 获得的句柄上使用 DuplicateHandle,将 dwDesiredAccess 设置为 DELETE
  • @HarryJohnston 好主意,可惜没用。我已经编辑了这个问题,以包含我迄今为止尝试过的内容。
  • 大概系统内核被配置为区分大小写,否则你一开始就不会有这样的文件;我不能使用 NtCreateFile 吗?
  • SWAG:听起来很愚蠢,您可能不需要在 OpenFileById 调用中包含 POSIX_SEMANTICS 来获得“兼容”句柄吗?是的 - 我知道 POSIX_SEMANTICS 名义上只与文件名有关......但世界上有更愚蠢的东西。 @HarryJohnston 提到了 ERROR_INVALID_PARAMETER - 也许在内部,它是“严重”比较标志。
  • 作为记录,documentation here: File System Behavior Overview (PDF) 确认(第 4.3.2 节)您不能为由 ID 打开的句柄设置关闭时删除标志。

标签: c winapi ntfs createfile


【解决方案1】:

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

您必须在创建文件时指定适当的访问标志 与 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 标志。
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365432(v=vs.85).aspxOpenFileById() 可以阅读: dw想要的

访问 [in]
对对象的访问。访问权限可以是读、写或两者兼有。

即使设置 DELETEGENERIC_ALL 功能也会失败。
如果您将传递给SetFileInformationByHandle 的句柄替换为使用设置了DELETE 标志的CreateFile 函数创建的句柄,如上所述,它可以工作。

【讨论】:

  • CreateFile 未用于创建 FILE_DISPOSITION_INFO 正在使用的句柄,OpenFileById 是。 GENERIC_ALL 用于访问,包括删除。用GENERIC_READ | GENERIC_WRITE | DELETE 替换它没有区别。
  • 我已经知道使用CreateFile 可以工作,如上所述。这仍然存在文件路径与CreateFile 不同的问题。通常是否可以通过磁盘 ID 删除文件,或者可靠地删除两个仅大小写不同的文件,如果一个是文件,另一个是目录?
  • MS 文件系统不区分大小写,因此通常答案是否定的。我已经检查了对我来说最后的机会是什么:使用ReOpenFile 重新打开使用OpenFileById 添加DELETE 标志创建的苍蝇,但这个解决方案也不起作用:(
  • 问题是 NTFS 区分大小写但 Windows 不区分。 ReOpenFile 的好主意,可惜没用。不过,FindNextFile 和类似方法仍将它们视为单独的文件。
  • OpenFileById 似乎是为网络文件而设计的,因此对真实文件的访问权限有限。也许有一个功能可以更改打开的文件上的某些内容。不是最常用的之一,也不是我记得的。也许你想在那个方向搜索类似ReOpenFile...
【解决方案2】:

您是否查看过 FILE_FLAG_POSIX_SEMANTICS?它将允许您打开仅在使用 CreateFile 的情况下不同的文件。

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

【讨论】:

  • 不起作用。你自己试过吗?名称无效也会失败。
  • 我正在尝试您的示例,我可以重现该行为。我想我现在向您提出的问题是,您希望哪一部分开始工作,删除仅大小写不同的文件或删除由 ID 打开的文件?
  • 删除由 ID 打开的文件,其中一个好处是能够删除仅大小写不同的文件以及无效文件名,例如包含 : 的文件。如果内核当前不区分大小写,FILE_FLAG_POSIX_SEMANTICS 也无法正常工作,这是默认设置。如果您可以形成一个您验证有效的代码示例,那可能会很有用。
【解决方案3】:

内核模式ZwCreateFile 的用户模式版本称为NTCreteFile,除其他外,它会为您提供OpenFileById 无法获得的所有访问权限(但您可以使用CreateFile )。它可以做CreateFile 可以做的所有事情,甚至更多。例如,它甚至可以创建目录。

好消息是,在 POBJECT_ATTRIBUTES 参数中指定文件 ID 的方式也非常老套(但很有趣),因此您可以获得世界上最好的...除了它是一个更尴尬的 API调用比普通的笨拙的 Windows 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 打开文件)。我发现这很常见,并且还发现大多数已记录的 Zwxxx 功能实际上确实存在于等效但未完整记录的 NTxxx 函数中。因此,您必须恰到好处地张开嘴巴才能获得必要的功能。

【讨论】:

    【解决方案4】:

    假设文件为 XXX 和 xxx,您要删除 XXX。

    1. MoveFile("XXX", "我认为是 XXX")
    2. 如果 XXX 被重命名,则 DeleteFile("I think it's XXX")
    3. 否则,DeleteFile("XXX"); MoveFile("我觉得是 XXX", "xxx")

    至于 OpenFileById,正如您所指出的,具有多个名称(又名硬链接)的文件可能存在歧义。允许 DELETE 访问可能会对此造成严重破坏,会删除一个意外的名称(如果留给文件系统选择哪个名称)。我怀疑他们选择了从不授予 DELETE 访问权限的简单案例。

    对于允许硬链接到目录,可以提出类似的论点。当然,你可以在某些时候正确地做到这一点,但是一旦你创建了一个循环,事情就会变得更加艰难......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-29
      • 2021-04-20
      • 2018-08-12
      • 1970-01-01
      • 2019-11-14
      相关资源
      最近更新 更多