From e5d04710ad44ed386cf3bc17aeeb5549371152ad Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 18 Jul 2025 20:40:39 +0200 Subject: [PATCH] create --files-changed=MODE option control how borg detects whether a file has changed while it was backed up, valid modes are ctime, mtime or disabled. ctime is the safest mode and the default. mtime can be useful if ctime does not work correctly for some reason (e.g. OneDrive files change their ctime without the user changing the file). disabled (= disabling change detection) is not recommended as it could lead to inconsistent backups. Only use if you know what you are doing. --- src/borg/archive.py | 13 +++++++++++-- src/borg/archiver.py | 14 +++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) 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.')