首先由GetLastError 返回的ERROR_ACCESS_DENIED 并不总是意味着拒绝访问错误。通常(但并非总是)错误源 - 是 NTSTATUS 代码,由本机 api 返回,然后通过 RtlNtStatusToDosError 转换为 win32 错误代码。但这种转换不是单射的。许多不同的NTSTATUS 代码转换为相同的ERROR_ACCESS_DENIED,不仅仅是STATUS_ACCESS_DENIED,因此经常存在对错误真正原因的混淆。万一GetFileAttributes, FindFirstFileExW 失败更好的调用
extern "C" NTSYSAPI ULONG NTAPI RtlGetLastNtStatus();
api,而不是GetLastError()。这是未记录的,但在某些情况下非常有用。
现在关于具体的GetFileAttributesW - 我们需要FILE_READ_ATTRIBUTES 访问该文件,以免因访问被拒绝而失败。但是文件系统将FILE_READ_ATTRIBUTES 授予调用者,或者如果文件安全描述符授予它,或者如果父文件夹为调用者授予FILE_LIST_DIRECTORY。当然,如果我们没有FILE_LIST_DIRECTORY 用于父文件夹和FILE_READ_ATTRIBUTES 用于文件并且调用者没有SeBackupPrivilege(此权限会导致系统授予对任何文件的所有读取访问控制,而不管访问控制如何为文件指定的列表 (ACL)。) - 这里什么也做不了。
但在具体情况下,我猜 op 确实不是 STATUS_ACCESS_DENIED 而是 STATUS_DELETE_PENDING 错误。如果有人调用 DeleteFile 但文件中仍然存在打开的句柄,则会出现此错误:
DeleteFile 函数将文件标记为在关闭时删除。
因此,文件删除直到最后一个句柄才会发生
该文件已关闭。随后调用CreateFile打开文件
以ERROR_ACCESS_DENIED 失败。
(在这种情况下,NTSTATUS 代码当然是STATUS_DELETE_PENDING,但RtlNtStatusToDosError 将其转换为ERROR_ACCESS_DENIED)
GetFileAttributes 失败的可能原因是该文件在关闭时标记为删除。但如果文件仍未删除,则可以通过FindFirstFileExW 获取它的属性。注意 - FindFirstFileExW 返回有关父文件夹中文件属性的信息。为了调用这个 api,我们只需要 FILE_LIST_DIRECTORY 访问父文件夹。这就是为什么文件系统为文件授予FILE_READ_ATTRIBUTES 的原因,如果我们有FILE_LIST_DIRECTORY 用于父文件夹。如果在页面文件上调用它,GetFileAttributes 也可能因错误STATUS_SHARING_VIOLATION 而失败(当然是非常特殊的情况)。如果我们没有 FILE_LIST_DIRECTORY 访问父文件夹(并且没有启用备份权限) - FindFirstFileExW 也失败了,这里什么也做不了。
所以可能的解决方案,不会失败,当文件标记为关闭时删除 - 在这种情况下调用FindFirstFileExW。请注意,FindFirstFileExW 与GetFileAttributes 相比,调用成本要高得多,从另一方面来看,当我们查询标记为删除的文件时,这种情况非常罕见。所以总是最好先调用GetFileAttributes,并且只有在代码STATUS_DELETE_PENDING失败时尝试FindFirstFileExW调用。
NTSTATUS GetFileAttributesEx(PCWSTR FileName, DWORD* pdwFileAttributes)
{
DWORD dwFileAttributes = GetFileAttributes(FileName);
if (INVALID_FILE_ATTRIBUTES != dwFileAttributes)
{
*pdwFileAttributes = dwFileAttributes;
return STATUS_SUCCESS;
}
NTSTATUS status = RtlGetLastNtStatus();
switch (status)
{
case STATUS_SHARING_VIOLATION: // this is only for pagefile i think can be
case STATUS_DELETE_PENDING:
WIN32_FIND_DATAW fd;
HANDLE hFile = FindFirstFileExW(FileName, FindExInfoBasic, &fd, FindExSearchNameMatch, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
*pdwFileAttributes = fd.dwFileAttributes;
FindClose(hFile);
status = STATUS_SUCCESS;
}
else
{
status = RtlGetLastNtStatus();
}
break;
}
return status;
}
用于测试的演示代码:
ULONG cb = 0, rcb = 0x80;
static volatile UCHAR guz = 0;
PVOID stack = alloca(guz);
PWSTR FileName = 0;
do
{
if (cb < rcb)
{
cb = (ULONG)((PWSTR)stack - (FileName = (PWSTR)alloca((rcb - cb)* sizeof(WCHAR))));
}
rcb = ExpandEnvironmentStringsW(L"%tmp%/test.tmp", FileName, cb);
} while (cb < rcb);
if (rcb)
{
HANDLE hFile = CreateFile(FileName, DELETE, 0, 0, CREATE_NEW,
FILE_ATTRIBUTE_TEMPORARY|FILE_ATTRIBUTE_HIDDEN|FILE_FLAG_DELETE_ON_CLOSE, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
ULONG dwFileAttributes;
GetFileAttributesEx(FileName, &dwFileAttributes);
static FILE_DISPOSITION_INFO fdi = { TRUE };
SetFileInformationByHandle(hFile, FileDispositionInfo, &fdi, sizeof(fdi));
GetFileAttributesEx(FileName, &dwFileAttributes);
CloseHandle(hFile);
GetFileAttributesEx(FileName, &dwFileAttributes);
}
}