cpython: f42cabe6ccb5 (original) (raw)
Mercurial > cpython
changeset 82121:f42cabe6ccb5
Issue #6975: os.path.realpath() now correctly resolves multiple nested symlinks on POSIX platforms. [#6975]
Serhiy Storchaka storchaka@gmail.com | |
---|---|
date | Sun, 10 Feb 2013 12:24:06 +0200 |
parents | bd6a2fcc7711(current diff)bfe9526606e2(diff) |
children | 12d75ca12ae7 |
files | Lib/posixpath.py Lib/test/test_posixpath.py Misc/NEWS |
diffstat | 3 files changed, 105 insertions(+), 39 deletions(-)[+] [-] Lib/posixpath.py 86 Lib/test/test_posixpath.py 55 Misc/NEWS 3 |
line wrap: on
line diff
--- 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."""
+ +# Join two paths, normalizing ang eliminating any symbolic links +# encountered in the second path. +def _joinrealpath(path, rest, seen):
empty = b''[](#l1.16)
empty = ''[](#l1.21)
- if isabs(filename):
bits = [sep] + filename.split(sep)[1:][](#l1.23)
- else:
bits = [empty] + filename.split(sep)[](#l1.25)
curdir = '.'[](#l1.26)
pardir = '..'[](#l1.27)
- for i in range(2, len(bits)+1):
component = join(*bits[0:i])[](#l1.30)
# Resolve symbolic links.[](#l1.31)
if islink(component):[](#l1.32)
resolved = _resolve_link(component)[](#l1.33)
if resolved is None:[](#l1.34)
# Infinite loop -- return original component + rest of the path[](#l1.35)
return abspath(join(*([component] + bits[i:])))[](#l1.36)
else:[](#l1.37)
newpath = join(*([resolved] + bits[i:]))[](#l1.38)
return realpath(newpath)[](#l1.39)
- while rest:
name, _, rest = rest.partition(sep)[](#l1.47)
if not name or name == curdir:[](#l1.48)
# current dir[](#l1.49)
continue[](#l1.50)
if name == pardir:[](#l1.51)
# parent dir[](#l1.52)
if path:[](#l1.53)
path = dirname(path)[](#l1.54)
else:[](#l1.55)
path = name[](#l1.56)
continue[](#l1.57)
newpath = join(path, name)[](#l1.58)
if not islink(newpath):[](#l1.59)
path = newpath[](#l1.60)
continue[](#l1.61)
# Resolve the symbolic link[](#l1.62)
if newpath in seen:[](#l1.63)
# Already seen this path[](#l1.64)
path = seen[newpath][](#l1.65)
if path is not None:[](#l1.66)
# use cached value[](#l1.67)
continue[](#l1.68)
# The symlink is not resolved, so we must have a symlink loop.[](#l1.69)
# Return already resolved part + rest of the path unchanged.[](#l1.70)
return join(newpath, rest), False[](#l1.71)
seen[newpath] = None # not resolved symlink[](#l1.72)
path, ok = _joinrealpath(path, os.readlink(newpath), seen)[](#l1.73)
if not ok:[](#l1.74)
return join(path, rest), False[](#l1.75)
seen[newpath] = path # resolved symlink[](#l1.76)
- """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:[](#l1.85)
# Already seen this path, so we must have a symlink loop[](#l1.86)
return None[](#l1.87)
paths_seen.add(path)[](#l1.88)
# Resolve where the link points to[](#l1.89)
resolved = os.readlink(path)[](#l1.90)
if not isabs(resolved):[](#l1.91)
dir = dirname(path)[](#l1.92)
path = normpath(join(dir, resolved))[](#l1.93)
else:[](#l1.94)
path = normpath(resolved)[](#l1.95)
- return path
+ supports_unicode_filenames = (sys.platform == 'darwin')
--- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -320,6 +320,22 @@ class PosixPathTest(unittest.TestCase): self.assertEqual(realpath(ABSTFN+"1"), ABSTFN+"1") self.assertEqual(realpath(ABSTFN+"2"), ABSTFN+"2")
self.assertEqual(realpath(ABSTFN+"1/x"), ABSTFN+"1/x")[](#l2.7)
self.assertEqual(realpath(ABSTFN+"1/.."), dirname(ABSTFN))[](#l2.8)
self.assertEqual(realpath(ABSTFN+"1/../x"), dirname(ABSTFN) + "/x")[](#l2.9)
os.symlink(ABSTFN+"x", ABSTFN+"y")[](#l2.10)
self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "y"),[](#l2.11)
ABSTFN + "y")[](#l2.12)
self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "1"),[](#l2.13)
ABSTFN + "1")[](#l2.14)
os.symlink(basename(ABSTFN) + "a/b", ABSTFN+"a")[](#l2.16)
self.assertEqual(realpath(ABSTFN+"a"), ABSTFN+"a/b")[](#l2.17)
os.symlink("../" + basename(dirname(ABSTFN)) + "/" +[](#l2.19)
basename(ABSTFN) + "c", ABSTFN+"c")[](#l2.20)
self.assertEqual(realpath(ABSTFN+"c"), ABSTFN+"c")[](#l2.21)
+ # Test using relative path as well. os.chdir(dirname(ABSTFN)) self.assertEqual(realpath(basename(ABSTFN)), ABSTFN) @@ -328,6 +344,45 @@ class PosixPathTest(unittest.TestCase): support.unlink(ABSTFN) support.unlink(ABSTFN+"1") support.unlink(ABSTFN+"2")
support.unlink(ABSTFN+"y")[](#l2.30)
support.unlink(ABSTFN+"c")[](#l2.31)
- @unittest.skipUnless(hasattr(os, "symlink"),
"Missing symlink implementation")[](#l2.34)
- @skip_if_ABSTFN_contains_backslash
- def test_realpath_repeated_indirect_symlinks(self):
# Issue #6975.[](#l2.37)
try:[](#l2.38)
os.mkdir(ABSTFN)[](#l2.39)
os.symlink('../' + basename(ABSTFN), ABSTFN + '/self')[](#l2.40)
os.symlink('self/self/self', ABSTFN + '/link')[](#l2.41)
self.assertEqual(realpath(ABSTFN + '/link'), ABSTFN)[](#l2.42)
finally:[](#l2.43)
support.unlink(ABSTFN + '/self')[](#l2.44)
support.unlink(ABSTFN + '/link')[](#l2.45)
safe_rmdir(ABSTFN)[](#l2.46)
- @unittest.skipUnless(hasattr(os, "symlink"),
"Missing symlink implementation")[](#l2.49)
- @skip_if_ABSTFN_contains_backslash
- def test_realpath_deep_recursion(self):
depth = 10[](#l2.52)
old_path = abspath('.')[](#l2.53)
try:[](#l2.54)
os.mkdir(ABSTFN)[](#l2.55)
for i in range(depth):[](#l2.56)
os.symlink('/'.join(['%d' % i] * 10), ABSTFN + '/%d' % (i + 1))[](#l2.57)
os.symlink('.', ABSTFN + '/0')[](#l2.58)
self.assertEqual(realpath(ABSTFN + '/%d' % depth), ABSTFN)[](#l2.59)
# Test using relative path as well.[](#l2.61)
os.chdir(ABSTFN)[](#l2.62)
self.assertEqual(realpath('%d' % depth), ABSTFN)[](#l2.63)
finally:[](#l2.64)
os.chdir(old_path)[](#l2.65)
for i in range(depth + 1):[](#l2.66)
support.unlink(ABSTFN + '/%d' % i)[](#l2.67)
safe_rmdir(ABSTFN)[](#l2.68)
@unittest.skipUnless(hasattr(os, "symlink"), "Missing symlink implementation")
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -244,6 +244,9 @@ Core and Builtins Library ------- +- Issue #6975: os.path.realpath() now correctly resolves multiple nested