常规的DOS路径限制为
MAX_PATH
(260)个字符,包括字符串的终止符
NUL
。您可以通过使用以
\\?\
前缀开头的扩展长度路径来超过此限制。此路径必须是Unicode字符串,完全合格,并且只使用反斜杠作为路径分隔符。根据Microsoft的
文件系统功能比较,最大扩展路径长度为32760个字符。单个文件或目录名称最长可达255个字符(UDF文件系统为127个字符)。扩展UNC路径也支持
\\?\UNC\server\share
。
例如:
import os
def winapi_path(dos_path, encoding=None):
if (not isinstance(dos_path, unicode) and
encoding is not None):
dos_path = dos_path.decode(encoding)
path = os.path.abspath(dos_path)
if path.startswith(u"\\\\"):
return u"\\\\?\\UNC\\" + path[2:]
return u"\\\\?\\" + path
path = winapi_path(os.path.join(u"JSONFiles",
item["category"],
item["action"],
item["source"],
fileName + ".json"))
>>> path = winapi_path("C:\\Temp\\test.txt")
>>> print path
\\?\C:\Temp\test.txt
请参阅MSDN上的以下页面:
背景
Windows调用NT运行库函数RtlDosPathNameToRelativeNtPathName_U_WithStatus
将DOS路径转换为本地NT路径。如果我们在该函数上设置断点,然后使用open
(即CreateFile
)打开上述路径,我们可以看到它如何处理以\\?\
前缀开头的路径。
Breakpoint 0 hit
ntdll!RtlDosPathNameToRelativeNtPathName_U_WithStatus:
00007ff9`d1fb5880 4883ec58 sub rsp,58h
0:000> du @rcx
000000b4`52fc0f60 "\\?\C:\Temp\test.txt"
0:000> r rdx
rdx=000000b450f9ec18
0:000> pt
ntdll!RtlDosPathNameToRelativeNtPathName_U_WithStatus+0x66:
00007ff9`d1fb58e6 c3 ret
该结果将
\\?\
替换为NT DOS设备前缀
\??\
,并将字符串复制到本地的
UNICODE_STRING
中。
0:000> dS b450f9ec18
000000b4`536b7de0 "\??\C:\Temp\test.txt"
如果你使用//?/
而不是 \\?\
,那么路径仍然被限制在MAX_PATH
个字符。如果太长,则RtlDosPathNameToRelativeNtPathName
返回状态码STATUS_NAME_TOO_LONG
(0xC0000106)。
如果在前缀中使用\\?\
,但在路径的其余部分使用斜杠,则Windows不会为您将斜杠转换为反斜杠:
Breakpoint 0 hit
ntdll!RtlDosPathNameToRelativeNtPathName_U_WithStatus:
00007ff9`d1fb5880 4883ec58 sub rsp,58h
0:000> du @rcx
0000005b`c2ffbf30 "\\?\C:/Temp/test.txt"
0:000> r rdx
rdx=0000005bc0b3f068
0:000> pt
ntdll!RtlDosPathNameToRelativeNtPathName_U_WithStatus+0x66:
00007ff9`d1fb58e6 c3 ret
0:000> dS 5bc0b3f068
0000005b`c3066d30 "\??\C:/Temp/test.txt"
正斜杠是NT命名空间中的有效对象名称字符。它被Microsoft文件系统保留,但您可以在其他命名的内核对象中使用正斜杠,这些对象存储在\BaseNamedObjects
或\Sessions\[session number]\BaseNamedObjects
中。此外,我认为I/O管理器不会强制执行设备和文件名中保留字符的策略。这取决于设备。也许有人拥有一个实现允许在名称中使用正斜杠的命名空间的Windows设备。至少,您可以创建包含正斜杠的DOS设备名称。例如:
>>> kernel32 = ctypes.WinDLL('kernel32')
>>> kernel32.DefineDosDeviceW(0, u'My/Device', u'C:\\Temp')
>>> os.path.exists(u'\\\\?\\My/Device\\test.txt')
True
你可能会想知道
\??
代表什么。这曾经是DOS设备链接在对象命名空间中的一个实际目录,但从NT5开始(或NT4带终端服务),这变成了一个虚拟前缀。对象管理器通过首先检查登录会话在目录
\Sessions\0\DosDevices\[LOGON_SESSION_ID]
中的DOS设备链接,然后检查全局DOS设备链接在
\Global??
目录中来处理此前缀。
请注意,前者是登录会话,而不是Windows会话。登录会话目录都在Windows会话0(即Vista+中的服务会话)的
DosDevices
目录下。因此,如果您有一个映射到非提升登录的驱动器,则会发现它在提升的命令提示符中不可用,因为您的提升令牌实际上是针对另一个登录会话的。
DOS设备链接的一个例子是
\Global??\C:
=>
\Device\HarddiskVolume2
。在这种情况下,DOS
C:
驱动器实际上是指向
HarddiskVolume2
设备的符号链接。
这是系统处理路径打开文件的简要概述。由于我们调用了WinAPI的
CreateFile
,它将已转换的NT
UNICODE_STRING
存储在
OBJECT_ATTRIBUTES
结构中,并调用系统函数
NtCreateFile
。
0:000> g
Breakpoint 1 hit
ntdll!NtCreateFile:
00007ff9`d2023d70 4c8bd1 mov r10,rcx
0:000> !obja @r8
Obja +000000b450f9ec58 at 000000b450f9ec58:
Name is \??\C:\Temp\test.txt
OBJ_CASE_INSENSITIVE
NtCreateFile
调用 I/O 管理器函数 IoCreateFile
,后者又调用未记录的对象管理器 API ObOpenObjectByName
。这个 API 完成了解析路径的工作。对象管理器从 \??\C:\Temp\test.txt
开始。然后将其替换为 \Global??\C:Temp\test.txt
。接下来,它解析到 C:
符号链接并不得不重新开始 (重新解析) 最终路径 \Device\HarddiskVolume2\Temp\test.txt
。
一旦对象管理器到达
HarddiskVolume2
设备对象,解析就会移交给实现
Device
对象类型的I/O管理器。 I/O
Device
的
ParseProcedure
创建
File
对象和一个
I/O请求包(IRP),其中包含
主功能码IRP_MJ_CREATE
(打开/创建操作),以由设备堆栈处理。这通过
IoCallDriver
发送给设备驱动程序。如果设备实现重分析点(例如连接点,符号链接等)并且路径包含重分析点,则必须重新提交已解析的路径以从头开始进行解析。
设备驱动程序将使用进程令牌(或模拟的线程)的
SeChangeNotifyPrivilege
(几乎总是存在且启用),以便在遍历目录时绕过访问检查。但是,最终对设备和目标文件的访问必须由安全描述符允许,该描述符通过
SeAccessCheck
进行验证。除了FAT32等简单文件系统不支持文件安全性外。
os.path.join()
创建路径。 - Lafexlos