[3.9] bpo-43757: Make pathlib use os.path.realpath() to resolve symlinks in a path (GH-25264) (GH-135035)

Also adds a new "strict" argument to realpath() to avoid changing the default behaviour of pathlib while sharing the implementation.

(cherry-picked from commit baecfbd849)

Co-authored-by: Barney Gale <barney.gale@gmail.com>
This commit is contained in:
Łukasz Langa 2025-06-02 18:28:09 +02:00 committed by GitHub
parent 03ac445b11
commit 00af9794dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 192 additions and 124 deletions

View file

@ -385,16 +385,16 @@ def abspath(path):
# Return a canonical path (i.e. the absolute location of a file on the
# filesystem).
def realpath(filename):
def realpath(filename, *, strict=False):
"""Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path."""
filename = os.fspath(filename)
path, ok = _joinrealpath(filename[:0], filename, {})
path, ok = _joinrealpath(filename[:0], filename, strict, {})
return abspath(path)
# Join two paths, normalizing and eliminating any symbolic links
# encountered in the second path.
def _joinrealpath(path, rest, seen):
def _joinrealpath(path, rest, strict, seen):
if isinstance(path, bytes):
sep = b'/'
curdir = b'.'
@ -423,7 +423,15 @@ def _joinrealpath(path, rest, seen):
path = pardir
continue
newpath = join(path, name)
if not islink(newpath):
try:
st = os.lstat(newpath)
except OSError:
if strict:
raise
is_link = False
else:
is_link = stat.S_ISLNK(st.st_mode)
if not is_link:
path = newpath
continue
# Resolve the symbolic link
@ -434,10 +442,14 @@ def _joinrealpath(path, rest, seen):
# use cached value
continue
# The symlink is not resolved, so we must have a symlink loop.
# Return already resolved part + rest of the path unchanged.
return join(newpath, rest), False
if strict:
# Raise OSError(errno.ELOOP)
os.stat(newpath)
else:
# Return already resolved part + rest of the path unchanged.
return join(newpath, rest), False
seen[newpath] = None # not resolved symlink
path, ok = _joinrealpath(path, os.readlink(newpath), seen)
path, ok = _joinrealpath(path, os.readlink(newpath), strict, seen)
if not ok:
return join(path, rest), False
seen[newpath] = path # resolved symlink