Sfoglia il codice sorgente

raise BackupOSError subclasses

Thomas Waldmann 1 anno fa
parent
commit
97dd287584

+ 7 - 5
docs/internals/frontends.rst

@@ -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

+ 11 - 2
src/borg/archive.py

@@ -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()

+ 5 - 5
src/borg/archiver/__init__.py

@@ -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

+ 7 - 13
src/borg/archiver/create_cmd.py

@@ -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"

+ 6 - 8
src/borg/archiver/extract_cmd.py

@@ -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:

+ 2 - 1
src/borg/helpers/__init__.py

@@ -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

+ 38 - 49
src/borg/helpers/errors.py

@@ -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
-
-
-class PermissionWarning(BorgWarning):
-    """{}: {}"""
-
-    exit_mcode = 105
-
-
-class IOWarning(BorgWarning):
-    """{}: {}"""
-
-    exit_mcode = 106
+        assert isinstance(exc, BackupError)
+        return exc.exit_mcode
 
 
-class NotFoundWarning(BorgWarning):
-    """{}: {}"""
-
-    exit_mcode = 107
+class BackupError(Error):
+    """{}: backup error"""
 
-
-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.
 
-    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).
+class BackupOSError(BackupError):
+    """{}: {}"""
 
-    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