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
This commit is contained in:
Marian Beermann 2016-08-25 21:16:20 +02:00
parent b5d7f1df26
commit cd39ccb821
3 changed files with 16 additions and 12 deletions

View file

@ -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]

View file

@ -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)

View file

@ -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()