debian-forge/osbuild/util/rmrf.py
David Rheinsberg 1cdf2be0ac util/rmrf: use immutable helpers
Make use of the new immutable-flag ioctl helpers. While at it, move the
`chmod` to `fchmod` and re-use the open file-descriptor. Document the
behavior and move the `fchmod` into its own try-block for the same
reasons as the `ioctl` call: We rely on the following unlink() to catch
any errors. Errors in the fixperms() step are non-consequential.
2020-04-21 14:46:02 +02:00

98 lines
2.9 KiB
Python

"""Recursive File System Removal
This module implements `rm -rf` as a python function. Its core is the
`rmtree()` function, which takes a file-system path and then recursively
deletes everything it finds on that path, until eventually the path entry
itself is dropped. This is modeled around `shutil.rmtree()`.
This function tries to be as thorough as possible. That is, it tries its best
to modify permission bits and other flags to make sure directory entries can be
removed.
"""
import os
import shutil
import osbuild.util.linux as linux
__all__ = [
"rmtree",
]
def rmtree(path: str):
"""Recursively Remove from File System
This removes the object at the given path from the file-system. It
recursively iterates through its content and removes them, before removing
the object itself.
This function is modeled around `shutil.rmtree()`, but extends its
functionality with a more aggressive approach. It tries much harder to
unlink file system objects. This includes immutable markers and more.
Note that this function can still fail. In particular, missing permissions
can always prevent this function from succeeding. However, a caller should
never assume that they can intentionally prevent this function from
succeeding. In other words, this function might be extended in any way in
the future, to be more powerful and successful in removing file system
objects.
Parameters
---------
path
A file system path pointing to the object to remove.
Raises
------
Exception
This raises the same exceptions as `shutil.rmtree()` (since that
function is used internally). Consult its documentation for details.
"""
def fixperms(p):
fd = None
try:
fd = os.open(p, os.O_RDONLY)
# The root-only immutable flag prevents files from being unlinked
# or modified. Clear it, so we can unlink the file-system tree.
try:
linux.ioctl_toggle_immutable(fd, False)
except OSError:
pass
# If we do not have sufficient permissions on a directory, we
# cannot traverse it, nor unlink its content. Make sure to set
# sufficient permissions up front.
try:
os.fchmod(fd, 0o777)
except OSError:
pass
finally:
if fd is not None:
os.close(fd)
def unlink(p):
try:
os.unlink(p)
except IsADirectoryError:
rmtree(p)
except FileNotFoundError:
pass
def on_error(_fn, p, exc_info):
e = exc_info[0]
if issubclass(e, FileNotFoundError):
pass
elif issubclass(e, PermissionError):
if p != path:
fixperms(os.path.dirname(p))
fixperms(p)
unlink(p)
else:
raise e
shutil.rmtree(path, onerror=on_error)