summaryrefslogtreecommitdiff
path: root/Lib/posixpath.py
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2013-02-10 12:24:06 +0200
committerSerhiy Storchaka <storchaka@gmail.com>2013-02-10 12:24:06 +0200
commitf2619236ebc6ea2d0d0dc64f3b672896072e9dfc (patch)
tree7b31e89a6fb06cf196c436f245a14087f4341c42 /Lib/posixpath.py
parente35042d13a888aa1b5d94d109763f021b62d786b (diff)
parentd83c82440b85faa6aefdf56fce25f285d8164bb7 (diff)
downloadcpython-git-f2619236ebc6ea2d0d0dc64f3b672896072e9dfc.tar.gz
Issue #6975: os.path.realpath() now correctly resolves multiple nested symlinks on POSIX platforms.
Diffstat (limited to 'Lib/posixpath.py')
-rw-r--r--Lib/posixpath.py90
1 files changed, 49 insertions, 41 deletions
diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index 3c83704320..6390a8651c 100644
--- a/Lib/posixpath.py
+++ b/Lib/posixpath.py
@@ -363,51 +363,59 @@ def abspath(path):
def realpath(filename):
"""Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path."""
- if isinstance(filename, bytes):
+ path, ok = _joinrealpath(filename[:0], filename, {})
+ return abspath(path)
+
+# Join two paths, normalizing ang eliminating any symbolic links
+# encountered in the second path.
+def _joinrealpath(path, rest, seen):
+ if isinstance(path, bytes):
sep = b'/'
- empty = b''
+ curdir = b'.'
+ pardir = b'..'
else:
sep = '/'
- empty = ''
- if isabs(filename):
- bits = [sep] + filename.split(sep)[1:]
- else:
- bits = [empty] + filename.split(sep)
-
- for i in range(2, len(bits)+1):
- component = join(*bits[0:i])
- # Resolve symbolic links.
- if islink(component):
- resolved = _resolve_link(component)
- if resolved is None:
- # Infinite loop -- return original component + rest of the path
- return abspath(join(*([component] + bits[i:])))
+ curdir = '.'
+ pardir = '..'
+
+ if isabs(rest):
+ rest = rest[1:]
+ path = sep
+
+ while rest:
+ name, _, rest = rest.partition(sep)
+ if not name or name == curdir:
+ # current dir
+ continue
+ if name == pardir:
+ # parent dir
+ if path:
+ path = dirname(path)
else:
- newpath = join(*([resolved] + bits[i:]))
- return realpath(newpath)
-
- return abspath(filename)
-
-
-def _resolve_link(path):
- """Internal helper function. Takes a path and follows symlinks
- until we either arrive at something that isn't a symlink, or
- encounter a path we've seen before (meaning that there's a loop).
- """
- paths_seen = set()
- while islink(path):
- if path in paths_seen:
- # Already seen this path, so we must have a symlink loop
- return None
- paths_seen.add(path)
- # Resolve where the link points to
- resolved = os.readlink(path)
- if not isabs(resolved):
- dir = dirname(path)
- path = normpath(join(dir, resolved))
- else:
- path = normpath(resolved)
- return path
+ path = name
+ continue
+ newpath = join(path, name)
+ if not islink(newpath):
+ path = newpath
+ continue
+ # Resolve the symbolic link
+ 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.
+ # Return already resolved part + rest of the path unchanged.
+ return join(newpath, rest), False
+ seen[newpath] = None # not resolved symlink
+ path, ok = _joinrealpath(path, os.readlink(newpath), seen)
+ if not ok:
+ return join(path, rest), False
+ seen[newpath] = path # resolved symlink
+
+ return path, True
+
supports_unicode_filenames = (sys.platform == 'darwin')