diff -r c5de208d205c Lib/posixpath.py --- a/Lib/posixpath.py Mon Mar 28 13:50:41 2011 +0300 +++ b/Lib/posixpath.py Mon Mar 28 10:36:27 2011 -0700 @@ -377,52 +377,50 @@ def realpath(filename): """Return the canonical path of the specified filename, eliminating any -symbolic links encountered in the path.""" - if isinstance(filename, bytes): - sep = b'/' - empty = 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:]))) - 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). + symbolic links encountered in the path. """ 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)) + current = [] # the working stack + # get abspath of filename without normalization + if isinstance(filename, bytes): + cwd = os.getcwdb() + _sep = sep.encode(sys.getfilesystemencoding()) + else: + cwd = os.getcwd() + _sep = sep + components = join(cwd, filename).split(_sep) + while len(components): + comp = components.pop(0) + if comp == '.': + continue + elif comp == '..': + current.pop() else: - path = normpath(resolved) - return path + current.append(comp) + + # join what we have so far + cwd = _sep.join(current) + if islink(cwd): + if cwd in paths_seen: + # already seen this path, so we must have a symlink loop + return abspath(filename) + paths_seen.add(cwd) + # resolve the symlink + tmp = os.readlink(cwd) + tmplist = tmp.split(_sep) + # insert the symlink expansion into the components stack + components = tmplist + components + if isabs(tmp): + # symlink to an abs path; start over because there might be + # another symlink inside the target abs path + current = [] + else: + # pop the previously pushed symlink; continue popping + # components, starting with the recently pushed expansion + current.pop() + + return _sep.join(current) + supports_unicode_filenames = False diff -r c5de208d205c Lib/test/test_posixpath.py --- a/Lib/test/test_posixpath.py Mon Mar 28 13:50:41 2011 +0300 +++ b/Lib/test/test_posixpath.py Mon Mar 28 10:36:27 2011 -0700 @@ -588,6 +588,30 @@ safe_rmdir(ABSTFN + "/k") safe_rmdir(ABSTFN) + def test_realpath_issue11397(self): + # Issue #11397: realpath() fails to return the correct real path of + # some tree of symlinks. + try: + old_path = abspath('.') + os.mkdir(ABSTFN) + os.chdir(ABSTFN) + os.mkdir(ABSTFN + '/a') + os.symlink('../a', 'a/blink') + os.makedirs(ABSTFN + '/a/c/d') + os.symlink('a/c/d/..', 'xlink') # xlink -> /a/c + os.symlink('xlink/../blink', 'zlink') # zlink -> /a/blink -> /a + + self.assertEqual(realpath('zlink'), ABSTFN + '/a') + finally: + os.chdir(old_path) + test_support.unlink(ABSTFN + '/zlink') + test_support.unlink(ABSTFN + '/xlink') + test_support.unlink(ABSTFN + '/a/blink') + safe_rmdir(ABSTFN + '/a/c/d') + safe_rmdir(ABSTFN + '/a/c') + safe_rmdir(ABSTFN + '/a') + safe_rmdir(ABSTFN) + def test_relpath(self): (real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar") try: