Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
bpo-43757: make pathlib use os.path.realpath() to resolve all symlinks in a path#25264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
164dbfd2894e0a495bd8b492c66bc8bac15fe696884114be92a594cefb36a400634486600fc9e047471cbf3a3f7f839db02d30bb768c65339fa60ebf979947a82bc1886c318c7362f7e72147c5df04357File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -14,12 +14,6 @@ | ||
| from urllib.parse import quote_from_bytes as urlquote_from_bytes | ||
| if os.name == 'nt': | ||
| from nt import _getfinalpathname | ||
| else: | ||
| _getfinalpathname = None | ||
| __all__ = [ | ||
| "PurePath", "PurePosixPath", "PureWindowsPath", | ||
| "Path", "PosixPath", "WindowsPath", | ||
| @@ -29,14 +23,17 @@ | ||
| # Internals | ||
| # | ||
| _WINERROR_NOT_READY = 21 # drive exists but is not accessible | ||
| _WINERROR_INVALID_NAME = 123 # fix for bpo-35306 | ||
| _WINERROR_CANT_RESOLVE_FILENAME = 1921 # broken symlink pointing to itself | ||
| # EBADF - guard against macOS `stat` throwing EBADF | ||
| _IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF, ELOOP) | ||
| _IGNORED_WINERRORS = ( | ||
| 21, # ERROR_NOT_READY - drive exists but is not accessible | ||
| 123, # ERROR_INVALID_NAME - fix for bpo-35306 | ||
| 1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself | ||
| ) | ||
| _WINERROR_NOT_READY, | ||
| _WINERROR_INVALID_NAME, | ||
| _WINERROR_CANT_RESOLVE_FILENAME) | ||
| def _ignore_error(exception): | ||
| return (getattr(exception, 'errno', None) in _IGNORED_ERROS or | ||
| @@ -186,30 +183,6 @@ def casefold_parts(self, parts): | ||
| def compile_pattern(self, pattern): | ||
| return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch | ||
| def resolve(self, path, strict=False): | ||
| s = str(path) | ||
| if not s: | ||
| return path._accessor.getcwd() | ||
| previous_s = None | ||
| if _getfinalpathname is not None: | ||
| if strict: | ||
| return self._ext_to_normal(_getfinalpathname(s)) | ||
| else: | ||
| tail_parts = [] # End of the path after the first one not found | ||
| while True: | ||
| try: | ||
| s = self._ext_to_normal(_getfinalpathname(s)) | ||
| except FileNotFoundError: | ||
| previous_s = s | ||
| s, tail = os.path.split(s) | ||
| tail_parts.append(tail) | ||
| if previous_s == s: | ||
| return path | ||
| else: | ||
| return os.path.join(s, *reversed(tail_parts)) | ||
| # Means fallback on absolute | ||
| return None | ||
| def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): | ||
| prefix = '' | ||
| if s.startswith(ext_prefix): | ||
| @@ -220,10 +193,6 @@ def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): | ||
| s = '\\' + s[3:] | ||
| return prefix, s | ||
| def _ext_to_normal(self, s): | ||
| # Turn back an extended path into a normal DOS-like path | ||
| return self._split_extended_path(s)[1] | ||
| def is_reserved(self, parts): | ||
| # NOTE: the rules for reserved names seem somewhat complicated | ||
| # (e.g. r"..\NUL" is reserved but not r"foo\NUL"). | ||
| @@ -281,54 +250,6 @@ def casefold_parts(self, parts): | ||
| def compile_pattern(self, pattern): | ||
| return re.compile(fnmatch.translate(pattern)).fullmatch | ||
| def resolve(self, path, strict=False): | ||
| sep = self.sep | ||
| accessor = path._accessor | ||
| seen = {} | ||
| def _resolve(path, rest): | ||
| if rest.startswith(sep): | ||
| path = '' | ||
| for name in rest.split(sep): | ||
| if not name or name == '.': | ||
| # current dir | ||
| continue | ||
| if name == '..': | ||
| # parent dir | ||
| path, _, _ = path.rpartition(sep) | ||
| continue | ||
| if path.endswith(sep): | ||
| newpath = path + name | ||
| else: | ||
| newpath = path + sep + name | ||
| if newpath in seen: | ||
| # Already seen this path | ||
| path = seen[newpath] | ||
| if path is not None: | ||
| # use cached value | ||
| continue | ||
| # The symlink is not resolved, so we must have a symlink loop. | ||
| raise RuntimeError("Symlink loop from %r" % newpath) | ||
| # Resolve the symbolic link | ||
| try: | ||
| target = accessor.readlink(newpath) | ||
| except OSError as e: | ||
| if e.errno != EINVAL and strict: | ||
| raise | ||
| # Not a symlink, or non-strict mode. We just leave the path | ||
| # untouched. | ||
| path = newpath | ||
| else: | ||
| seen[newpath] = None # not resolved symlink | ||
| path = _resolve(path, target) | ||
| seen[newpath] = path # resolved symlink | ||
| return path | ||
| # NOTE: according to POSIX, getcwd() cannot contain path components | ||
| # which are symlinks. | ||
| base = '' if path.is_absolute() else accessor.getcwd() | ||
| return _resolve(base, str(path)) or sep | ||
| def is_reserved(self, parts): | ||
| return False | ||
| @@ -424,6 +345,8 @@ def group(self, path): | ||
| expanduser = staticmethod(os.path.expanduser) | ||
| realpath = staticmethod(os.path.realpath) | ||
barneygale marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| _normal_accessor = _NormalAccessor() | ||
| @@ -1132,15 +1055,27 @@ def resolve(self, strict=False): | ||
| normalizing it (for example turning slashes into backslashes under | ||
| Windows). | ||
| """ | ||
| s = self._flavour.resolve(self, strict=strict) | ||
| if s is None: | ||
| # No symlink resolution => for consistency, raise an error if | ||
| # the path doesn't exist or is forbidden | ||
| self.stat() | ||
| s = str(self.absolute()) | ||
| # Now we have no symlinks in the path, it's safe to normalize it. | ||
| normed = self._flavour.pathmod.normpath(s) | ||
| return self._from_parts((normed,)) | ||
| def check_eloop(e): | ||
| winerror = getattr(e, 'winerror', 0) | ||
| if e.errno == ELOOP or winerror == _WINERROR_CANT_RESOLVE_FILENAME: | ||
| raise RuntimeError("Symlink loop from %r" % e.filename) | ||
| try: | ||
| s = self._accessor.realpath(self, strict=strict) | ||
| except OSError as e: | ||
| check_eloop(e) | ||
| raise | ||
| p = self._from_parts((s,)) | ||
| # In non-strict mode, realpath() doesn't raise on symlink loops. | ||
| # Ensure we get an exception by calling stat() | ||
| if not strict: | ||
| try: | ||
| p.stat() | ||
| except OSError as e: | ||
| check_eloop(e) | ||
| return p | ||
| def stat(self, *, follow_symlinks=True): | ||
| """ | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.