diff --git a/src/borg/archive.py b/src/borg/archive.py index 25a9b9b43..0ec5d7b9e 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -1302,7 +1302,8 @@ class FilesystemObjectProcessors: def __init__(self, *, metadata_collector, cache, key, add_item, process_file_chunks, chunker_params, show_progress, sparse, - log_json, iec, file_status_printer=None): + log_json, iec, file_status_printer=None, + files_changed='ctime'): self.metadata_collector = metadata_collector self.cache = cache self.key = key @@ -1310,6 +1311,7 @@ class FilesystemObjectProcessors: self.process_file_chunks = process_file_chunks self.show_progress = show_progress self.print_file_status = file_status_printer or (lambda *args: None) + self.files_changed = files_changed self.hard_links = {} self.stats = Statistics(output_json=log_json, iec=iec) # threading: done by cache (including progress) @@ -1482,7 +1484,14 @@ class FilesystemObjectProcessors: # special files: # - fifos change naturally, because they are fed from the other side. no problem. # - blk/chr devices don't change ctime anyway. - changed_while_backup = not is_special_file and st.st_ctime_ns != st2.st_ctime_ns + if self.files_changed == 'disabled' or is_special_file: + changed_while_backup = False + elif self.files_changed == 'ctime': + changed_while_backup = st.st_ctime_ns != st2.st_ctime_ns + elif self.files_changed == 'mtime': + changed_while_backup = st.st_mtime_ns != st2.st_mtime_ns + else: + raise ValueError('invalid files_changed value: %r' % self.files_changed) if changed_while_backup: status = 'C' # regular file changed while we backed it up, might be inconsistent/corrupt! if not is_special_file and not changed_while_backup: diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 0ab5b62b3..498d87b51 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -692,7 +692,8 @@ class Archiver: fso = FilesystemObjectProcessors(metadata_collector=metadata_collector, cache=cache, key=key, process_file_chunks=cp.process_file_chunks, add_item=archive.add_item, chunker_params=args.chunker_params, show_progress=args.progress, sparse=args.sparse, - log_json=args.log_json, iec=args.iec, file_status_printer=self.print_file_status) + log_json=args.log_json, iec=args.iec, file_status_printer=self.print_file_status, + files_changed=args.files_changed) create_inner(archive, cache, fso) else: create_inner(None, None, None) @@ -3563,6 +3564,14 @@ class Archiver: it had before a content change happened. This can be used maliciously as well as well-meant, but in both cases mtime based cache modes can be problematic. + The ``--files-changed`` option controls how Borg detects if a file has changed during backup: + + - ctime (default): Use ctime to detect changes. This is the safest option. + - mtime: Use mtime to detect changes. + - disabled: Disable the "file has changed while we backed it up" detection completely. + This is not recommended unless you know what you're doing, as it could lead to + inconsistent backups if files change during the backup process. + The mount points of filesystems or filesystem snapshots should be the same for every creation of a new archive to ensure fast operation. This is because the file cache that is used to determine changed files quickly uses absolute filenames. @@ -3776,6 +3785,9 @@ class Archiver: fs_group.add_argument('--files-cache', metavar='MODE', dest='files_cache_mode', action=Highlander, type=FilesCacheMode, default=FILES_CACHE_MODE_UI_DEFAULT, help='operate files cache in MODE. default: %s' % FILES_CACHE_MODE_UI_DEFAULT) + fs_group.add_argument('--files-changed', metavar='MODE', dest='files_changed', action=Highlander, + choices=['ctime', 'mtime', 'disabled'], default='ctime', + help='specify how to detect if a file has changed during backup (ctime, mtime, disabled). default: ctime') fs_group.add_argument('--read-special', dest='read_special', action='store_true', help='open and read block and char device files as well as FIFOs as if they were ' 'regular files. Also follows symlinks pointing to these kinds of files.')