diff --git a/src/borg/helpers.py b/src/borg/helpers.py index f9080e0f0..7fe9c274f 100644 --- a/src/borg/helpers.py +++ b/src/borg/helpers.py @@ -2401,10 +2401,10 @@ def secure_erase(path): def truncate_and_unlink(path): """ - Truncate and then unlink *path*. + Safely unlink (delete) *path*. - Do not create *path* if it does not exist. - Open *path* for truncation in r+b mode (=O_RDWR|O_BINARY). + If we run out of space while deleting the file, we try truncating it first. + BUT we truncate only if path is the only hardlink referring to this content. Use this when deleting potentially large files when recovering from a VFS error such as ENOSPC. It can help a full file system @@ -2412,13 +2412,27 @@ def truncate_and_unlink(path): in repository.py for further explanations. """ try: - with open(path, 'r+b') as fd: - fd.truncate() - except OSError as err: - if err.errno != errno.ENOTSUP: + os.unlink(path) + except OSError as unlink_err: + if unlink_err.errno != errno.ENOSPC: + # not free space related, give up here. raise - # don't crash if the above ops are not supported. - os.unlink(path) + # we ran out of space while trying to delete the file. + st = os.stat(path) + if st.st_nlink > 1: + # rather give up here than cause collateral damage to the other hardlink. + raise + # no other hardlink! try to recover free space by truncating this file. + try: + # Do not create *path* if it does not exist, open for truncation in r+b mode (=O_RDWR|O_BINARY). + with open(path, 'r+b') as fd: + fd.truncate() + except OSError: + # truncate didn't work, so we still have the original unlink issue - give up: + raise unlink_err + else: + # successfully truncated the file, try again deleting it: + os.unlink(path) def popen_with_error_handling(cmd_line: str, log_prefix='', **kwargs):