Files
supervisor/tests/backups
Stefan Agner 1fcfededac Let write-side OSError propagate during backup creation (#6704)
* Let OSError propagate during backup creation

Securetar's create_tar context manager uses a two-phase header write:
on enter it writes a placeholder tar header (size unknown), and on exit
_finalize_tar_entry seeks back to rewrite the header with the actual
size. If an OSError (e.g. ENOSPC) occurs mid-write, the inner tar entry
is left with truncated data and a placeholder header. Continuing to
write more entries on top of this produces a structurally invalid tar
file that cannot be restored.

Previously, _folder_save wrapped OSError as BackupError, which
store_folders then caught and swallowed — allowing the backup to
continue writing to an already corrupt outer tar. Similarly,
_create_finalize silently swallowed OSError when writing backup.json,
and the finally block in create() could raise a secondary OSError from
_close_outer_tarfile that replaced the original exception.

Securetar already distinguishes read vs write errors: read-side errors
(e.g. permission denied on a source file) are wrapped as AddFileError
(non-fatal, skip the file), while write-side OSError propagates as-is.

With this change, write-side OSError is wrapped as BackupFatalError
(a BackupError subclass) instead of plain BackupError. This ensures:
- store_folders/store_addons do not swallow it (they only catch
  BackupError, and re-raise BackupFatalError explicitly).
- The job decorator handles it as a HassioError (no extra Sentry
  event). Letting OSError bubble up raw would cause the job decorator
  to treat it as an unhandled exception, capturing it to Sentry and
  wrapping it as JobException — producing more Sentry noise, not less.
- _do_backup catches it via `except BackupError` and deletes the
  incomplete backup file. This is the correct behavior since the tar
  is structurally corrupt and not restorable.

Changes:
- Add BackupFatalError exception for write-side I/O errors.
- In create(), use except/else instead of finally so that finalization
  is skipped when an error already occurred during yield. This prevents
  a secondary exception from _close_outer_tarfile replacing the
  original error.
- In _create_finalize, raise BackupFatalError on OSError instead of
  swallowing it.
- In _folder_save, wrap OSError as BackupFatalError (not BackupError).
- In store_folders, re-raise BackupFatalError instead of swallowing.
- In store_supervisor_config, wrap OSError as BackupFatalError.

Fixes SUPERVISOR-B53
Fixes SUPERVISOR-1FAJ
Fixes SUPERVISOR-BJ4
Fixes SUPERVISOR-18KS
Fixes SUPERVISOR-1HE6

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Rename BackupFatalError to BackupFatalIOError

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:45:43 +02:00
..