From 630df37116b1c5b381984c547ef9d23792ceb464 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Wed, 10 Apr 2024 18:17:18 +0100 Subject: [PATCH] GH-117546: Fix symlink resolution in `os.path.realpath('loop/../link')` (#117568) Continue resolving symlink targets after encountering a symlink loop, which matches coreutils `realpath` behaviour. --- Doc/library/os.path.rst | 5 ++--- Lib/posixpath.py | 15 ++------------- Lib/test/test_posixpath.py | 2 +- ...2024-04-05-13-38-53.gh-issue-117546.lWjhHE.rst | 2 ++ 4 files changed, 7 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-04-05-13-38-53.gh-issue-117546.lWjhHE.rst diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index fcf4b6d68e018c..ebeb3bb50b8b1f 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -409,9 +409,8 @@ the :mod:`glob` module.) style names such as ``C:\\PROGRA~1`` to ``C:\\Program Files``. If a path doesn't exist or a symlink loop is encountered, and *strict* is - ``True``, :exc:`OSError` is raised. If *strict* is ``False``, the path is - resolved as far as possible and any remainder is appended without checking - whether it exists. + ``True``, :exc:`OSError` is raised. If *strict* is ``False`` these errors + are ignored, and so the result might be missing or otherwise inaccessible. .. note:: This function emulates the operating system's procedure for making a path diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 79e65587e66282..8fd49cdc358908 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -431,11 +431,6 @@ def realpath(filename, *, strict=False): # the same links. seen = {} - # Whether we're calling lstat() and readlink() to resolve symlinks. If we - # encounter an OSError for a symlink loop in non-strict mode, this is - # switched off. - querying = True - while rest: name = rest.pop() if name is None: @@ -453,9 +448,6 @@ def realpath(filename, *, strict=False): newpath = path + name else: newpath = path + sep + name - if not querying: - path = newpath - continue try: st = os.lstat(newpath) if not stat.S_ISLNK(st.st_mode): @@ -477,11 +469,8 @@ def realpath(filename, *, strict=False): if strict: # Raise OSError(errno.ELOOP) os.stat(newpath) - else: - # Return already resolved part + rest of the path unchanged. - path = newpath - querying = False - continue + path = newpath + continue seen[newpath] = None # not resolved symlink target = os.readlink(newpath) if target.startswith(sep): diff --git a/Lib/test/test_posixpath.py b/Lib/test/test_posixpath.py index ff78410738022d..248fe2cc5d5ca8 100644 --- a/Lib/test/test_posixpath.py +++ b/Lib/test/test_posixpath.py @@ -484,7 +484,7 @@ def test_realpath_symlink_loops(self): self.assertEqual(realpath(ABSTFN+"1/../x"), dirname(ABSTFN) + "/x") os.symlink(ABSTFN+"x", ABSTFN+"y") self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "y"), - ABSTFN + "y") + ABSTFN + "x") self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "1"), ABSTFN + "1") diff --git a/Misc/NEWS.d/next/Library/2024-04-05-13-38-53.gh-issue-117546.lWjhHE.rst b/Misc/NEWS.d/next/Library/2024-04-05-13-38-53.gh-issue-117546.lWjhHE.rst new file mode 100644 index 00000000000000..9762991e47a6a4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-04-05-13-38-53.gh-issue-117546.lWjhHE.rst @@ -0,0 +1,2 @@ +Fix issue where :func:`os.path.realpath` stopped resolving symlinks after +encountering a symlink loop on POSIX.