Eryk Sun added the comment:

I overlooked some aspects of the problem:

* A short relative path may end up exceeding MAX_PATH when normalized as a 
fully qualified path.
* The working directory may be a UNC path or may already have the \\?\ prefix. 
It's not thread-safe to check this beforehand.
* A path that contains a reserved DOS device name results in a \\.\ path.
 
Thus on second thought I think it's safer to call GetFullPathNameW for all 
paths that lack the \\?\ prefix, and then copy the result if it needs to be 
prefixed by \\?\ or \\?\UNC. The final path, if it's a filesystem path, should 
always use the \\?\ namespace to ensure that functions such as shutil.rmtree 
won't fail. 

For example:

    _DOS_DEVICES = "\\\\.\\"
    _NT_DOS_DEVICES = "\\\\?\\"
    _NT_UNC_DEVICE = "\\\\?\\UNC"

    def winapi_path(path):
        path = os.fsdecode(path) or '.'
        if path.startswith(_NT_DOS_DEVICES):
            return path
        temp = os.path._getfullpathname(path)
        if temp.startswith((_NT_DOS_DEVICES, _DOS_DEVICES)):
            return path if temp == path else temp
        if temp.startswith('\\\\'):
            return _NT_UNC_DEVICE + temp[1:]
        return _NT_DOS_DEVICES + temp

For reference, here's a typical call pattern when Windows 10.0.10586 converts a 
DOS path to an NT path:

    RtlInitUnicodeStringEx
    RtlDosPathNameToRelativeNtPathName_U_WithStatus
        RtlInitUnicodeStringEx
        RtlDosPathNameToRelativeNtPathName
            RtlGetFullPathName_Ustr
            RtlDetermineDosPathNameType_Ustr
            RtlAllocateHeap
            memcpy

RtlGetFullPathName_Ustr is called with a buffer that's sizeof(WCHAR) * MAX_PATH 
bytes. GetFullPathNameW also calls RtlGetFullPathName_Ustr, but with a 
caller-supplied buffer that can be up to sizeof(WCHAR) * 32768 bytes.

Here's the call pattern for a \\?\ path:

    RtlInitUnicodeStringEx
    RtlDosPathNameToRelativeNtPathName_U_WithStatus
        RtlInitUnicodeStringEx
        RtlDosPathNameToRelativeNtPathName
            RtlpWin32NtNameToNtPathName
                RtlAllocateHeap
                RtlAppendUnicodeStringToString
                RtlAppendUnicodeStringToString

RtlpWin32NtNameToNtPathName copies the path, replacing \\? with the object 
manager's \?? virtual DOS devices directory. 

Here's some background information for those who don't already know the basics 
of how Windows implements DOS devices in NT's object namespace, which you can 
explore using Microsoft's free WinObj tool.

In Windows NT 3 and 4 (before Terminal Services) there was a single \DosDevices 
directory, which is where the system created DOS device links to the actual NT 
devices in \Device, such as C: => \Device\HarddiskVolume2. Windows 2000 changed 
this in ways that were problematic, mostly due to using a per-session directory 
instead of a per-logon directory. (Tokens for multiple logon sessions can be 
used in a single Windows session, and almost always are since UAC split tokens 
arrived in Vista.) 

The design was changed again in Windows XP. \DosDevices is now just a link to 
the virtual \?? directory. The system creates DOS devices in a local (per 
logon) directory, except for system threads and LocalSystem logons (typically 
services), which use the \GLOBAL?? directory. The per-logon directories are 
located at \Sessions\0\DosDevices\[LogonAuthenticationId]. The object manager 
parses \?? by first checking the local DOS devices and then the global DOS 
devices. Each local DOS devices directory also has a Global link back to 
\GLOBAL??. It's accessible as \\?\Global\[Device Name], which is useful when a 
local device has the same name as a global one. The root directory of the 
object namespace is accessible to administrators using the \GLOBAL??\GLOBALROOT 
link, which from the Windows API is \\?\GLOBALROOT.

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue27730>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to