extend tarfile API for rescue mode
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 22 Aug 2017 13:30:15 +0000 (15:30 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Mon, 2 Apr 2018 11:34:09 +0000 (13:34 +0200)
deltatar/deltatar.py
deltatar/tarfile.py

index 55a1905..b1b8ccc 100644 (file)
@@ -1296,7 +1296,7 @@ class DeltaTar(object):
 
     def restore_backup(self, target_path, backup_indexes_paths=[],
                        backup_tar_path=None, restore_callback=None,
-                       disaster=False):
+                       disaster=tarfile.TOLERANCE_STRICT):
         '''
         Restores a backup.
 
@@ -1386,7 +1386,8 @@ class DeltaTar(object):
             helper = RestoreHelper(self, cwd, backup_path=backup_tar_path,
                                    tarobj=index_it.tar_obj)
         elif mode == "diff":
-            helper = RestoreHelper(self, cwd, backup_indexes_paths, disaster=disaster)
+            helper = RestoreHelper(self, cwd, backup_indexes_paths,
+                                   disaster=disaster)
             try:
                 # get iterator from newest index at _data[0]
                 index1 = helper._data[0]["path"]
@@ -1439,7 +1440,7 @@ class DeltaTar(object):
                     iipath = ipath.get ("path", "")
                     self.logger.error("FAILED to restore: {} ({})"
                                       .format(iipath, e))
-                    if disaster is True:
+                    if disaster != tarfile.TOLERANCE_STRICT:
                         failed.append ((iipath, e))
                 continue
 
@@ -1459,7 +1460,7 @@ class DeltaTar(object):
                 try:
                     helper.restore(ipath, l_no, restore_callback)
                 except Exception as e:
-                    if disaster is False:
+                    if disaster == tarfile.TOLERANCE_STRICT:
                         raise
                     failed.append ((ipath.get ("path", ""), e))
                     continue
@@ -1486,7 +1487,19 @@ class DeltaTar(object):
         """
         return self.restore_backup(target_path,
                                    backup_indexes_paths=backup_indexes_paths,
-                                   disaster=True)
+                                   disaster=tarfile.TOLERANCE_RECOVER)
+
+
+    def rescue_backup(self, target_path, backup_indexes_paths=[],
+                      restore_callback=None):
+        """
+        More aggressive “unfsck” mode: do not rely on the index data as the
+        files may be corrupt; skim files for header-like information and
+        attempt to retrieve the data.
+        """
+        return self.restore_backup(target_path,
+                                   backup_indexes_paths=backup_indexes_paths,
+                                   disaster=tarfile.TOLERANCE_RESCUE)
 
 
     def _parse_json_line(self, f, l_no):
@@ -1529,10 +1542,10 @@ class RestoreHelper(object):
     # tarfile.extractall for details.
     _directories = []
 
-    _disaster = False
+    _disaster = tarfile.TOLERANCE_STRICT
 
     def __init__(self, deltatar, cwd, index_list=None, backup_path=False,
-                 tarobj=None, disaster=False):
+                 tarobj=None, disaster=tarfile.TOLERANCE_STRICT):
         '''
         Constructor opens the tars and init the data structures.
 
@@ -1878,7 +1891,7 @@ class RestoreHelper(object):
                     encryption=index_data["decryptor"],
                     new_volume_handler=index_data['new_volume_handler'],
                     save_to_members=False,
-                    tolerant=self._disaster)
+                    tolerance=self._disaster)
 
                 member = index_data['tarobj'].__iter__().__next__()
 
index a57983f..cc74e92 100644 (file)
@@ -126,6 +126,10 @@ GZ_MAGIC_BYTES       = struct.pack ("<BB", GZ_MAGIC [0], GZ_MAGIC [1])
 GZ_MAGIC_DEFLATE     = struct.pack ("<BBB", GZ_MAGIC [0], GZ_MAGIC [1],
                                     GZ_METHOD_DEFLATE)
 
+TOLERANCE_STRICT  = 0
+TOLERANCE_RECOVER = 1 # rely on offsets in index
+TOLERANCE_RESCUE  = 2 # deduce metadata from archive contents
+
 #---------------------------------------------------------
 # archive handling mode
 #---------------------------------------------------------
@@ -466,15 +470,15 @@ class _Stream:
     """
 
     remainder = -1 # track size in encrypted entries
-    tolerant  = False
+    tolerance = TOLERANCE_STRICT
 
     def __init__(self, name, mode, comptype, fileobj, bufsize,
                  concat=False, encryption=None, enccounter=None,
-                 compresslevel=9, tolerant=False):
+                 compresslevel=9, tolerance=TOLERANCE_STRICT):
         """Construct a _Stream object.
         """
         self.arcmode = arcmode_set (concat, encryption, comptype)
-        self.tolerant = tolerant
+        self.tolerance = tolerance
 
         self._extfileobj = True
         if fileobj is None:
@@ -891,7 +895,7 @@ class _Stream:
         try:
             return self.encryption.process (buf)
         except RuntimeError as exn:
-            if self.tolerant is True:
+            if self.tolerance != TOLERANCE_STRICT:
                 raise DecryptionError (exn)
             raise
 
@@ -1008,7 +1012,7 @@ class _Stream:
                     try:
                         self._init_read_gz()
                     except DecryptionError:
-                        if self.tolerant is True:
+                        if self.tolerance != TOLERANCE_STRICT:
                             # return whatever data was processed successfully
                             if len (buf) > 0:
                                 t.append (buf)
@@ -1067,7 +1071,7 @@ class _Stream:
                     else:
                         self.remainder -= todo
             except DecryptionError:
-                if self.tolerant is False:
+                if self.tolerance == TOLERANCE_STRICT:
                     raise
                 self.encryption.drop ()
                 if good_crypto == 0:
@@ -2101,7 +2105,8 @@ class TarFile(object):
 
     @classmethod
     def open(cls, name=None, mode="r", fileobj=None, bufsize=RECORDSIZE,
-             encryption=None, compresslevel=9, tolerant=False, **kwargs):
+             encryption=None, compresslevel=9, tolerance=TOLERANCE_STRICT,
+             **kwargs):
         """Open a tar archive for reading, writing or appending. Return
            an appropriate TarFile class.
 
@@ -2198,7 +2203,7 @@ class TarFile(object):
 
             stream = _Stream(name, filemode, comptype, fileobj, bufsize,
                              concat=True, encryption=encryption,
-                             compresslevel=compresslevel, tolerant=tolerant)
+                             compresslevel=compresslevel, tolerance=tolerance)
             kwargs ["concat"] = True
             try:
                 t = cls(name, filemode, stream, **kwargs)