瀏覽代碼

raise BackupOSError subclasses

Thomas Waldmann 1 年之前
父節點
當前提交
83bf4d8c7d
共有 4 個文件被更改,包括 67 次插入76 次删除
  1. 7 5
      docs/internals/frontends.rst
  2. 11 2
      src/borg/archive.py
  3. 14 22
      src/borg/archiver.py
  4. 35 47
      src/borg/helpers/errors.py

+ 7 - 5
docs/internals/frontends.rst

@@ -695,15 +695,17 @@ Warnings
         {}: file changed while we backed it up
         {}: file changed while we backed it up
     IncludePatternNeverMatchedWarning rc: 101
     IncludePatternNeverMatchedWarning rc: 101
         Include pattern '{}' never matched.
         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
 Operations

+ 11 - 2
src/borg/archive.py

@@ -1,3 +1,4 @@
+import errno
 import json
 import json
 import os
 import os
 import socket
 import socket
@@ -25,7 +26,8 @@ from .crypto.key import key_factory, UnsupportedPayloadError
 from .compress import Compressor, CompressionSpec
 from .compress import Compressor, CompressionSpec
 from .constants import *  # NOQA
 from .constants import *  # NOQA
 from .crypto.low_level import IntegrityError as IntegrityErrorBase
 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 .hashindex import ChunkIndex, ChunkIndexEntry, CacheSynchronizer
 from .helpers import Manifest
 from .helpers import Manifest
 from .helpers import hardlinkable
 from .helpers import hardlinkable
@@ -190,7 +192,14 @@ class BackupIO:
 
 
     def __exit__(self, exc_type, exc_val, exc_tb):
     def __exit__(self, exc_type, exc_val, exc_tb):
         if exc_type and issubclass(exc_type, OSError):
         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()
 backup_io = BackupIO()

+ 14 - 22
src/borg/archiver.py

@@ -47,7 +47,7 @@ try:
     from .crypto.keymanager import KeyManager
     from .crypto.keymanager import KeyManager
     from .helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE, classify_ec
     from .helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE, classify_ec
     from .helpers import Error, NoManifestError, CancelledByUser, RTError, CommandError, modern_ec, set_ec, get_ec
     from .helpers import Error, NoManifestError, CancelledByUser, RTError, CommandError, modern_ec, set_ec, get_ec
-    from .helpers import add_warning, BorgWarning, FileChangedWarning, BackupWarning, BackupOSWarning, IncludePatternNeverMatchedWarning
+    from .helpers import add_warning, BorgWarning, FileChangedWarning, BackupWarning, IncludePatternNeverMatchedWarning
     from .helpers import positive_int_validator, location_validator, archivename_validator, ChunkerParams, Location
     from .helpers import positive_int_validator, location_validator, archivename_validator, ChunkerParams, Location
     from .helpers import PrefixSpec, GlobSpec, CommentSpec, PathSpec, SortBySpec, FilesCacheMode
     from .helpers import PrefixSpec, GlobSpec, CommentSpec, PathSpec, SortBySpec, FilesCacheMode
     from .helpers import BaseFormatter, ItemFormatter, ArchiveFormatter
     from .helpers import BaseFormatter, ItemFormatter, ArchiveFormatter
@@ -256,10 +256,10 @@ class Archiver:
 
 
     def print_warning_instance(self, warning):
     def print_warning_instance(self, warning):
         assert isinstance(warning, BorgWarning)
         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):
     def print_file_status(self, status, path):
         # if we get called with status == None, the final file status was already printed
         # if we get called with status == None, the final file status was already printed
@@ -544,7 +544,7 @@ class Archiver:
                         rc = proc.wait()
                         rc = proc.wait()
                         if rc != 0:
                         if rc != 0:
                             raise CommandError('Command %r exited with status %d', args.paths[0], rc)
                             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)
                         raise Error('%s: %s', path, e)
                 else:
                 else:
                     status = '-'
                     status = '-'
@@ -568,9 +568,6 @@ class Archiver:
                             st = os_stat(path=path, parent_fd=None, name=None, follow_symlinks=False)
                             st = os_stat(path=path, parent_fd=None, name=None, follow_symlinks=False)
                         status = self._process_any(path=path, parent_fd=None, name=None, st=st, fso=fso,
                         status = self._process_any(path=path, parent_fd=None, name=None, st=st, fso=fso,
                                                    cache=cache, read_special=args.read_special, dry_run=dry_run)
                                                    cache=cache, 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:
                     except BackupError as e:
                         self.print_warning_instance(BackupWarning(path, e))
                         self.print_warning_instance(BackupWarning(path, e))
                         status = 'E'
                         status = 'E'
@@ -591,8 +588,8 @@ class Archiver:
                         if not dry_run:
                         if not dry_run:
                             try:
                             try:
                                 status = fso.process_pipe(path=path, cache=cache, fd=sys.stdin.buffer, mode=mode, user=user, group=group)
                                 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'
                                 status = 'E'
                         else:
                         else:
                             status = '-'
                             status = '-'
@@ -611,9 +608,9 @@ class Archiver:
                         # if we get back here, we've finished recursing into <path>,
                         # 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)
                         # 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))
                         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
                         # 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
                         continue
             if not dry_run:
             if not dry_run:
                 if args.progress:
                 if args.progress:
@@ -823,9 +820,6 @@ class Archiver:
                                     exclude_caches=exclude_caches, exclude_if_present=exclude_if_present,
                                     exclude_caches=exclude_caches, exclude_if_present=exclude_if_present,
                                     keep_exclude_tags=keep_exclude_tags, skip_inodes=skip_inodes, restrict_dev=restrict_dev,
                                     keep_exclude_tags=keep_exclude_tags, skip_inodes=skip_inodes, restrict_dev=restrict_dev,
                                     read_special=read_special, dry_run=dry_run)
                                     read_special=read_special, dry_run=dry_run)
-        except BackupOSError as e:
-            self.print_warning_instance(BackupOSWarning(path, e))
-            status = 'E'
         except BackupError as e:
         except BackupError as e:
             self.print_warning_instance(BackupWarning(path, e))
             self.print_warning_instance(BackupWarning(path, e))
             status = 'E'
             status = 'E'
@@ -902,8 +896,8 @@ class Archiver:
                     dir_item = dirs.pop(-1)
                     dir_item = dirs.pop(-1)
                     try:
                     try:
                         archive.extract_item(dir_item, stdout=stdout)
                         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:
             if output_list:
                 logging.getLogger('borg.output.list').info(remove_surrogates(item.path))
                 logging.getLogger('borg.output.list').info(remove_surrogates(item.path))
             try:
             try:
@@ -916,8 +910,6 @@ class Archiver:
                     else:
                     else:
                         archive.extract_item(item, stdout=stdout, sparse=sparse, hardlink_masters=hardlink_masters,
                         archive.extract_item(item, stdout=stdout, sparse=sparse, hardlink_masters=hardlink_masters,
                                              stripped_components=strip_components, original_path=orig_path, pi=pi)
                                              stripped_components=strip_components, original_path=orig_path, pi=pi)
-            except BackupOSError as e:
-                self.print_warning_instance(BackupOSWarning(remove_surrogates(orig_path), e))
             except BackupError as e:
             except BackupError as e:
                 self.print_warning_instance(BackupWarning(remove_surrogates(orig_path), e))
                 self.print_warning_instance(BackupWarning(remove_surrogates(orig_path), e))
         if pi:
         if pi:
@@ -931,8 +923,8 @@ class Archiver:
                 dir_item = dirs.pop(-1)
                 dir_item = dirs.pop(-1)
                 try:
                 try:
                     archive.extract_item(dir_item, stdout=stdout)
                     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():
         for pattern in matcher.get_unmatched_include_patterns():
             self.print_warning_instance(IncludePatternNeverMatchedWarning(pattern))
             self.print_warning_instance(IncludePatternNeverMatchedWarning(pattern))

+ 35 - 47
src/borg/helpers/errors.py

@@ -1,4 +1,3 @@
-import errno
 import os
 import os
 
 
 from ..constants import *  # NOQA
 from ..constants import *  # NOQA
@@ -102,66 +101,40 @@ class IncludePatternNeverMatchedWarning(BorgWarning):
 
 
 class BackupWarning(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
     @property
     def exit_code(self):
     def exit_code(self):
         if not modern_ec:
         if not modern_ec:
             return EXIT_WARNING
             return EXIT_WARNING
         exc = self.args[1]
         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
-
-
-class NotFoundWarning(BorgWarning):
-    """{}: {}"""
-    exit_mcode = 107
+        assert isinstance(exc, BackupError)
+        return exc.exit_mcode
 
 
 
 
-class BackupError(Exception):
-    """
-    Exception raised for non-OSError-based exceptions while accessing backup files.
-    """
+class BackupError(Error):
+    """{}: backup error"""
+    # Exception raised for non-OSError-based exceptions while accessing backup files.
+    exit_mcode = 102
 
 
 
 
 class BackupRaceConditionError(BackupError):
 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):
+    """{}: {}"""
+    # 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
 
 
-    Any unwrapped IO error is critical and aborts execution (for example repository IO failure).
-    """
     def __init__(self, op, os_error):
     def __init__(self, op, os_error):
         self.op = op
         self.op = op
         self.os_error = os_error
         self.os_error = os_error
@@ -174,3 +147,18 @@ class BackupOSError(Exception):
             return f'{self.op}: {self.os_error}'
             return f'{self.op}: {self.os_error}'
         else:
         else:
             return str(self.os_error)
             return str(self.os_error)
+
+
+class BackupPermissionError(BackupOSError):
+    """{}: {}"""
+    exit_mcode = 105
+
+
+class BackupIOError(BackupOSError):
+    """{}: {}"""
+    exit_mcode = 106
+
+
+class BackupFileNotFoundError(BackupOSError):
+    """{}: {}"""
+    exit_mcode = 107