Repository navigation
os.path.realpath returns invalid path for junction pointing to letter-less volume #89760
Description
Activity
If a path contains a junction pointing to a dir on a letter-less drive then
os.path.realpathreturnsVolume{<uuid>}\dir, without\\?\prefix.This path, of course, doesn't work correctly. Actually, it looks relative.
Original issue: pypa/pip#10597
- added3.8 (EOL)end of lifeend of life3.9 (EOL)end of lifeend of life3.10 (EOL)end of lifeend of life
on Oct 24, 2021 This is from checking whether the \\?\ prefix can be stripped. The _getfinalpathname() call that it makes fails with the initial winerror (ERROR_PATH_NOT_FOUND), since nt._getfinalpathname() still lacks support for volume GUID paths. In this case, it assumes the path doesn't exist and removes the prefix.
This check should be skipped for all prefixed paths if they aren't drives or UNC shares. For example, if colon_sep (i.e. ":\\") is defined:
# The path returned by _getfinalpathname will always start with \\?\ - # strip off that prefix unless it was already provided on the original # path. if not had_prefix and path.startswith(prefix): # For UNC paths, the prefix will be \\?\UNC\ if path.startswith(unc_prefix): spath = new_unc_prefix + path[len(unc_prefix):] # For drive paths, the root is of the form \\?\X:\ elif path.startswith(colon_sep, len(prefix) + 1): spath = path[len(prefix):] # For all others, the prefix must be retained. else: spath = None if spath is not None: # Ensure that the non-prefixed path resolves to the same path try: if _getfinalpathname(spath) == path: path = spath except OSError as ex: # If the path does not exist and originally did not exist, then # strip the prefix anyway. if ex.winerror == initial_winerror: path = spath return path
@eryksun, are you proposing a change to discuss, or…?
Your code works for me. Although I found the comment misleading.are you proposing a change to discuss, or…?
Yes, the suggested change in
ntpath.realpath()will always keep the "\\?\" prefix if the resolved path is not on the "UNC" device or a DOS drive.Although I found the comment misleading.
GetFinalPathNameByHandleW()supports getting the final path asVOLUME_NAME_DOS(i.e. using a DOS drive-letter volume name or a UNC share) orVOLUME_NAME_GUID(i.e. using a "Volume{GUID}" volume name). If a volume has not been assigned a DOS drive-letter name, then requestingVOLUME_NAME_DOSwill fail. Python's implementation ofnt._getfinalpathname()doesn't fall back onVOLUME_NAME_GUIDin this case. It could, but it currently doesn't. But solving this issue by extending the implementation of_getfinalpathname()in "Modules/posixmodule.c" would make the solution overly difficult, and it still might miss edge cases. It's simpler to just change the implementation ofntpath.realpath()as mentioned above.Yes, the suggested change
Why not open a PR?
Or suggested changes must be discussed/approved/… before PR? I'm not familiar with rules here.Although I found the comment misleading
I meant, the term "drive path" is not wide-accepted.
I'd just sayRegular paths start from \\?\<drive letter>:\.Python's implementation of
nt._getfinalpathname()doesn't fall back onVOLUME_NAME_GUIDin this caseAnd what results would
GetFinalPathNameByHandleWwithVOLUME_NAME_GUIDgive in this case?I haven't tested, but IIUC, these flags aren't fallbacks. They choose which variant should be returned when there are several possibilities (with drive letter or with volume GUID). But if there is no letter,
VOLUME_NAME_GUIDwould give the same result asVOLUME_NAME_DOS.Note that reproducing the issue requires the error after stripping the "\\?\" prefix to match
initial_winerror. So sometimesrealpath()will work correctly, given the two error codes are different.Also, there's no misbehavior if the path of the junction is registered with the mount point manager as the canonical DOS path of the volume, e.g. a junction created by "mountvol.exe" or WinAPI
SetVolumeMountPointW()(requires administrator access).nt._getfinalpathname()returns the canonical DOS path.Why not open a PR?
Or suggested changes must be discussed/approved/… before PR?Personally, I prefer to discuss ideas in the issue itself. It leaves a more accessible record. I've learned a lot from reading old issues. You're welcome to create a PR if you're eager to resolve this issue. I'll try to help with code review and testing.
I haven't tested, but IIUC, these flags aren't fallbacks. They choose
which variant should be returned when there are several possibilities
(with drive letter or with volume GUID). But if there is no letter,
VOLUME_NAME_GUIDwould give the same result asVOLUME_NAME_DOS.If a volume has no canonical DOS mount point, then requesting
VOLUME_NAME_DOSfails. In this case, I suggested that the implementation ofnt._getfinalpathname()could automatically fall back on requesting the GUID name viaVOLUME_NAME_GUID. This will succeed as long as the volume supports the mount point manager. In general, this would be a welcome enhancement.But, as I said, for this issue it's better to modify
realpath()to never try removing the "\\?\" prefix if the path doesn't start with a drive-letter drive or "UNC" drive.I meant, the term "drive path" is not wide-accepted.
I'd just sayRegular paths start from \\?\<drive letter>:\.How about clarifying the comments as "For UNC drives, the path starts with \\?\UNC\", and "For drive-letter drives, the path starts with \\?\<drive letter>:\"?
7 remaining items
- added3.16new features, bugs and security fixesnew features, bugs and security fixestype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errorstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directoryand removed3.10 (EOL)end of lifeend of life3.9 (EOL)end of lifeend of life3.8 (EOL)end of lifeend of life
on Aug 8, 2026 - added 3 commits that reference this issue
on Aug 29, 2026 - added a commit that references this issue
on Aug 30, 2026 - added a commit that references this issue
on Oct 9, 2026
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
Linked PRs