我创建了一个小型框架,为多个文件系统/API(即 Win32、Posix、NFS)提供统一的 API。所述API有点类似于Posix——要访问文件,您需要“打开”它,它提供了预期目的的提示(
r
、w
或rw
)。类似于open_file("/abc/log.txt", access::rw)
。
在这个框架中支持 Win32 API 让我很头疼,因为 Win32 的“声明性”性质——你应该预先知道你计划在给定的句柄上执行哪些操作,并将相关的
dwDesiredAccess
传递到相关的 (Nt)CreateFile()
调用中。不幸的是,除了通用的 r/w/rw
提示之外,框架不知道客户端将执行什么操作(即更改所有者、写入属性等)。而且我不愿意让 Win32 概念泄漏到我的框架中(即我不喜欢将 dwDesiredAccess
等效项添加到我的 open_file()
中)。
这是我尝试过的:
1。 MAXIMUM_ALLOWED
想法: 使用 MAXIMUM_ALLOWED 打开相关句柄——我将得到我能得到的一切,如果缺少某些权限,相关操作(例如
set_mtime()
)将简单地失败,并显示 access denied
。
问题:
(Nt)CreateFile()
失败并显示access denied
)MAXIMUM_ALLOWED
似乎不受欢迎2。必要时重新打开对象
想法: 虽然
r/w/rw
和 GENERIC_READ
表示 GENERIC_WRITE
,并且对于需要额外访问权限的所有操作(例如 delete()
需要 DELETE
),以所需的访问权限重新打开对象。
问题:
set_mtime()
使用 FILE_WRITE_ATTRIBUTES|SYNCHRONIZE
NtSetInformationFile(... FileBasicInformation)
更新元数据并关闭句柄ModifiedTime
设置的
set_mtime()
3.复制句柄而不是重新打开对象
想法:与上一节相同,但不是重新打开对象——复制原始句柄(请求新的访问权限):
HANDLE h;
HANDLE hp = GetCurrentProcess();
CHECK_WIN32( DuplicateHandle(hp, hFile, hp, &h, FILE_WRITE_ATTRIBUTES|SYNCHRONIZE, FALSE, 0) );
问题:
DuplicateHandle()
文档警告(未提供任何详细信息)请求额外访问权限可能失败。它在我检查过的所有用例中都工作得很好(通常要求在用 DELETE
打开的句柄上使用 FILE_WRITE_ATTRIBUTES
/GENERIC_READ
之类的东西),但显然 Win32 API 不提供任何保证:-/...否则方法似乎有效。
底线:
我正在寻找解决
MAXIMUM_ALLOWED
问题的方法。 (或者对替代方法的建议,也许?)
编辑:这是另一个原因为什么重新打开文件不是一个好主意。
无法可靠地使用
MAXIMUM_ALLOWED
——R/O 文件和卷会导致其出错。设计不佳的功能。
另一种方法是获得最小访问权限并根据需要“扩展”它(通过使用新的
dwAccessRequired
标志重新打开文件)。这不起作用:
如果您临时打开文件,通过新句柄所做的一些更改(例如
mtime
修改)将在稍后原始句柄关闭时被清除(并且底层内核对象将数据刷新到磁盘)
如果您尝试用新句柄替换旧句柄,这意味着昂贵的刷新(在旧句柄关闭时)+ MT 同步,这意味着我无法从多个线程有效地使用我的
file
对象(我知道现在由于 FILE_SYNCHRONOUS_IO_NONALERT
无论如何,所有操作都会序列化,但会在短期内修复)
唉,
DuplicateHandle()
无法授予新的访问权限——所以这也无济于事。
基本上,我需要的是一个线程安全的
BOOL ExtendAccess(HANDLE h, DWORD dwAdditionalAccess)
函数。看起来即使通过 NT API 也无法获得它——只能在内核模式下实现。
幸运的是,这个框架始终在特权帐户下使用,这意味着我可以启用
SE_BACKUP_NAME
、使用 FILE_OPEN_FOR_BACKUP_INTENT
、过度请求访问(在只读量的情况下具有最小的回退)并避免处理限制性 DACL。啊,是的,处理 ReadOnly
中的 delete()
属性。还没决定如果用户想打开只读文件进行写入该怎么办...
我最终得到了这个:
W32Handle open_file_(HANDLE hparent, UNICODE_STRING zwpath, access a, disposition d, truncate t)
{
...
ACCESS_MASK access = [a]() -> ACCESS_MASK {
switch(a)
{
case access::r : return GENERIC_READ;
case access::w : [[fallthrough]]; // MSDN suggests to use GENERIC_READ with GENERIC_WRITE over network (performance reasons)
case access::rw : return GENERIC_READ|GENERIC_WRITE;
}
UNREACHEABLE;
}();
constexpr DWORD write_access = FILE_WRITE_ATTRIBUTES|DELETE|WRITE_OWNER; // we want to always have these (for apply, unlink, chown, etc)
access |= write_access;
access |= SYNCHRONIZE|READ_CONTROL|ACCESS_SYSTEM_SECURITY; // add "read DACL/SACL" rights (for full_metadata)
ULONG flags = FILE_SYNCHRONOUS_IO_NONALERT|FILE_NON_DIRECTORY_FILE|FILE_OPEN_FOR_BACKUP_INTENT;
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &zwpath, 0, hparent, NULL);
HANDLE h;
IO_STATUS_BLOCK io;
NTSTATUS r = ZwCreateFile(&h, access, &oa, &io, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_VALID_FLAGS, disposition, flags, NULL, 0);
if (r == STATUS_SUCCESS) return W32Handle(h);
if (r == STATUS_MEDIA_WRITE_PROTECTED) // try again without write flags
{
access &= ~write_access;
r = ZwCreateFile(&h, access, &oa, &io, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_VALID_FLAGS, disposition, flags, NULL, 0);
if (r == STATUS_SUCCESS) return W32Handle(h);
}
HR_THROW_(HRESULT_FROM_NT(r), "%s: Failed to open file", __func__);
}
总体来说 API 很糟糕,特殊情况就像意大利面条一样。我希望我有自己的 SMB 客户端。