From ac59969d8285f339a13d9b5cb0d914e3f200b08c Mon Sep 17 00:00:00 2001 From: Fuxino Date: Wed, 21 Oct 2026 22:32:50 +0200 Subject: [PATCH] Catch all OS errors when removing old local backups Errors other than a missing directory or denied permission (e.g. read-only filesystem, device busy) ended in a traceback after the backup. --- simple_backup/simple_backup.py | 3 +++ tests/test_local.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/simple_backup/simple_backup.py b/simple_backup/simple_backup.py index fb0d5b2..bc7f2ca 100755 --- a/simple_backup/simple_backup.py +++ b/simple_backup/simple_backup.py @@ -405,6 +405,9 @@ class Backup: except PermissionError: logger.error( 'Error while removing backup %s. Permission denied', dirs[i]) + except OSError as e: + # E.g. read-only filesystem or device busy + logger.error('Error while removing backup %s. %s', dirs[i], e.strerror or e) self._removed_count = count diff --git a/tests/test_local.py b/tests/test_local.py index 09b8ff9..7af0a53 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -1,5 +1,6 @@ """Tests for local backups, with real rsync runs and with a fake Popen""" +import errno import io import os from collections.abc import Callable, Iterator @@ -161,6 +162,19 @@ class TestRsync: # Nothing to link against, as the old backup was removed first assert copied(out, backup, src / 'same.txt').stat().st_nlink == 1 + def test_removal_os_error(self, src: Path, out: Path, monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture) -> None: + assert run_backup(out, [str(src)]) == 0 + + def busy(path: str) -> None: + raise OSError(errno.EBUSY, os.strerror(errno.EBUSY), path) + + monkeypatch.setattr(sb, 'rmtree', busy) + + assert run_backup(out, [str(src)], keep=0) == 0 + assert backups(out) == ['2026-10-14_10-00-00', '2026-10-14_11-00-00'] + assert 'Error while removing backup 2026-10-14_10-00-00. Device or resource busy' in caplog.text + def test_stray_files_and_symlinks(self, src: Path, out: Path) -> None: assert run_backup(out, [str(src)]) == 0