Selaa lähdekoodia

raise BackupOSError subclasses

Thomas Waldmann 1 vuosi sitten
vanhempi
sitoutus
83bf4d8c7d
4 muutettua tiedostoa jossa 67 lisäystä ja 76 poistoa
  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
     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,3 +1,4 @@
+import errno
 import json
 import os
 import socket
@@ -25,7 +26,8 @@ from .crypto.key import key_factory, UnsupportedPayloadError
 from .compress import Compressor, 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 Manifest
 from .helpers import hardlinkable
@@ -190,7 +192,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()

+ 14 - 22
src/borg/archiver.py

@@ -47,7 +47,7 @@ try:
     from .crypto.keymanager import KeyManager
     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 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 PrefixSpec, GlobSpec, CommentSpec, PathSpec, SortBySpec, FilesCacheMode
     from .helpers import BaseFormatter, ItemFormatter, ArchiveFormatter
@@ -256,10 +256,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
@@ -544,7 +544,7 @@ class Archiver:
                         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 = '-'
@@ -568,9 +568,6 @@ class Archiver:
                             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,
                                                    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:
                         self.print_warning_instance(BackupWarning(path, e))
                         status = 'E'
@@ -591,8 +588,8 @@ class Archiver:
                         if not dry_run:
                             try:
                                 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 = '-'
@@ -611,9 +608,9 @@ class Archiver:
                         # 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:
@@ -823,9 +820,6 @@ class Archiver:
                                     exclude_caches=exclude_caches, exclude_if_present=exclude_if_present,
                                     keep_exclude_tags=keep_exclude_tags, skip_inodes=skip_inodes, restrict_dev=restrict_dev,
                                     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:
             self.print_warning_instance(BackupWarning(path, e))
             status = 'E'
@@ -902,8 +896,8 @@ class Archiver:
                     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:
@@ -916,8 +910,6 @@ class Archiver:
                     else:
                         archive.extract_item(item, stdout=stdout, sparse=sparse, hardlink_masters=hardlink_masters,
                                              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:
                 self.print_warning_instance(BackupWarning(remove_surrogates(orig_path), e))
         if pi:
@@ -931,8 +923,8 @@ class Archiver:
                 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))

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

@@ -1,4 +1,3 @@
-import errno
 import os
 
 from ..constants import *  # NOQA
@@ -102,66 +101,40 @@ 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
-
-
-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):
-    """
-    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):
         self.op = op
         self.os_error = os_error
@@ -174,3 +147,18 @@ 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