Skip to content

gh-156210: Fix shutil.copytree() detection of dangling relative symlinks - #156214

Open
lpyu001 wants to merge 1 commit into
python:mainfrom
lpyu001:shutil
Open

gh-156210: Fix shutil.copytree() detection of dangling relative symlinks#156214
lpyu001 wants to merge 1 commit into
python:mainfrom
lpyu001:shutil

Conversation

@lpyu001

@lpyu001 lpyu001 commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

shutil.copytree() used the raw result of os.readlink() to determine whether a symbolic link was dangling when ignore_dangling_symlinks=True. Relative targets were therefore checked against the current working directory instead of the link's parent directory.

This could cause valid relative symbolic links to be skipped, or dangling links to be copied when the current working directory contained a matching path.

The check now uses the source link path, allowing the target to be resolved from the correct directory. Regression tests cover both cases.

@lpyu001
lpyu001 requested a review from giampaolo as a code owner August 22, 2026 03:12
@lpyu001 lpyu001 changed the title gh-156210: Fix shutil.copytree() detection of dangling relative symlinks gh-156210: Fix shutil.copytree() detection of dangling relative symlinks Aug 22, 2026

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is correct. Please explain why you used srcname instead of linkto. AFAICT, the problem is linkto is the incorrect file (because readlink() resolves according to PWD and not to src).

Otherwise, I wonder whether this is deliberate and instead should be documented.

Comment thread Lib/shutil.py
else:
# ignore dangling symlink if the flag is on
if not os.path.exists(linkto) and ignore_dangling_symlinks:
if not os.path.exists(srcname) and ignore_dangling_symlinks:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is correct though? srcname is the the symlink file. However, we want to resolve the relative symlink with respect to src, that is, we read the symlink content and then check if the file exists. The problem is therefore not here but rather the computation of "linkto". Instead, we should readlink(srcname) by interpreting srcname's content as relative to the src rather than PWD.

@bedevere-app

bedevere-app Bot commented Aug 22, 2026

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@lpyu001

lpyu001 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

I think the purpose of os.path.exists(linkto) here is simply to check whether the symlink ultimately resolves to an existing target, so we can directly call os.path.exists(srcname) on the original symlink path instead.
In addition,I don't see a need to use linkto to locate the target file in this case ,So I think using srcname here is correct. @picnixz

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I actually forgot that os.path.exists() works on the symlink's target, not on the symlink itself considered as a regular file. So it should be ok.

However, I still don't know whether this was deliberate or not (that is, whether we expect this behavior). Is there some POSIX compliance that we want to follow? I find it weird but at the same time it can be expected since readlink reads a relative symlink with respect to the process workding directory and not the relative to the location. So I'm still unsure whether this is the correct choice.

cc @serhiy-storchaka @encukou @barneygale

@lpyu001

lpyu001 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

readlink reads a relative symlink with respect to the process workding directory

-->I think the CWD dependency comes from os.path.exists(linkto), rather than from readlink() itself. readlink() only returns the raw link contents; it does not resolve a relative target.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants