Skip to content

gh-110495: ensure ntpath.realpath() result is a final, normalized pathname. - #110751

Open
moonsikpark wants to merge 4 commits into
python:mainfrom
moonsikpark:gh-110495-ensure-ntpath-realpath-final-normalized-pathname
Open

gh-110495: ensure ntpath.realpath() result is a final, normalized pathname.#110751
moonsikpark wants to merge 4 commits into
python:mainfrom
moonsikpark:gh-110495-ensure-ntpath-realpath-final-normalized-pathname

Conversation

@moonsikpark

@moonsikparkmoonsikpark commented Oct 12, 2023

Copy link
Copy Markdown
Contributor

By not breaking out of the loop when _readlink_deep() is successful, we can ensure that the result of ntpath.realpath() is a final, normalized pathname.

@moonsikpark

Copy link
Copy Markdown
ContributorAuthor

@eryksun can you review this?

@encukou

Copy link
Copy Markdown
Member

cc @serhiy-storchaka (os.path expert) & @barneygale (with the reproducer in the issue, pathlib.Path('C:\\testdir\\symlink2\\filelink.txt').resolve() also shows the symlink rather than file)

Comment threadLib/ntpath.py
@moonsikpark
moonsikparkforce-pushed the gh-110495-ensure-ntpath-realpath-final-normalized-pathname branch from 1f8656e to 3ae56f0CompareMarch 19, 2024 05:41
@eryksun

eryksun commented Jun 17, 2024

Copy link
Copy Markdown
Contributor

I'm not having much luck getting this to work with a common seen set. I think an independent set is needed in _getfinalpathname_nonstrict(). For example:

def_getfinalpathname_nonstrict(path):
# These error codes indicate that we should stop resolving the# path and return the value we currently have.allowed_winerror= (
1, # ERROR_INVALID_FUNCTION2, # ERROR_FILE_NOT_FOUND3, # ERROR_DIRECTORY_NOT_FOUND5, # ERROR_ACCESS_DENIED21, # ERROR_NOT_READY (e.g. drive with no media)32, # ERROR_SHARING_VIOLATION (e.g. NTFS paging file)50, # ERROR_NOT_SUPPORTED53, # ERROR_BAD_NETPATH65, # ERROR_NETWORK_ACCESS_DENIED67, # ERROR_BAD_NET_NAME (e.g. remote server unavailable)87, # ERROR_INVALID_PARAMETER123, # ERROR_INVALID_NAME161, # ERROR_BAD_PATHNAME1920, # ERROR_CANT_ACCESS_FILE1921, # ERROR_CANT_RESOLVE_FILENAME (e.g. can't follow symlink)
)
# Non-strict algorithm is to find as much of the target# directory as we can and join the rest.tail=path[:0]
seen=set()
whilepathandnormcase(path) notinseen:
seen.add(normcase(path))
try:
path=_getfinalpathname(path)
returnjoin(path, tail) iftailelsepathexceptOSErrorasex:
ifex.winerrornotinallowed_winerror:
raise# Split off the base name, and break if it's root.parent_path, name=split(path)
ifnotname:
breaktry:
# Try resolving the final component as a link. If# successful, continue resolving the real path.new_path=_readlink_deep(path)
ifnew_path!=path:
path=new_pathcontinueexceptOSError:
pass# For some error cases, try to get the real name from# the directory entry.ifex.winerrorin (1, 5, 32, 50, 87, 1920, 1921):
try:
name=_findfirstfile(path)
exceptOSError:
passpath=parent_pathtail=join(name, tail) iftailelsenameifpath:
returnjoin(path, tail) iftailelsepathreturntail

@eryksun

Copy link
Copy Markdown
Contributor

Some of the checks in test_realpath_broken_symlinks() that validate an incomplete resolution of the final path would need to be modified.

# bpo-38453: We no longer recursively resolve segments of relative
# symlinks that the OS cannot resolve.
self.assertPathEqual(ntpath.realpath(r"broken1"),
ABSTFN+r"\broken\bar")
self.assertPathEqual(ntpath.realpath(r"broken1\baz"),
ABSTFN+r"\broken\bar\baz")
self.assertPathEqual(ntpath.realpath("broken2"),
ABSTFN+r"\self\self\missing")
self.assertPathEqual(ntpath.realpath("broken3"),
ABSTFN+r"\subdir\parent\subdir\parent\missing")

New checks:

self.assertPathEqual(ntpath.realpath(r"broken1"),
ABSTFN+r"\missing\bar")
self.assertPathEqual(ntpath.realpath(r"broken1\baz"),
ABSTFN+r"\missing\bar\baz")
self.assertPathEqual(ntpath.realpath("broken2"),
ABSTFN+r"\missing")
self.assertPathEqual(ntpath.realpath("broken3"),
ABSTFN+r"\missing")

The corresponding bytes path checks would also have to be updated.

@github-actions

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

@github-actionsgithub-actionsBot added the stale Stale PR or inactive for long period of time. label Apr 14, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting reviewstaleStale PR or inactive for long period of time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@moonsikpark@encukou@eryksun@barneygale