diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2013-12-16 19:57:41 +0100 |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2013-12-16 19:57:41 +0100 |
commit | c274fd22edd2471b4f73f5cc690851cfbc716589 (patch) | |
tree | 3dc529eb9aed8034ba7a01dc3544328d6500f8df /Lib/pathlib.py | |
parent | d2e48ca813a70edc4a28477c150ad8079df584e6 (diff) | |
download | cpython-git-c274fd22edd2471b4f73f5cc690851cfbc716589.tar.gz |
Issue #19887: Improve the Path.resolve() algorithm to support certain symlink chains.
Original patch by Serhiy.
Diffstat (limited to 'Lib/pathlib.py')
-rw-r--r-- | Lib/pathlib.py | 75 |
1 files changed, 40 insertions, 35 deletions
diff --git a/Lib/pathlib.py b/Lib/pathlib.py index b404c1f023..9b4fde1d15 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -254,42 +254,47 @@ class _PosixFlavour(_Flavour): def resolve(self, path): sep = self.sep - def split(p): - return [x for x in p.split(sep) if x] - def absparts(p): - # Our own abspath(), since the posixpath one makes - # the mistake of "normalizing" the path without resolving the - # symlinks first. - if not p.startswith(sep): - return split(os.getcwd()) + split(p) - else: - return split(p) - parts = absparts(str(path))[::-1] accessor = path._accessor - resolved = cur = "" - symlinks = {} - while parts: - part = parts.pop() - cur = resolved + sep + part - if cur in symlinks and symlinks[cur] <= len(parts): - # We've already seen the symlink and there's not less - # work to do than the last time. - raise RuntimeError("Symlink loop from %r" % cur) - try: - target = accessor.readlink(cur) - except OSError as e: - if e.errno != EINVAL: - raise - # Not a symlink - resolved = cur - else: - # Take note of remaining work from this symlink - symlinks[cur] = len(parts) - if target.startswith(sep): - # Symlink points to absolute path - resolved = "" - parts.extend(split(target)[::-1]) - return resolved or sep + seen = {} + def _resolve(path, rest): + if rest.startswith(sep): + path = '' + + for name in rest.split(sep): + if not name or name == '.': + # current dir + continue + if name == '..': + # parent dir + path, _, _ = path.rpartition(sep) + continue + newpath = path + sep + name + if newpath in seen: + # Already seen this path + path = seen[newpath] + if path is not None: + # use cached value + continue + # The symlink is not resolved, so we must have a symlink loop. + raise RuntimeError("Symlink loop from %r" % newpath) + # Resolve the symbolic link + try: + target = accessor.readlink(newpath) + except OSError as e: + if e.errno != EINVAL: + raise + # Not a symlink + path = newpath + else: + seen[newpath] = None # not resolved symlink + path = _resolve(path, target) + seen[newpath] = path # resolved symlink + + return path + # NOTE: according to POSIX, getcwd() cannot contain path components + # which are symlinks. + base = '' if path.is_absolute() else os.getcwd() + return _resolve(base, str(path)) or sep def is_reserved(self, parts): return False |