瀏覽代碼

fix overeager storing of hardlink masters

n.b. we only need to store them for items that we wouldn't extract.

this also fixes an intersting edge case in extracting hard links
with --strip-components
Marian Beermann 8 年之前
父節點
當前提交
cd39ccb821
共有 3 個文件被更改,包括 16 次插入12 次删除
  1. 5 3
      src/borg/archive.py
  2. 9 7
      src/borg/archiver.py
  3. 2 2
      src/borg/testsuite/archiver.py

+ 5 - 3
src/borg/archive.py

@@ -422,7 +422,7 @@ Number of files: {0.stats.nfiles}'''.format(
         return stats
         return stats
 
 
     def extract_item(self, item, restore_attrs=True, dry_run=False, stdout=False, sparse=False,
     def extract_item(self, item, restore_attrs=True, dry_run=False, stdout=False, sparse=False,
-                     hardlink_masters=None, original_path=None, pi=None):
+                     hardlink_masters=None, stripped_components=0, original_path=None, pi=None):
         """
         """
         Extract archive item.
         Extract archive item.
 
 
@@ -432,9 +432,11 @@ Number of files: {0.stats.nfiles}'''.format(
         :param stdout: write extracted data to stdout
         :param stdout: write extracted data to stdout
         :param sparse: write sparse files (chunk-granularity, independent of the original being sparse)
         :param sparse: write sparse files (chunk-granularity, independent of the original being sparse)
         :param hardlink_masters: maps paths to (chunks, link_target) for extracting subtrees with hardlinks correctly
         :param hardlink_masters: maps paths to (chunks, link_target) for extracting subtrees with hardlinks correctly
+        :param stripped_components: stripped leading path components to correct hard link extraction
         :param original_path: 'path' key as stored in archive
         :param original_path: 'path' key as stored in archive
         :param pi: ProgressIndicatorPercent (or similar) for file extraction progress (in bytes)
         :param pi: ProgressIndicatorPercent (or similar) for file extraction progress (in bytes)
         """
         """
+        hardlink_masters = hardlink_masters or {}
         has_damaged_chunks = 'chunks_healthy' in item
         has_damaged_chunks = 'chunks_healthy' in item
         if dry_run or stdout:
         if dry_run or stdout:
             if 'chunks' in item:
             if 'chunks' in item:
@@ -473,11 +475,11 @@ Number of files: {0.stats.nfiles}'''.format(
                     os.makedirs(os.path.dirname(path))
                     os.makedirs(os.path.dirname(path))
             # Hard link?
             # Hard link?
             if 'source' in item:
             if 'source' in item:
-                source = os.path.join(dest, item.source)
+                source = os.path.join(dest, *item.source.split(os.sep)[stripped_components:])
                 with backup_io():
                 with backup_io():
                     if os.path.exists(path):
                     if os.path.exists(path):
                         os.unlink(path)
                         os.unlink(path)
-                    if not hardlink_masters:
+                    if item.source not in hardlink_masters:
                         os.link(source, path)
                         os.link(source, path)
                         return
                         return
                 item.chunks, link_target = hardlink_masters[item.source]
                 item.chunks, link_target = hardlink_masters[item.source]

+ 9 - 7
src/borg/archiver.py

@@ -420,12 +420,14 @@ class Archiver:
     def build_filter(matcher, peek_and_store_hardlink_masters, strip_components):
     def build_filter(matcher, peek_and_store_hardlink_masters, strip_components):
         if strip_components:
         if strip_components:
             def item_filter(item):
             def item_filter(item):
-                peek_and_store_hardlink_masters(item)
-                return matcher.match(item.path) and os.sep.join(item.path.split(os.sep)[strip_components:])
+                matched = matcher.match(item.path) and os.sep.join(item.path.split(os.sep)[strip_components:])
+                peek_and_store_hardlink_masters(item, matched)
+                return matched
         else:
         else:
             def item_filter(item):
             def item_filter(item):
-                peek_and_store_hardlink_masters(item)
-                return matcher.match(item.path)
+                matched = matcher.match(item.path)
+                peek_and_store_hardlink_masters(item, matched)
+                return matched
         return item_filter
         return item_filter
 
 
     @with_repository()
     @with_repository()
@@ -450,8 +452,8 @@ class Archiver:
         partial_extract = not matcher.empty() or strip_components
         partial_extract = not matcher.empty() or strip_components
         hardlink_masters = {} if partial_extract else None
         hardlink_masters = {} if partial_extract else None
 
 
-        def peek_and_store_hardlink_masters(item):
-            if (partial_extract and stat.S_ISREG(item.mode) and
+        def peek_and_store_hardlink_masters(item, matched):
+            if (partial_extract and not matched and stat.S_ISREG(item.mode) and
                     item.get('hardlink_master', True) and 'source' not in item):
                     item.get('hardlink_master', True) and 'source' not in item):
                 hardlink_masters[item.get('path')] = (item.get('chunks'), None)
                 hardlink_masters[item.get('path')] = (item.get('chunks'), None)
 
 
@@ -486,7 +488,7 @@ class Archiver:
                         archive.extract_item(item, restore_attrs=False)
                         archive.extract_item(item, restore_attrs=False)
                     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,
-                                             original_path=orig_path, pi=pi)
+                                             stripped_components=strip_components, original_path=orig_path, pi=pi)
             except BackupOSError as e:
             except BackupOSError as e:
                 self.print_warning('%s: %s', remove_surrogates(orig_path), e)
                 self.print_warning('%s: %s', remove_surrogates(orig_path), e)
 
 

+ 2 - 2
src/borg/testsuite/archiver.py

@@ -2204,8 +2204,8 @@ def test_compare_chunk_contents():
 
 
 class TestBuildFilter:
 class TestBuildFilter:
     @staticmethod
     @staticmethod
-    def peek_and_store_hardlink_masters(item):
-        return False
+    def peek_and_store_hardlink_masters(item, matched):
+        pass
 
 
     def test_basic(self):
     def test_basic(self):
         matcher = PatternMatcher()
         matcher = PatternMatcher()