-
-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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
bpo-42782: Fail fast for permission errors in shutil.move() #24001
Merged
Merged
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
fe8f24a
Fail fast in shutil.move() to avoid creating destination directories …
winsonluk 8826d00
Add NEWS entry
winsonluk a231ca6
Update Misc/NEWS.d/next/Library/2020-12-29-13-46-57.bpo-42782.3r0HFY.rst
winsonluk ee8073d
check if pytest is run as root
winsonluk 7c11a09
Merge branch 'fix-shutil-move' of github.com:avocadoscan/cpython into…
winsonluk 1082d79
add unit test
winsonluk 6326e7c
update test
winsonluk 79d6801
test empty immutable directories
winsonluk 2567fb2
add platform check
winsonluk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
Misc/NEWS.d/next/Library/2020-12-29-13-46-57.bpo-42782.3r0HFY.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Fail fast in shutil.move() to avoid creating destination directories on | ||
winsonluk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
failure. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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'm not sure that this is the right list of permissions to check for and could potentially raise errors for move() operations that would have otherwise succeeded. I might be wrong, but I believe it's the parent directory's write permissions that matter.
Perhaps another approach would be to put copytree() and rmtree() in try except blocks, then cleaning up and raising an error accordingly on catching a permission error.
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.
Thanks for the review - sorry, I should have clarified what this line does.
Background: This check only runs when attempting to move directories, and OS move permissions are tied to the source directory, not its parent directory. The
elif os.path.isdir(src):
confirms thatsrc
is the directory being moved.not os.access(src, os.W_OK)
checks ifsrc
is write-enabled. We need this check not because we're writing tosrc
, but because we need to delete it. Previously, ifsrc
was not write-enabled,rmtree
would fail with a permission error.os.listdir(src)
checks ifsrc
is empty: this is important because if a user doesn't have write access to a directory, but the directory is empty, the directory can be removed by the user. Weird OS quirk, I know. So this check makes sure we don't raise an exception if the directory is empty (os.listdir(src) == []
), becausesrc
can still be removed withrmtree
despite the lack of write permissions.I'm going to disagree with the try/except suggestion, since permission errors are easier to prevent than catch.
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.
Ah I see that this check happens when handling an
OSError
after first trying to rename the directory, which was the scenario I thought it might block.However, in that case, that's probably because the user does not have write access to the parent directory, so
copytree()
would throw aPermissionError
beforermtree()
is even run which means that the destination directory isn't created.I'm not able to reproduce the error with the commands you provided, perhaps you could provide additional context for the permissions on the parent directory?
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.
yeah, this situation only occurs if the user has permission to the parent directory, but not the src directory - this means the user can mkdir and cp within parent (with copytree succeeding), but can't rm src.
if the user doesn't have permission to the parent directory, you're correct that the operation would fail at copytree.
here's the example where copy succeeds, but rm fails
Running move():
End state:
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.
Hi @winsonluk
I looked a bit deeper, and I understand the theory in your PR, but I am unable to come out with a scenario that will reproduce the error in your original scenario.
The parent permissions seem to satisfy the requirements.
Here I tried creating the dir permissions as above.
And
Is it possible for you to share set of instructions (including chmod/chown instruction) that can help reproduce this issue to verify? FWIW, I used https://bugs.python.org/msg384023 and didn't see this scenario that you encountered.
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.
@orsenthil Huh, good catch. I've been testing this on MacOS
darwin
machines (python3 installed with Homebrew). I just retried this on some sparelinux
,freebsd
, andwin32
servers, and the file was moved successfully in all cases.This is probably a darwin-only bug, so it's only reproducible on MacOS.
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 wonder if this is a MacOS bug. It's strange not to see this behavior in FreeBSD, Linux and Win32 and only in MacOS. And we are not even talking about Python implementation. The problem should be observable with Darwin's mv utility too.
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'm not sure if it's a bug or intentional, but I can confirm that Darwin's
mv
fails when other OS's succeed (e.g.,mv src dst
). So while this PR is necessary to mitigate Darwin's irregular behavior, it doesn't impact other operating systems.