Skip to content

Commit

Permalink
pythonGH-117546: Fix symlink resolution in `os.path.realpath('loop/..…
Browse files Browse the repository at this point in the history
…/link')` (python#117568)

Continue resolving symlink targets after encountering a symlink loop, which
matches coreutils `realpath` behaviour.
  • Loading branch information
barneygale authored Apr 10, 2024
1 parent 6bc0b33 commit 630df37
Show file tree
Hide file tree
Showing 4 changed files with 7 additions and 17 deletions.
5 changes: 2 additions & 3 deletions Doc/library/os.path.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 2 additions & 13 deletions Lib/posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
Expand All @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix issue where :func:`os.path.realpath` stopped resolving symlinks after
encountering a symlink loop on POSIX.

0 comments on commit 630df37

Please sign in to comment.