diff options
author | Barney Gale <barney.gale@gmail.com> | 2024-06-05 18:54:50 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-05 18:54:50 +0100 |
commit | e83ce850f433fd8bbf8ff4e8d7649b942639db31 (patch) | |
tree | ca186e3e7520df9fa5f623f55dad31e34481f0d5 /Lib/posixpath.py | |
parent | 14e3c7071bd1add30d4b69b62e011c7d38aebd9b (diff) | |
download | cpython-e83ce850f433fd8bbf8ff4e8d7649b942639db31.tar.gz cpython-e83ce850f433fd8bbf8ff4e8d7649b942639db31.zip |
pathlib ABCs: remove duplicate `realpath()` implementation. (#119178)
Add private `posixpath._realpath()` function, which is a generic version of `realpath()` that can be parameterised with string tokens (`sep`, `curdir`, `pardir`) and query functions (`getcwd`, `lstat`, `readlink`). Also add support for limiting the number of symlink traversals.
In the private `pathlib._abc.PathBase` class, call `posixpath._realpath()` and remove our re-implementation of the same algorithm.
No change to any public APIs, either in `posixpath` or `pathlib`.
Co-authored-by: Nice Zombies <nineteendo19d0@gmail.com>
Diffstat (limited to 'Lib/posixpath.py')
-rw-r--r-- | Lib/posixpath.py | 40 |
1 files changed, 29 insertions, 11 deletions
diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 47b2aa572e5..fccca4e066b 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -22,6 +22,7 @@ defpath = '/bin:/usr/bin' altsep = None devnull = '/dev/null' +import errno import os import sys import stat @@ -401,7 +402,10 @@ symbolic links encountered in the path.""" curdir = '.' pardir = '..' getcwd = os.getcwd + return _realpath(filename, strict, sep, curdir, pardir, getcwd) +def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir, + getcwd=os.getcwd, lstat=os.lstat, readlink=os.readlink, maxlinks=None): # The stack of unresolved path parts. When popped, a special value of None # indicates that a symlink target has been resolved, and that the original # symlink path can be retrieved by popping again. The [::-1] slice is a @@ -418,6 +422,10 @@ symbolic links encountered in the path.""" # the same links. seen = {} + # Number of symlinks traversed. When the number of traversals is limited + # by *maxlinks*, this is used instead of *seen* to detect symlink loops. + link_count = 0 + while rest: name = rest.pop() if name is None: @@ -436,11 +444,19 @@ symbolic links encountered in the path.""" else: newpath = path + sep + name try: - st = os.lstat(newpath) + st = lstat(newpath) if not stat.S_ISLNK(st.st_mode): path = newpath continue - if newpath in seen: + elif maxlinks is not None: + link_count += 1 + if link_count > maxlinks: + if strict: + raise OSError(errno.ELOOP, os.strerror(errno.ELOOP), + newpath) + path = newpath + continue + elif newpath in seen: # Already seen this path path = seen[newpath] if path is not None: @@ -448,26 +464,28 @@ symbolic links encountered in the path.""" continue # The symlink is not resolved, so we must have a symlink loop. if strict: - # Raise OSError(errno.ELOOP) - os.stat(newpath) + raise OSError(errno.ELOOP, os.strerror(errno.ELOOP), + newpath) path = newpath continue - target = os.readlink(newpath) + target = readlink(newpath) except OSError: if strict: raise path = newpath continue # Resolve the symbolic link - seen[newpath] = None # not resolved symlink if target.startswith(sep): # Symlink target is absolute; reset resolved path. path = sep - # Push the symlink path onto the stack, and signal its specialness by - # also pushing None. When these entries are popped, we'll record the - # fully-resolved symlink target in the 'seen' mapping. - rest.append(newpath) - rest.append(None) + if maxlinks is None: + # Mark this symlink as seen but not fully resolved. + seen[newpath] = None + # Push the symlink path onto the stack, and signal its specialness + # by also pushing None. When these entries are popped, we'll + # record the fully-resolved symlink target in the 'seen' mapping. + rest.append(newpath) + rest.append(None) # Push the unresolved symlink target parts onto the stack. rest.extend(target.split(sep)[::-1]) |