summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/posixpath.py16
-rw-r--r--Lib/test/test_posixpath.py97
-rw-r--r--Misc/NEWS6
3 files changed, 109 insertions, 10 deletions
diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index 557f8f2..345a742 100644
--- a/Lib/posixpath.py
+++ b/Lib/posixpath.py
@@ -400,9 +400,11 @@ def abspath(path):
def realpath(filename):
"""Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path."""
- filename = abspath(filename)
-
- bits = ['/'] + filename.split('/')[1:]
+ 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.
@@ -410,13 +412,13 @@ symbolic links encountered in the path."""
resolved = _resolve_link(component)
if resolved is None:
# Infinite loop -- return original component + rest of the path
- return join(*([component] + bits[i:]))
+ return abspath(join(*([component] + bits[i:])))
else:
newpath = join(*([resolved] + bits[i:]))
- return realpath(newpath)
-
- return filename
+ return realpath(newpath)
+ return abspath(filename)
+
def _resolve_link(path):
"""Internal helper function. Takes a path and follows symlinks
diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py
index fcf2ef5..75cd50b 100644
--- a/Lib/test/test_posixpath.py
+++ b/Lib/test/test_posixpath.py
@@ -2,6 +2,12 @@ import unittest
from test import test_support
import posixpath, os
+from posixpath import realpath, abspath, join, dirname, basename
+
+# An absolute path to a temporary filename for testing. We can't rely on TESTFN
+# being an absolute path, so we need this.
+
+ABSTFN = abspath(test_support.TESTFN)
class PosixPathTest(unittest.TestCase):
@@ -389,9 +395,96 @@ class PosixPathTest(unittest.TestCase):
self.assertRaises(TypeError, posixpath.abspath)
def test_realpath(self):
- self.assert_("foo" in posixpath.realpath("foo"))
-
+ self.assert_("foo" in realpath("foo"))
self.assertRaises(TypeError, posixpath.realpath)
+
+ if hasattr(os, "symlink"):
+ def test_realpath_basic(self):
+ # Basic operation.
+ try:
+ os.symlink(ABSTFN+"1", ABSTFN)
+ self.assertEqual(realpath(ABSTFN), ABSTFN+"1")
+ finally:
+ self.safe_remove(ABSTFN)
+
+ def test_realpath_symlink_loops(self):
+ # Bug #930024, return the path unchanged if we get into an infinite
+ # symlink loop.
+ try:
+ old_path = abspath('.')
+ os.symlink(ABSTFN, ABSTFN)
+ self.assertEqual(realpath(ABSTFN), ABSTFN)
+
+ os.symlink(ABSTFN+"1", ABSTFN+"2")
+ os.symlink(ABSTFN+"2", ABSTFN+"1")
+ self.assertEqual(realpath(ABSTFN+"1"), ABSTFN+"1")
+ self.assertEqual(realpath(ABSTFN+"2"), ABSTFN+"2")
+
+ # Test using relative path as well.
+ os.chdir(dirname(ABSTFN))
+ self.assertEqual(realpath(basename(ABSTFN)), ABSTFN)
+ finally:
+ os.chdir(old_path)
+ self.safe_remove(ABSTFN)
+ self.safe_remove(ABSTFN+"1")
+ self.safe_remove(ABSTFN+"2")
+
+ def test_realpath_resolve_parents(self):
+ # We also need to resolve any symlinks in the parents of a relative
+ # path passed to realpath. E.g.: current working directory is
+ # /usr/doc with 'doc' being a symlink to /usr/share/doc. We call
+ # realpath("a"). This should return /usr/share/doc/a/.
+ try:
+ old_path = abspath('.')
+ os.mkdir(ABSTFN)
+ os.mkdir(ABSTFN + "/y")
+ os.symlink(ABSTFN + "/y", ABSTFN + "/k")
+
+ os.chdir(ABSTFN + "/k")
+ self.assertEqual(realpath("a"), ABSTFN + "/y/a")
+ finally:
+ os.chdir(old_path)
+ self.safe_remove(ABSTFN + "/k")
+ self.safe_rmdir(ABSTFN + "/y")
+ self.safe_rmdir(ABSTFN)
+
+ def test_realpath_resolve_before_normalizing(self):
+ # Bug #990669: Symbolic links should be resolved before we
+ # normalize the path. E.g.: if we have directories 'a', 'k' and 'y'
+ # in the following hierarchy:
+ # a/k/y
+ #
+ # and a symbolic link 'link-y' pointing to 'y' in directory 'a',
+ # then realpath("link-y/..") should return 'k', not 'a'.
+ try:
+ old_path = abspath('.')
+ os.mkdir(ABSTFN)
+ os.mkdir(ABSTFN + "/k")
+ os.mkdir(ABSTFN + "/k/y")
+ os.symlink(ABSTFN + "/k/y", ABSTFN + "/link-y")
+
+ # Absolute path.
+ self.assertEqual(realpath(ABSTFN + "/link-y/.."), ABSTFN + "/k")
+ # Relative path.
+ os.chdir(dirname(ABSTFN))
+ self.assertEqual(realpath(basename(ABSTFN) + "/link-y/.."), ABSTFN + "/k")
+ finally:
+ os.chdir(old_path)
+ self.safe_remove(ABSTFN + "/link-y")
+ self.safe_rmdir(ABSTFN + "/k/y")
+ self.safe_rmdir(ABSTFN + "/k")
+ self.safe_rmdir(ABSTFN)
+
+ # Convenience functions for removing temporary files.
+ def pass_os_error(self, func, filename):
+ try: func(filename)
+ except OSError: pass
+
+ def safe_remove(self, filename):
+ self.pass_os_error(os.remove, filename)
+
+ def safe_rmdir(self, dirname):
+ self.pass_os_error(os.rmdir, dirname)
def test_main():
test_support.run_unittest(PosixPathTest)
diff --git a/Misc/NEWS b/Misc/NEWS
index 04ab6dd..ab83648 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -41,6 +41,10 @@ Extension modules
Library
-------
+- bug #990669: os.path.realpath() will resolve symlinks before normalizing the
+ path, as normalizing the path may alter the meaning of the path if it
+ contains symlinks.
+
- bug #851123: shutil.copyfile will raise an exception when trying to copy a
file onto a link to itself. Thanks Gregory Ball.
@@ -77,7 +81,7 @@ C API
Documentation
-------------
-- bug 990669: os.path.normpath may alter the meaning of a path if it contains
+- bug #990669: os.path.normpath may alter the meaning of a path if it contains
symbolic links. This has been documented in a comment since 1992, but is now in
the library reference as well.