[issue32434] pathlib.WindowsPath.reslove(strict=False) returns absoulte path only if at least one component exists

Eryk Sun report at bugs.python.org
Fri Dec 29 03:43:19 EST 2017


Eryk Sun <eryksun at gmail.com> added the comment:

resolve() has additional problems, which possibly could be addressed all at once because it's a small method and the problems are closely related.

For an empty path it returns os.getcwd(). I don't think this case is possible in the normal way a Path gets constructed. Anyway, returning the unresolved working directory is incorrect. Windows is not a POSIX OS. WinAPI [Set,Get]CurrentDirectory does not ensure the working directory is a resolved path. An empty path should be replaced with "." and processed normally.

It fails when access is denied. _getfinalpathname needs to open a handle, and CreateFile requires at least the right to read file attributes and synchronize. For example, resolve() fails for a path in another user's profile, which grants access only to the user, system, and administrators.

It doesn't keep the "\\\\?\\" extended-path prefix when the source path already has it. It should only strip the prefix if the source path doesn't have it. That said, the resolved path may actually require it (i.e. long paths, DOS device names, trailing spaces), so maybe it should  never be removed.

Its behavior is inconsistent for invalid paths. For example, if "C:/Temp" exists and "C:/Spam" does not, then resolving "C:/Temp/bad?" raises whereas "C:/Spam/bad?" does not. IMO, it's simpler if neither raises in non-strict mode, at least not in _WindowsFlavour.resolve. Malformed paths will slip through, but raising an exception in those cases should be the job of the constructor. 

It fails to handle device paths. For example, C:/Temp/nul" resolves to the "NUL" device if "C:/Temp" exists. In this case _getfinalpathname will typically fail, either due to an invalid function or an invalid parameter. These errors, along with the error for an invalid filename, get lumped into the CRT's default EINVAL error code.

Also, GetFinalPathNameByHandle was added in Vista, so _getfinalpathname is always available in 3.5+. There's no need to use it conditionally.

Here's a prototype that addresses these issues:

    import nt
    import os
    import errno

    def _is_extended(path):
        return path.startswith('\\\\?\\')

    def _extended_to_normal(path):
        if _is_extended(path):
            path = path[4:]
            if path.startswith('UNC\\'):
                path = '\\' + path[3:]
        return path

    def _getfinalpathname(path):
        if not path:
            path = '.'
        elif _is_extended(path):
            return nt._getfinalpathname(path)
        return _extended_to_normal(nt._getfinalpathname(path))

    def resolve(path, strict=False):
        s = str(path)
        if strict:
            return _getfinalpathname(s)
        # Non-strict mode resolves as much as possible while retaining
        # tail components that cannot be resolved if they're missing,
        # inaccessible, or invalid.
        tail_parts = []
        while True:
            try:
                s = _getfinalpathname(s)
                break
            except OSError as e:
                if not (isinstance(e, (FileNotFoundError, PermissionError)) or
                        e.errno == errno.EINVAL):
                    raise
            head, tail = os.path.split(s)
            if head == s:
                return path.absolute()
            s = head
            tail_parts.append(tail)
        if tail_parts:
            s = os.path.join(s, *reversed(tail_parts))
        return s

----------

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue32434>
_______________________________________


More information about the Python-bugs-list mailing list