summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Gijsbers <jlg@dds.nl>2004-08-14 15:01:53 (GMT)
committerJohannes Gijsbers <jlg@dds.nl>2004-08-14 15:01:53 (GMT)
commit4ec40648a56b661ca78f795aa0718836b6020e1b (patch)
tree8721483a04fbf218e56ca7299cd9eb5a9a50f7dc
parentf9a098efe1dcfba59d8bf2026892ee65454c29b6 (diff)
downloadcpython-4ec40648a56b661ca78f795aa0718836b6020e1b.zip
cpython-4ec40648a56b661ca78f795aa0718836b6020e1b.tar.gz
cpython-4ec40648a56b661ca78f795aa0718836b6020e1b.tar.bz2
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. Also add tests for infinite symlink loops and parent symlinks that need to be resolved.
-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.