Commit 508c6a3f authored by Thomas Gambier's avatar Thomas Gambier 🚴🏼

rmtree: import more code from slapos.core to support case of parent directory not writable

See merge request nexedi/slapos.buildout!22
parents 44b3c076 6dcf6b6e
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
import shutil import shutil
import os import os
import doctest import doctest
import errno
import sys
def rmtree (path): def rmtree (path):
""" """
...@@ -26,6 +28,8 @@ def rmtree (path): ...@@ -26,6 +28,8 @@ def rmtree (path):
Also it tries to remove symlink itself if a symlink as passed as Also it tries to remove symlink itself if a symlink as passed as
path argument. path argument.
Finally, it tries to make parent directory writable.
>>> from tempfile import mkdtemp >>> from tempfile import mkdtemp
Let's make a directory ... Let's make a directory ...
...@@ -46,7 +50,11 @@ def rmtree (path): ...@@ -46,7 +50,11 @@ def rmtree (path):
and make it unwriteable and make it unwriteable
>>> os.chmod (foo, 256) # 0400 >>> os.chmod (foo, 0o400)
and make parent dir unwritable
>>> os.chmod (d, 0o400)
rmtree should be able to remove it: rmtree should be able to remove it:
...@@ -89,7 +97,9 @@ def rmtree (path): ...@@ -89,7 +97,9 @@ def rmtree (path):
>>> os.path.isdir (d) >>> os.path.isdir (d)
0 0
""" """
def retry_writeable (func, path, exc): def chmod_retry(func, failed_path, exc_info):
"""Make sure the directories are executable and writable.
"""
if func is os.path.islink: if func is os.path.islink:
os.unlink(path) os.unlink(path)
elif func is os.lstat: elif func is os.lstat:
...@@ -97,10 +107,33 @@ def rmtree (path): ...@@ -97,10 +107,33 @@ def rmtree (path):
raise raise
os.unlink(path) os.unlink(path)
else: else:
os.chmod(path, 0o600) # Depending on the Python version, the following items differ.
func(path) if sys.version_info >= (3, ):
expected_error_type = PermissionError
shutil.rmtree (path, onerror = retry_writeable) expected_func_tuple = (os.lstat, os.open)
else:
expected_error_type = OSError
expected_func_tuple = (os.listdir, )
e = exc_info[1]
if isinstance(e, expected_error_type):
if e.errno == errno.ENOENT:
# because we are calling again rmtree on listdir errors, this path might
# have been already deleted by the recursive call to rmtree.
return
if e.errno == errno.EACCES:
if func in expected_func_tuple:
os.chmod(failed_path, 0o700)
# corner case to handle errors in listing directories.
# https://bugs.python.org/issue8523
return shutil.rmtree(failed_path, onerror=chmod_retry)
# If parent directory is not writable, we still cannot delete the file.
# But make sure not to change the parent of the folder we are deleting.
if failed_path != path:
os.chmod(os.path.dirname(failed_path), 0o700)
return func(failed_path)
raise
shutil.rmtree(path, onerror=chmod_retry)
def test_suite(): def test_suite():
return doctest.DocTestSuite() return doctest.DocTestSuite()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment