diff -r f316e6d6271a Lib/posixpath.py --- a/Lib/posixpath.py Mon Mar 21 10:32:02 2011 +0100 +++ b/Lib/posixpath.py Mon Mar 21 03:26:48 2011 -0700 @@ -354,46 +354,44 @@ def realpath(filename): """Return the canonical path of the specified filename, eliminating any -symbolic links encountered in the path.""" - if isabs(filename): - bits = ['/'] + filename.split('/')[1:] - else: - bits = [''] + filename.split('/') - - 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 + cwd = os.getcwdu() if isinstance(filename, unicode) else os.getcwd() + 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 = (sys.platform == 'darwin') diff -r f316e6d6271a Lib/test/test_posixpath.py --- a/Lib/test/test_posixpath.py Mon Mar 21 10:32:02 2011 +0100 +++ b/Lib/test/test_posixpath.py Mon Mar 21 03:26:48 2011 -0700 @@ -309,6 +309,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: