mirror of
https://github.com/borgbackup/borg.git
synced 2025-01-01 12:45:34 +00:00
implement --nobsdflags and --exclude-nodump, fixes #3160
do no read/archive bsdflags: borg create --nobsdflags ... do not extract/set bsdflags: borg extract --nobsdflags ... use cases: - fs shows wrong / random bsdflags (bug in filesystem) - fs does not support bsdflags anyway - already archived bsdflags are wrong / unwanted - borg shows any sort of unwanted effect due to get_flags, esp. on Linux the nodump flag ("do not backup this file") is not honoured any more by default because this functionality (esp. if it happened by error or unexpected) was rather confusing and unexplainable at first to users. if you want that "do not backup NODUMP-flagged files" behaviour, use: borg create --exclude-nodump ...
This commit is contained in:
parent
ac0ad9adf0
commit
10adadf685
3 changed files with 36 additions and 19 deletions
|
@ -282,8 +282,8 @@ class IncompatibleFilesystemEncodingError(Error):
|
|||
"""Failed to encode filename "{}" into file system encoding "{}". Consider configuring the LANG environment variable."""
|
||||
|
||||
def __init__(self, repository, key, manifest, name, cache=None, create=False,
|
||||
checkpoint_interval=300, numeric_owner=False, noatime=False, noctime=False, progress=False,
|
||||
chunker_params=CHUNKER_PARAMS, start=None, start_monotonic=None, end=None,
|
||||
checkpoint_interval=300, numeric_owner=False, noatime=False, noctime=False, nobsdflags=False,
|
||||
progress=False, chunker_params=CHUNKER_PARAMS, start=None, start_monotonic=None, end=None,
|
||||
consider_part_files=False, log_json=False):
|
||||
self.cwd = os.getcwd()
|
||||
self.key = key
|
||||
|
@ -300,6 +300,7 @@ def __init__(self, repository, key, manifest, name, cache=None, create=False,
|
|||
self.numeric_owner = numeric_owner
|
||||
self.noatime = noatime
|
||||
self.noctime = noctime
|
||||
self.nobsdflags = nobsdflags
|
||||
assert (start is None) == (start_monotonic is None), 'Logic error: if start is given, start_monotonic must be given as well and vice versa.'
|
||||
if start is None:
|
||||
start = datetime.utcnow()
|
||||
|
@ -691,7 +692,8 @@ def restore_attrs(self, path, item, symlink=False, fd=None):
|
|||
# some systems don't support calling utime on a symlink
|
||||
pass
|
||||
acl_set(path, item, self.numeric_owner)
|
||||
if 'bsdflags' in item:
|
||||
|
||||
if not self.nobsdflags and 'bsdflags' in item:
|
||||
try:
|
||||
set_flags(path, item.bsdflags, fd=fd)
|
||||
except OSError:
|
||||
|
@ -903,10 +905,11 @@ def defer_if_necessary(item1, item2):
|
|||
|
||||
|
||||
class MetadataCollector:
|
||||
def __init__(self, *, noatime, noctime, numeric_owner):
|
||||
def __init__(self, *, noatime, noctime, numeric_owner, nobsdflags):
|
||||
self.noatime = noatime
|
||||
self.noctime = noctime
|
||||
self.numeric_owner = numeric_owner
|
||||
self.nobsdflags = nobsdflags
|
||||
|
||||
def stat_simple_attrs(self, st):
|
||||
attrs = dict(
|
||||
|
@ -931,9 +934,11 @@ def stat_simple_attrs(self, st):
|
|||
|
||||
def stat_ext_attrs(self, st, path):
|
||||
attrs = {}
|
||||
bsdflags = 0
|
||||
with backup_io('extended stat'):
|
||||
xattrs = xattr.get_all(path, follow_symlinks=False)
|
||||
bsdflags = get_flags(path, st)
|
||||
if not self.nobsdflags:
|
||||
bsdflags = get_flags(path, st)
|
||||
acl_get(path, attrs, st, self.numeric_owner)
|
||||
if xattrs:
|
||||
attrs['xattrs'] = StableDict(xattrs)
|
||||
|
|
|
@ -154,7 +154,9 @@ def with_archive(method):
|
|||
@functools.wraps(method)
|
||||
def wrapper(self, args, repository, key, manifest, **kwargs):
|
||||
archive = Archive(repository, key, manifest, args.location.archive,
|
||||
numeric_owner=getattr(args, 'numeric_owner', False), cache=kwargs.get('cache'),
|
||||
numeric_owner=getattr(args, 'numeric_owner', False),
|
||||
nobsdflags=getattr(args, 'nobsdflags', False),
|
||||
cache=kwargs.get('cache'),
|
||||
consider_part_files=args.consider_part_files, log_json=args.log_json)
|
||||
return method(self, args, repository=repository, manifest=manifest, key=key, archive=archive, **kwargs)
|
||||
return wrapper
|
||||
|
@ -485,6 +487,8 @@ def create_inner(archive, cache, fso):
|
|||
self.output_filter = args.output_filter
|
||||
self.output_list = args.output_list
|
||||
self.ignore_inode = args.ignore_inode
|
||||
self.nobsdflags = args.nobsdflags
|
||||
self.exclude_nodump = args.exclude_nodump
|
||||
self.files_cache_mode = args.files_cache_mode
|
||||
dry_run = args.dry_run
|
||||
t0 = datetime.utcnow()
|
||||
|
@ -499,7 +503,7 @@ def create_inner(archive, cache, fso):
|
|||
chunker_params=args.chunker_params, start=t0, start_monotonic=t0_monotonic,
|
||||
log_json=args.log_json)
|
||||
metadata_collector = MetadataCollector(noatime=args.noatime, noctime=args.noctime,
|
||||
numeric_owner=args.numeric_owner)
|
||||
nobsdflags=args.nobsdflags, numeric_owner=args.numeric_owner)
|
||||
cp = ChunksProcessor(cache=cache, key=key,
|
||||
add_item=archive.add_item, write_checkpoint=archive.write_checkpoint,
|
||||
checkpoint_interval=args.checkpoint_interval)
|
||||
|
@ -542,11 +546,12 @@ def _process(self, fso, cache, matcher, exclude_caches, exclude_if_present,
|
|||
# directory of the mounted filesystem that shadows the mountpoint dir).
|
||||
recurse = restrict_dev is None or st.st_dev == restrict_dev
|
||||
status = None
|
||||
# Ignore if nodump flag is set
|
||||
with backup_io('flags'):
|
||||
if get_flags(path, st) & stat.UF_NODUMP:
|
||||
self.print_file_status('x', path)
|
||||
return
|
||||
if self.exclude_nodump:
|
||||
# Ignore if nodump flag is set
|
||||
with backup_io('flags'):
|
||||
if get_flags(path, st) & stat.UF_NODUMP:
|
||||
self.print_file_status('x', path)
|
||||
return
|
||||
if stat.S_ISREG(st.st_mode):
|
||||
if not dry_run:
|
||||
status = fso.process_file(path, st, cache, self.ignore_inode, self.files_cache_mode)
|
||||
|
@ -2198,6 +2203,7 @@ def define_exclude_and_patterns(add_option, *, tag_files=False, strip_components
|
|||
def define_exclusion_group(subparser, **kwargs):
|
||||
exclude_group = subparser.add_argument_group('Exclusion options')
|
||||
define_exclude_and_patterns(exclude_group.add_argument, **kwargs)
|
||||
return exclude_group
|
||||
|
||||
def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
|
||||
filters_group = subparser.add_argument_group('Archive filters',
|
||||
|
@ -2727,7 +2733,9 @@ def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
|
|||
subparser.add_argument('--no-files-cache', dest='cache_files', action='store_false',
|
||||
help='do not load/update the file metadata cache used to detect unchanged files')
|
||||
|
||||
define_exclusion_group(subparser, tag_files=True)
|
||||
exclude_group = define_exclusion_group(subparser, tag_files=True)
|
||||
exclude_group.add_argument('--exclude-nodump', dest='exclude_nodump', action='store_true',
|
||||
help='exclude files flagged NODUMP')
|
||||
|
||||
fs_group = subparser.add_argument_group('Filesystem options')
|
||||
fs_group.add_argument('-x', '--one-file-system', dest='one_file_system', action='store_true',
|
||||
|
@ -2738,6 +2746,8 @@ def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
|
|||
help='do not store atime into archive')
|
||||
fs_group.add_argument('--noctime', dest='noctime', action='store_true',
|
||||
help='do not store ctime into archive')
|
||||
fs_group.add_argument('--nobsdflags', dest='nobsdflags', action='store_true',
|
||||
help='do not read and store bsdflags (e.g. NODUMP, IMMUTABLE) into archive')
|
||||
fs_group.add_argument('--ignore-inode', dest='ignore_inode', action='store_true',
|
||||
help='ignore inode data in the file metadata cache used to detect unchanged files.')
|
||||
fs_group.add_argument('--files-cache', metavar='MODE', dest='files_cache_mode',
|
||||
|
@ -2804,6 +2814,8 @@ def define_archive_filters_group(subparser, *, sort_by=True, first_last=True):
|
|||
help='do not actually change any files')
|
||||
subparser.add_argument('--numeric-owner', dest='numeric_owner', action='store_true',
|
||||
help='only obey numeric user and group identifiers')
|
||||
subparser.add_argument('--nobsdflags', dest='nobsdflags', action='store_true',
|
||||
help='do not extract/set bsdflags (e.g. NODUMP, IMMUTABLE)')
|
||||
subparser.add_argument('--stdout', dest='stdout', action='store_true',
|
||||
help='write all extracted data to stdout')
|
||||
subparser.add_argument('--sparse', dest='sparse', action='store_true',
|
||||
|
|
|
@ -380,8 +380,8 @@ def test_basic_functionality(self):
|
|||
output = self.cmd('init', '--encryption=repokey', '--show-version', '--show-rc', self.repository_location, fork=True)
|
||||
self.assert_in('borgbackup version', output)
|
||||
self.assert_in('terminating with success status, rc 0', output)
|
||||
self.cmd('create', self.repository_location + '::test', 'input')
|
||||
output = self.cmd('create', '--stats', self.repository_location + '::test.2', 'input')
|
||||
self.cmd('create', '--exclude-nodump', self.repository_location + '::test', 'input')
|
||||
output = self.cmd('create', '--exclude-nodump', '--stats', self.repository_location + '::test.2', 'input')
|
||||
self.assert_in('Archive name: test.2', output)
|
||||
self.assert_in('This archive: ', output)
|
||||
with changedir('output'):
|
||||
|
@ -1655,13 +1655,13 @@ def test_file_status_excluded(self):
|
|||
self.create_regular_file('file3', size=1024 * 80)
|
||||
platform.set_flags(os.path.join(self.input_path, 'file3'), stat.UF_NODUMP)
|
||||
self.cmd('init', '--encryption=repokey', self.repository_location)
|
||||
output = self.cmd('create', '--list', self.repository_location + '::test', 'input')
|
||||
output = self.cmd('create', '--list', '--exclude-nodump', self.repository_location + '::test', 'input')
|
||||
self.assert_in("A input/file1", output)
|
||||
self.assert_in("A input/file2", output)
|
||||
if has_lchflags:
|
||||
self.assert_in("x input/file3", output)
|
||||
# should find second file as excluded
|
||||
output = self.cmd('create', '--list', self.repository_location + '::test1', 'input', '--exclude', '*/file2')
|
||||
output = self.cmd('create', '--list', '--exclude-nodump', self.repository_location + '::test1', 'input', '--exclude', '*/file2')
|
||||
self.assert_in("U input/file1", output)
|
||||
self.assert_in("x input/file2", output)
|
||||
if has_lchflags:
|
||||
|
@ -2059,8 +2059,8 @@ def has_noatime(some_file):
|
|||
self.cmd('init', '--encryption=repokey', self.repository_location)
|
||||
self.create_test_files()
|
||||
have_noatime = has_noatime('input/file1')
|
||||
self.cmd('create', self.repository_location + '::archive', 'input')
|
||||
self.cmd('create', self.repository_location + '::archive2', 'input')
|
||||
self.cmd('create', '--exclude-nodump', self.repository_location + '::archive', 'input')
|
||||
self.cmd('create', '--exclude-nodump', self.repository_location + '::archive2', 'input')
|
||||
if has_lchflags:
|
||||
# remove the file we did not backup, so input and output become equal
|
||||
os.remove(os.path.join('input', 'flagfile'))
|
||||
|
|
Loading…
Reference in a new issue