c++ - 如何判断路径是文件还是没有提升权限的目录

标签 c++ c++11 winapi filesystems

我有一个没有提升权限的进程,因此,我无法获取某些文件/目录的文件属性

const auto attr = GetFileAttributesW(path);

auto *pwfd = new WIN32_FIND_DATAW;
const auto handle = FindFirstFileW(path, pwfd);

在这两种情况下,我无法访问文件属性是有道理的,(因为我没有提升的权限)

但我只需要知道 path 是文件还是目录。

有没有办法知道有效路径是文件还是没有提升权限的目录?

编辑 它不是How can I tell if a given path is a directory or a file? (C/C++) 的拷贝,(例如),我已经知道如何获取文件属性了。

我想知道路径是否是没有提升权限的文件/目录。

最佳答案

首先 GetLastError 返回的 ERROR_ACCESS_DENIED 并不总是意味着访问被拒绝错误。通常(但不总是)错误源 - 是 NTSTATUS 代码,由 native api 返回,然后通过 RtlNtStatusToDosError 转换为 win32 错误代码.但这种转换不是单射的。许多不同的 NTSTATUS 代码转换为相同的 ERROR_ACCESS_DENIED,而不仅仅是 STATUS_ACCESS_DENIED,因此经常存在对错误真正原因的混淆。如果 GetFileAttributesFindFirstFileExW 失败,最好调用

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)。)- 这里什么也做不了。

但在具体情况下,我猜想操作实际上得到的不是 STATUS_ACCESS_DENIED 而是 STATUS_DELETE_PENDING 错误。如果有人调用 DeleteFile 但文件上仍然存在打开的句柄,则会出现此错误:

The DeleteFile function marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.

(在这种情况下,NTSTATUS 代码当然是 STATUS_DELETE_PENDING,但 RtlNtStatusToDosError 将其转换为 ERROR_ACCESS_DENIED)

GetFileAttributes 失败的可能原因是该文件在关闭时标记为删除。但如果文件仍未删除,可以通过 FindFirstFileExW 获取它的属性.注意 - FindFirstFileExW返回有关父文件夹中文件属性的信息。要调用此 api,我们只需要 FILE_LIST_DIRECTORY 访问父文件夹。这就是为什么如果我们有父文件夹的 FILE_LIST_DIRECTORY,文件系统会为文件授予 FILE_READ_ATTRIBUTES 的原因。如果在页面文件上调用 GetFileAttributes 也可能会失败并出现错误 STATUS_SHARING_VIOLATION(当然是非常特殊的情况)。如果我们没有 FILE_LIST_DIRECTORY 访问父文件夹(并且没有启用备份权限)- FindFirstFileExW 也会失败,这里什么也做不了。

如此可能的解决方案,为了不失败,当文件在关闭时标记为删除 - 在这种情况下调用 FindFirstFileExW。请注意,与 GetFileAttributes 相比,FindFirstFileExW 的调用成本要高得多,从另一方面来看,当我们查询标记为删除的文件时,这种情况非常罕见。所以总是最好先调用 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);
    }
}

关于c++ - 如何判断路径是文件还是没有提升权限的目录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52706859/

相关文章:

C++ 内联函数和重新声明

c++ - SDL 2.0 如何在一切之上生成纹理?

c++ - cpp make_shared 用于空指针

winapi - OpenGL 和 wglUseFontBitmaps 只绘制等距的字母

c++ - 想通过使用带变量的构造函数来声明数组

c++ - Boost Spirit 自动解析器因 double 元组而失败

c++ - 转换为指向模板的指针是否会实例化该模板?

c++11 - 在 C++11 中编写持有 STL 容器的类的构造函数的最佳方法

c++ - 由于构建错误 : 'STARTUPINFO' : undeclared identifier,无法使用 CreateProcess

c++ - 如何将 Win32 HRESULT 转换为 int 返回值?