From cd39ccb82106089129ced9ccccabf7a4703f0207 Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Thu, 25 Aug 2016 21:16:20 +0200 Subject: [PATCH] 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 --- src/borg/archive.py | 8 +++++--- src/borg/archiver.py | 16 +++++++++------- src/borg/testsuite/archiver.py | 4 ++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/borg/archive.py b/src/borg/archive.py index f4acf8749..c27faf67c 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -422,7 +422,7 @@ Number of files: {0.stats.nfiles}'''.format( return stats 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. @@ -432,9 +432,11 @@ Number of files: {0.stats.nfiles}'''.format( :param stdout: write extracted data to stdout :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 stripped_components: stripped leading path components to correct hard link extraction :param original_path: 'path' key as stored in archive :param pi: ProgressIndicatorPercent (or similar) for file extraction progress (in bytes) """ + hardlink_masters = hardlink_masters or {} has_damaged_chunks = 'chunks_healthy' in item if dry_run or stdout: if 'chunks' in item: @@ -473,11 +475,11 @@ Number of files: {0.stats.nfiles}'''.format( os.makedirs(os.path.dirname(path)) # Hard link? 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(): if os.path.exists(path): os.unlink(path) - if not hardlink_masters: + if item.source not in hardlink_masters: os.link(source, path) return item.chunks, link_target = hardlink_masters[item.source] diff --git a/src/borg/archiver.py b/src/borg/archiver.py index cf2f233f1..f5ebd7308 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -420,12 +420,14 @@ class Archiver: def build_filter(matcher, peek_and_store_hardlink_masters, strip_components): if strip_components: 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: 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 @with_repository() @@ -450,8 +452,8 @@ class Archiver: partial_extract = not matcher.empty() or strip_components 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): hardlink_masters[item.get('path')] = (item.get('chunks'), None) @@ -486,7 +488,7 @@ class Archiver: archive.extract_item(item, restore_attrs=False) else: 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: self.print_warning('%s: %s', remove_surrogates(orig_path), e) diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index aa6614316..fd7eb5fc8 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -2204,8 +2204,8 @@ def test_compare_chunk_contents(): class TestBuildFilter: @staticmethod - def peek_and_store_hardlink_masters(item): - return False + def peek_and_store_hardlink_masters(item, matched): + pass def test_basic(self): matcher = PatternMatcher()