raise BackupOSError subclasses

This commit is contained in:
Thomas Waldmann 2023-11-27 17:02:23 +01:00
parent 9ac4672254
commit 97dd287584
No known key found for this signature in database
GPG Key ID: 243ACFA951F78E01
7 changed files with 77 additions and 84 deletions

View File

@ -713,15 +713,17 @@ Warnings
{}: file changed while we backed it up
IncludePatternNeverMatchedWarning rc: 101
Include pattern '{}' never matched.
BackupWarning rc: 102
BackupError rc: 102
{}: {}
BackupOSWarning rc: 104
BackupRaceConditionError rc: 103
Error: {}
BackupOSError rc: 104
{}: {}
PermissionWarning rc: 105
BackupPermissionError rc: 105
{}: {}
IOWarning rc: 106
BackupIOError rc: 106
{}: {}
NotFoundWarning rc: 107
BackupFileNotFoundError rc: 107
{}: {}
Operations

View File

@ -1,4 +1,5 @@
import base64
import errno
import json
import os
import stat
@ -26,7 +27,8 @@ from .crypto.key import key_factory, UnsupportedPayloadError
from .compress import CompressionSpec
from .constants import * # NOQA
from .crypto.low_level import IntegrityError as IntegrityErrorBase
from .helpers import BackupError, BackupOSError, BackupRaceConditionError
from .helpers import BackupError, BackupRaceConditionError
from .helpers import BackupOSError, BackupPermissionError, BackupFileNotFoundError, BackupIOError
from .hashindex import ChunkIndex, ChunkIndexEntry, CacheSynchronizer
from .helpers import HardLinkManager
from .helpers import ChunkIteratorFileWrapper, open_item
@ -194,7 +196,14 @@ class BackupIO:
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type and issubclass(exc_type, OSError):
raise BackupOSError(self.op, exc_val) from exc_val
E_MAP = {
errno.EPERM: BackupPermissionError,
errno.EACCES: BackupPermissionError,
errno.ENOENT: BackupFileNotFoundError,
errno.EIO: BackupIOError,
}
e_cls = E_MAP.get(exc_val.errno, BackupOSError)
raise e_cls(self.op, exc_val) from exc_val
backup_io = BackupIO()

View File

@ -26,7 +26,7 @@ try:
from ..constants import * # NOQA
from ..helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE, classify_ec
from ..helpers import Error, CommandError, get_ec, modern_ec
from ..helpers import add_warning, BorgWarning
from ..helpers import add_warning, BorgWarning, BackupWarning
from ..helpers import format_file_size
from ..helpers import remove_surrogates, text_to_json
from ..helpers import DatetimeWrapper, replace_placeholders
@ -144,10 +144,10 @@ class Archiver(
def print_warning_instance(self, warning):
assert isinstance(warning, BorgWarning)
msg = type(warning).__doc__
msgid = type(warning).__qualname__
args = warning.args
self.print_warning(msg, *args, wc=warning.exit_code, wt="curly", msgid=msgid)
# if it is a BackupWarning, use the wrapped BackupError exception instance:
cls = type(warning.args[1]) if isinstance(warning, BackupWarning) else type(warning)
msg, msgid, args, wc = cls.__doc__, cls.__qualname__, warning.args, warning.exit_code
self.print_warning(msg, *args, wc=wc, wt="curly", msgid=msgid)
def print_file_status(self, status, path):
# if we get called with status == None, the final file status was already printed

View File

@ -29,7 +29,7 @@ from ..helpers import prepare_subprocess_env
from ..helpers import sig_int, ignore_sigint
from ..helpers import iter_separated
from ..helpers import MakePathSafeAction
from ..helpers import Error, CommandError, BackupWarning, BackupOSWarning, FileChangedWarning
from ..helpers import Error, CommandError, BackupWarning, FileChangedWarning
from ..manifest import Manifest
from ..patterns import PatternMatcher
from ..platform import is_win32
@ -87,7 +87,7 @@ class CreateMixIn:
rc = proc.wait()
if rc != 0:
raise CommandError("Command %r exited with status %d", args.paths[0], rc)
except BackupOSError as e:
except BackupError as e:
raise Error("%s: %s", path, e)
else:
status = "+" # included
@ -121,9 +121,6 @@ class CreateMixIn:
read_special=args.read_special,
dry_run=dry_run,
)
except BackupOSError as e:
self.print_warning_instance(BackupOSWarning(path, e))
status = "E"
except BackupError as e:
self.print_warning_instance(BackupWarning(path, e))
status = "E"
@ -151,8 +148,8 @@ class CreateMixIn:
status = fso.process_pipe(
path=path, cache=cache, fd=sys.stdin.buffer, mode=mode, user=user, group=group
)
except BackupOSError as e:
self.print_warning_instance(BackupOSWarning(path, e))
except BackupError as e:
self.print_warning_instance(BackupWarning(path, e))
status = "E"
else:
status = "+" # included
@ -183,9 +180,9 @@ class CreateMixIn:
# if we get back here, we've finished recursing into <path>,
# we do not ever want to get back in there (even if path is given twice as recursion root)
skip_inodes.add((st.st_ino, st.st_dev))
except BackupOSError as e:
except BackupError as e:
# this comes from os.stat, self._rec_walk has own exception handler
self.print_warning_instance(BackupOSWarning(path, e))
self.print_warning_instance(BackupWarning(path, e))
continue
if not dry_run:
if args.progress:
@ -368,7 +365,7 @@ class CreateMixIn:
else:
self.print_warning("Unknown file type: %s", path)
return
except (BackupError, BackupOSError) as err:
except BackupError as err:
if isinstance(err, BackupOSError):
if err.errno in (errno.EPERM, errno.EACCES):
# Do not try again, such errors can not be fixed by retrying.
@ -524,9 +521,6 @@ class CreateMixIn:
dry_run=dry_run,
)
except BackupOSError as e:
self.print_warning_instance(BackupOSWarning(path, e))
status = "E"
except BackupError as e:
self.print_warning_instance(BackupWarning(path, e))
status = "E"

View File

@ -6,13 +6,13 @@ import stat
from ._common import with_repository, with_archive
from ._common import build_filter, build_matcher
from ..archive import BackupError, BackupOSError
from ..archive import BackupError
from ..constants import * # NOQA
from ..helpers import archivename_validator, PathSpec
from ..helpers import remove_surrogates
from ..helpers import HardLinkManager
from ..helpers import ProgressIndicatorPercent
from ..helpers import BackupWarning, BackupOSWarning, IncludePatternNeverMatchedWarning
from ..helpers import BackupWarning, IncludePatternNeverMatchedWarning
from ..manifest import Manifest
from ..logger import create_logger
@ -65,8 +65,8 @@ class ExtractMixIn:
dir_item = dirs.pop(-1)
try:
archive.extract_item(dir_item, stdout=stdout)
except BackupOSError as e:
self.print_warning_instance(BackupOSWarning(remove_surrogates(dir_item.path), e))
except BackupError as e:
self.print_warning_instance(BackupWarning(remove_surrogates(dir_item.path), e))
if output_list:
logging.getLogger("borg.output.list").info(remove_surrogates(item.path))
try:
@ -80,8 +80,6 @@ class ExtractMixIn:
archive.extract_item(
item, stdout=stdout, sparse=sparse, hlm=hlm, pi=pi, continue_extraction=continue_extraction
)
except BackupOSError as e:
self.print_warning_instance(BackupOSWarning(remove_surrogates(orig_path), e))
except BackupError as e:
self.print_warning_instance(BackupWarning(remove_surrogates(orig_path), e))
if pi:
@ -96,8 +94,8 @@ class ExtractMixIn:
dir_item = dirs.pop(-1)
try:
archive.extract_item(dir_item, stdout=stdout)
except BackupOSError as e:
self.print_warning_instance(BackupOSWarning(remove_surrogates(dir_item.path), e))
except BackupError as e:
self.print_warning_instance(BackupWarning(remove_surrogates(dir_item.path), e))
for pattern in matcher.get_unmatched_include_patterns():
self.print_warning_instance(IncludePatternNeverMatchedWarning(pattern))
if pi:

View File

@ -13,8 +13,9 @@ from .checks import check_extension_modules, check_python
from .datastruct import StableDict, Buffer, EfficientCollectionQueue
from .errors import Error, ErrorWithTraceback, IntegrityError, DecompressionError, CancelledByUser, CommandError
from .errors import RTError, modern_ec
from .errors import BorgWarning, FileChangedWarning, BackupWarning, BackupOSWarning, IncludePatternNeverMatchedWarning
from .errors import BorgWarning, FileChangedWarning, BackupWarning, IncludePatternNeverMatchedWarning
from .errors import BackupError, BackupOSError, BackupRaceConditionError
from .errors import BackupPermissionError, BackupIOError, BackupFileNotFoundError
from .fs import ensure_dir, join_base_dir, get_socket_filename
from .fs import get_security_dir, get_keys_dir, get_base_dir, get_cache_dir, get_config_dir, get_runtime_dir
from .fs import dir_is_tagged, dir_is_cachedir, remove_dotdot_prefixes, make_path_safe, scandir_inorder

View File

@ -1,4 +1,3 @@
import errno
import os
from ..constants import * # NOQA
@ -112,70 +111,42 @@ class IncludePatternNeverMatchedWarning(BorgWarning):
class BackupWarning(BorgWarning):
"""{}: {}"""
exit_mcode = 102
class BackupOSWarning(BorgWarning):
"""{}: {}"""
exit_mcode = 104
# this is to wrap a caught BackupError exception, so it can be given to print_warning_instance
@property
def exit_code(self):
if not modern_ec:
return EXIT_WARNING
exc = self.args[1]
assert isinstance(exc, BackupOSError)
if exc.errno in (errno.EPERM, errno.EACCES):
return PermissionWarning.exit_mcode
elif exc.errno in (errno.ENOENT,):
return NotFoundWarning.exit_mcode
elif exc.errno in (errno.EIO,):
return IOWarning.exit_mcode
else:
return self.exit_mcode
assert isinstance(exc, BackupError)
return exc.exit_mcode
class PermissionWarning(BorgWarning):
"""{}: {}"""
class BackupError(Error):
"""{}: backup error"""
exit_mcode = 105
class IOWarning(BorgWarning):
"""{}: {}"""
exit_mcode = 106
class NotFoundWarning(BorgWarning):
"""{}: {}"""
exit_mcode = 107
class BackupError(Exception):
"""
Exception raised for non-OSError-based exceptions while accessing backup files.
"""
# Exception raised for non-OSError-based exceptions while accessing backup files.
exit_mcode = 102
class BackupRaceConditionError(BackupError):
"""
Exception raised when encountering a critical race condition while trying to back up a file.
"""
"""{}: file type or inode changed while we backed it up (race condition, skipped file)"""
# Exception raised when encountering a critical race condition while trying to back up a file.
exit_mcode = 103
class BackupOSError(Exception):
"""
Wrapper for OSError raised while accessing backup files.
class BackupOSError(BackupError):
"""{}: {}"""
Borg does different kinds of IO, and IO failures have different consequences.
This wrapper represents failures of input file or extraction IO.
These are non-critical and are only reported (exit code = 1, warning).
Any unwrapped IO error is critical and aborts execution (for example repository IO failure).
"""
# Wrapper for OSError raised while accessing backup files.
#
# Borg does different kinds of IO, and IO failures have different consequences.
# This wrapper represents failures of input file or extraction IO.
# These are non-critical and are only reported (warnings).
#
# Any unwrapped IO error is critical and aborts execution (for example repository IO failure).
exit_mcode = 104
def __init__(self, op, os_error):
self.op = op
@ -189,3 +160,21 @@ class BackupOSError(Exception):
return f"{self.op}: {self.os_error}"
else:
return str(self.os_error)
class BackupPermissionError(BackupOSError):
"""{}: {}"""
exit_mcode = 105
class BackupIOError(BackupOSError):
"""{}: {}"""
exit_mcode = 106
class BackupFileNotFoundError(BackupOSError):
"""{}: {}"""
exit_mcode = 107