根据磁盘ID删除文件

如此处所述,使用带有FILE_DISPOSITION_INFO允许设置具有打开句柄的文件,以便在关闭所有句柄时删除该文件。

但是,我试图根据文件索引(磁盘ID)删除文件,该文件由FILE_DISPOSITION_INFOOpenFileById检索,以便安全地删除目录中只有大小写不同的文件/目录。 这在我的用例中是安全的,因为在NTFS系统上,文件索引在删除之前是持久的 ,否定当前代码库处理的ReplaceFile的使用。

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

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

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

我试过的其他方法:

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

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

 #include  #include  #include  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:  \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及更高版本。 也欢迎提出改进代码的建议。

为了使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,则该文件将需要在调用CreateFile函数时请求的DELETE访问权限。 要查看此示例,请参阅示例代码部分。 有关文件权限的详细信息,请参阅文件安全性和访问权限。 即

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

但似乎无法使用使用OpenFileById()创建的句柄,因为该函数无法接受DELETE标志。
OpenFileById()上的https://msdn.microsoft.com/en-us/library/windows/desktop/aa365432(v=vs.85).aspx可以看到:dwDesired

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

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

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

好的部分是,在POBJECT_ATTRIBUTES参数中指定文件ID也是一种非常hacky(但很有趣)的方式,所以你可以获得最好的世界……除了它是一个比你的运行更难以调用的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打开文件)。 我发现这很常见,并且还发现大多数记录的Zw xxxfunction确实存在于等效但未完全记录的NT xxx函数中。 因此,您必须正确地抓住您的嘴,以获得必要的function。

你看过FILE_FLAG_POSIX_SEMANTICS了吗? 它允许您打开仅在使用CreateFile的情况下不同的文件。

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

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

  1. MoveFile(“XXX”,“我认为它是XXX”)
  2. 如果XXX被重命名,那么DeleteFile(“我认为它是XXX”)
  3. 否则,DeleteFile(“XXX”); MoveFile(“我认为它是XXX”,“xxx”)

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

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