Skip to content

Commit 4ff65ab

Browse files
committed
Fix 'force' remove file without write permissions
Preserve existing mode flags, handle case where we even lack permission to change the mode.
1 parent 660dafb commit 4ff65ab

File tree

1 file changed

+19
-9
lines changed

1 file changed

+19
-9
lines changed

src/pip/_internal/utils/misc.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,24 +154,34 @@ def rmtree_errorhandler(
154154
*,
155155
onexc: Callable[..., Any] = _onerror_reraise,
156156
) -> None:
157-
"""On Windows, the files in .svn are read-only, so when rmtree() tries to
158-
remove them, an exception is thrown. We catch that here, remove the
159-
read-only attribute, and hopefully continue without problems."""
157+
"""
158+
`rmtree` error handler to 'force' a file remove (i.e. like `rm -f`).
159+
160+
* If a file is readonly then it's write flag is set and operation is
161+
retried.
162+
163+
* `onerror` is the original callback from `rmtree(... onerror=onerror)`
164+
that is chained at the end if the "rm -f" still fails.
165+
"""
160166
try:
161-
has_attr_readonly = not (os.stat(path).st_mode & stat.S_IWRITE)
167+
st_mode = os.stat(path).st_mode
162168
except OSError:
163169
# it's equivalent to os.path.exists
164170
return
165171

166-
if has_attr_readonly:
172+
if not st_mode & stat.S_IWRITE:
167173
# convert to read/write
168-
os.chmod(path, stat.S_IWRITE)
169-
# use the original function to repeat the operation
170174
try:
171-
func(path)
172-
return
175+
os.chmod(path, st_mode | stat.S_IWRITE)
173176
except OSError:
174177
pass
178+
else:
179+
# use the original function to repeat the operation
180+
try:
181+
func(path)
182+
return
183+
except OSError:
184+
pass
175185

176186
onexc(func, path, exc_info)
177187

0 commit comments

Comments
 (0)