如何正确使用MAXIMUM_ALLOWED?
How to use MAXIMUM_ALLOWED properly?
我创建了一个小框架,提供统一API到多个文件systems/APIs(即Win32,Posix,NFS)。说 API 有点类似于 Posix —— 要访问一个文件,您需要“打开”它,为预期目的提供提示(r
、w
或 rw
).类似于 open_file("/abc/log.txt", access::rw)
.
在这个框架中支持 Win32 API 让我很头疼,因为 Win32 的“声明性”性质——你应该预先知道你计划在给定句柄上执行哪些操作并通过相关 dwDesiredAccess
进入相关的 (Nt)CreateFile()
调用。不幸的是,除了通用 r/w/rw
提示外,框架不知道客户端将执行什么操作(即更改所有者、写入属性等)。而且我不愿意让 Win32 概念泄漏到我的框架中(即我不喜欢在我的 open_file()
中添加 dwDesiredAccess
等价物)。
这是我试过的方法:
1. MAXIMUM_ALLOWED
思路:用MAXIMUM_ALLOWED打开相关句柄--我会尽我所能,如果缺少某些权利,相关操作(例如set_mime()
) 将简单地以 access denied
.
失败
问题:
- 它不适用于只读文件或卷(
(Nt)CreateFile()
因 access denied
而失败)
- MSDN warns 如果正在对 FAT 卷进行碎片整理——尝试以这种方式打开目录将失败
- 一般来说,使用
MAXIMUM_ALLOWED
似乎出于某种原因不受欢迎
2。必要时重新打开对象
想法: 通过 GENERIC_READ
和 GENERIC_WRITE
表示 r/w/rw
以及所有需要额外访问权限的操作(例如 delete()
需要 DELETE
) 重新打开具有所需访问权限的对象。
问题:
- 重新打开对象并不便宜
- 通过第二个对象所做的更改可以被静默覆盖,例如:
set_mtime()
使用 FILE_WRITE_ATTRIBUTES|SYNCHRONIZE
重新打开文件
- 调用
NtSetInformationFile(... FileBasicInformation)
更新元数据并关闭句柄
- 稍后原始句柄关闭,导致数据刷新并静默覆盖之前由
set_mtime()
设置的 ModifiedTime
3。复制句柄而不是重新打开对象
想法:与上一节相同,但不是重新打开对象——复制原始句柄(要求新访问):
HANDLE h;
HANDLE hp = GetCurrentProcess();
CHECK_WIN32( DuplicateHandle(hp, hFile, hp, &h, FILE_WRITE_ATTRIBUTES|SYNCHRONIZE, FALSE, 0) );
问题:
- 每次我需要执行(non-plain-read/write)操作时都复制(并关闭)文件句柄似乎过多且有些昂贵
DuplicateHandle()
documentation 警告(未提供任何细节)请求额外访问 可能 失败。它在我检查过的所有用例中都运行良好(通常在使用 GENERIC_READ
打开的句柄上询问 DELETE
/FILE_WRITE_ATTRIBUTES
之类的东西),但显然 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。啊,是的,处理 delete()
中的 ReadOnly
属性。如果用户想打开只读文件进行写入,还没有决定怎么办...
我最终得到了这个:
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 客户端。
我创建了一个小框架,提供统一API到多个文件systems/APIs(即Win32,Posix,NFS)。说 API 有点类似于 Posix —— 要访问一个文件,您需要“打开”它,为预期目的提供提示(r
、w
或 rw
).类似于 open_file("/abc/log.txt", access::rw)
.
在这个框架中支持 Win32 API 让我很头疼,因为 Win32 的“声明性”性质——你应该预先知道你计划在给定句柄上执行哪些操作并通过相关 dwDesiredAccess
进入相关的 (Nt)CreateFile()
调用。不幸的是,除了通用 r/w/rw
提示外,框架不知道客户端将执行什么操作(即更改所有者、写入属性等)。而且我不愿意让 Win32 概念泄漏到我的框架中(即我不喜欢在我的 open_file()
中添加 dwDesiredAccess
等价物)。
这是我试过的方法:
1. MAXIMUM_ALLOWED
思路:用MAXIMUM_ALLOWED打开相关句柄--我会尽我所能,如果缺少某些权利,相关操作(例如set_mime()
) 将简单地以 access denied
.
问题:
- 它不适用于只读文件或卷(
(Nt)CreateFile()
因access denied
而失败) - MSDN warns 如果正在对 FAT 卷进行碎片整理——尝试以这种方式打开目录将失败
- 一般来说,使用
MAXIMUM_ALLOWED
似乎出于某种原因不受欢迎
2。必要时重新打开对象
想法: 通过 GENERIC_READ
和 GENERIC_WRITE
表示 r/w/rw
以及所有需要额外访问权限的操作(例如 delete()
需要 DELETE
) 重新打开具有所需访问权限的对象。
问题:
- 重新打开对象并不便宜
- 通过第二个对象所做的更改可以被静默覆盖,例如:
set_mtime()
使用FILE_WRITE_ATTRIBUTES|SYNCHRONIZE
重新打开文件
- 调用
NtSetInformationFile(... FileBasicInformation)
更新元数据并关闭句柄 - 稍后原始句柄关闭,导致数据刷新并静默覆盖之前由
set_mtime()
设置的
ModifiedTime
3。复制句柄而不是重新打开对象
想法:与上一节相同,但不是重新打开对象——复制原始句柄(要求新访问):
HANDLE h;
HANDLE hp = GetCurrentProcess();
CHECK_WIN32( DuplicateHandle(hp, hFile, hp, &h, FILE_WRITE_ATTRIBUTES|SYNCHRONIZE, FALSE, 0) );
问题:
- 每次我需要执行(non-plain-read/write)操作时都复制(并关闭)文件句柄似乎过多且有些昂贵
DuplicateHandle()
documentation 警告(未提供任何细节)请求额外访问 可能 失败。它在我检查过的所有用例中都运行良好(通常在使用GENERIC_READ
打开的句柄上询问DELETE
/FILE_WRITE_ATTRIBUTES
之类的东西),但显然 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。啊,是的,处理 delete()
中的 ReadOnly
属性。如果用户想打开只读文件进行写入,还没有决定怎么办...
我最终得到了这个:
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 客户端。