-
-
Notifications
You must be signed in to change notification settings - Fork 35.3k
bpo-14094: Use _getfinalpathname to implement realpath #11248
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
Changes from all commits
f1e7011
6202808
58518f4
eb1127b
613115c
bb1c124
6769908
6842cde
58ef5f0
cb2c24c
9f5c9c0
04502bf
13f1676
6253ab3
b5683d3
1cacf6a
145a978
5da9b36
ccb4e1d
497aa20
41705e3
01b426e
fc2385a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -527,8 +527,6 @@ def abspath(path): | |
| except (OSError, ValueError): | ||
| return _abspath_fallback(path) | ||
|
|
||
| # realpath is a no-op on systems without islink support | ||
| realpath = abspath | ||
| # Win9x family and earlier have no Unicode filename support. | ||
| supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and | ||
| sys.getwindowsversion()[3] >= 2) | ||
|
|
@@ -640,23 +638,65 @@ def commonpath(paths): | |
| genericpath._check_arg_types('commonpath', *paths) | ||
| raise | ||
|
|
||
| MAX_PATH = 260 | ||
|
|
||
| # determine if two files are in fact the same file | ||
| try: | ||
| # GetFinalPathNameByHandle is available starting with Windows 6.0. | ||
| # Windows XP and non-Windows OS'es will mock _getfinalpathname. | ||
| if sys.getwindowsversion()[:2] >= (6, 0): | ||
| from nt import _getfinalpathname | ||
| else: | ||
| raise ImportError | ||
| except (AttributeError, ImportError): | ||
| # On Windows XP and earlier, two files are the same if their absolute | ||
| # pathnames are the same. | ||
| # Non-Windows operating systems fake this method with an XP | ||
| # approximation. | ||
| def _getfinalpathname(f): | ||
| return normcase(abspath(f)) | ||
| from nt import _getfinalpathname | ||
|
|
||
| # determine if two files are in fact the same file | ||
| def realpath(filename): | ||
| filename = os.fspath(filename) | ||
| is_str = isinstance(filename, str) | ||
| if not is_str: | ||
| filename = os.fsdecode(filename) | ||
| extended_path_prefix = '\\\\?\\' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer module-level |
||
| is_extended_path = filename.startswith(extended_path_prefix) | ||
| unresolved = filename if is_extended_path else abspath(filename) | ||
| resolved_parts = [] | ||
| while True: | ||
| try: | ||
| resolved_parts.append(_getfinalpathname(unresolved)) | ||
| break | ||
| except OSError: | ||
| unresolved, tail = split(unresolved) | ||
| if not tail: | ||
| resolved_parts.append(unresolved) | ||
| break | ||
| resolved_parts.append(tail) | ||
| resolved = join(*reversed(resolved_parts)) | ||
| # try to convert extended path to normal if | ||
| # initial path did not use \\?\ prefix and result uses it | ||
| if not is_extended_path and resolved.startswith(extended_path_prefix): | ||
| resolved = _extended_to_normal(resolved) | ||
| return resolved if is_str else os.fsencode(resolved) | ||
|
|
||
| def _extended_to_normal(path): | ||
| letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||
| if path[5:7] == ":\\" and path[4].upper() in letters: | ||
| # extended path with \\?\ prefix | ||
| # 4 is len('\\?\') | ||
| normal_path = normpath(path[4:]) | ||
| elif path[:8].upper() == '\\\\?\\UNC\\': | ||
| # UNC path with \\?\ prefix - drop prefix | ||
| # 7 is len('\\?\UNC') | ||
| normal_path = normpath('\\' + path[7:]) | ||
| else: | ||
| # not a UNC or drive-letter path | ||
| # return path as-is | ||
| return path | ||
|
|
||
| if (len(normal_path) < MAX_PATH and | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather not check the length here and just handle the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be convenient, but
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't understand why you say "but"? It sounds like (and I confirmed from source) all our supported platforms can handle normal and excessively long paths here just fine, so there's no need for the check, correct?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're probably talking past each other here. IMO, we shouldn't remove the "\\?\" prefix for long paths prior to Windows 10, and in Windows 10 we shouldn't remove it if long-path support isn't enabled by the "LongPathsEnabled" value in "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem". Maybe you mean that we can remove the prefix because none of our code will crash on long paths. My basis for suggesting this check was that We can skip the I was also concerned about sharing paths via IPC and configuration settings with programs that don't support long paths. But now that I think about it more, I don't think extended paths help this case much in general since many programs don't support them. In this case we can use some type of junction, which could be a drive-letter junction via
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see what you're thinking now. I'd rather move the path length condition out of this function then, so that:
The first point is the main part that was bothering me.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And |
||
| normal_path == _getfullpathname(normal_path)): | ||
| return normal_path | ||
|
vladima marked this conversation as resolved.
|
||
| return path | ||
| except ImportError: | ||
| def realpath(filename): | ||
| filename = os.fspath(filename) | ||
| extended_path_prefix = ( | ||
| '\\\\?\\' if isinstance(filename, str) else b'\\\\?\\' | ||
| ) | ||
| is_extended_path = filename.startswith(extended_path_prefix) | ||
| return filename if is_extended_path else abspath(filename) | ||
|
|
||
| try: | ||
| # The genericpath.isdir implementation uses os.stat and checks the mode | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ntpath.realpath() now uses ``GetFinalPathNameByHandle()``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_INTERNAL_CONSTANTor else document it (as a legacy value that can largely be ignored)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My later suggestion may remove the need for this constant entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's useful to document as a public constant, but maybe change it to
MAX_PATH_DOSorMAX_PATH_LEGACY? Python still has an inter-operation concern even if our own process supports long paths (in addition to our manifest setting, this requires enabling at the OS level , which last I checked isn't the default setting). Along the same lines,CreateProcessdoesn't allow the current directory (explicit or inherited) to be a long path.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've never had it in the past, and it's becoming less relevant. I'd rather not add it now, and continue the work we do to handle it as transparently as possible.