Browse Source

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.
Thomas Waldmann 1 month ago
parent
commit
e5d04710ad
2 changed files with 24 additions and 3 deletions
  1. 11 2
      src/borg/archive.py
  2. 13 1
      src/borg/archiver.py

+ 11 - 2
src/borg/archive.py

@@ -1302,7 +1302,8 @@ class FilesystemObjectProcessors:
     def __init__(self, *, metadata_collector, cache, key,
     def __init__(self, *, metadata_collector, cache, key,
                  add_item, process_file_chunks,
                  add_item, process_file_chunks,
                  chunker_params, show_progress, sparse,
                  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.metadata_collector = metadata_collector
         self.cache = cache
         self.cache = cache
         self.key = key
         self.key = key
@@ -1310,6 +1311,7 @@ class FilesystemObjectProcessors:
         self.process_file_chunks = process_file_chunks
         self.process_file_chunks = process_file_chunks
         self.show_progress = show_progress
         self.show_progress = show_progress
         self.print_file_status = file_status_printer or (lambda *args: None)
         self.print_file_status = file_status_printer or (lambda *args: None)
+        self.files_changed = files_changed
 
 
         self.hard_links = {}
         self.hard_links = {}
         self.stats = Statistics(output_json=log_json, iec=iec)  # threading: done by cache (including progress)
         self.stats = Statistics(output_json=log_json, iec=iec)  # threading: done by cache (including progress)
@@ -1482,7 +1484,14 @@ class FilesystemObjectProcessors:
                             # special files:
                             # special files:
                             # - fifos change naturally, because they are fed from the other side. no problem.
                             # - fifos change naturally, because they are fed from the other side. no problem.
                             # - blk/chr devices don't change ctime anyway.
                             # - 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:
                         if changed_while_backup:
                             status = 'C'  # regular file changed while we backed it up, might be inconsistent/corrupt!
                             status = 'C'  # regular file changed while we backed it up, might be inconsistent/corrupt!
                         if not is_special_file and not changed_while_backup:
                         if not is_special_file and not changed_while_backup:

+ 13 - 1
src/borg/archiver.py

@@ -692,7 +692,8 @@ class Archiver:
                 fso = FilesystemObjectProcessors(metadata_collector=metadata_collector, cache=cache, key=key,
                 fso = FilesystemObjectProcessors(metadata_collector=metadata_collector, cache=cache, key=key,
                     process_file_chunks=cp.process_file_chunks, add_item=archive.add_item,
                     process_file_chunks=cp.process_file_chunks, add_item=archive.add_item,
                     chunker_params=args.chunker_params, show_progress=args.progress, sparse=args.sparse,
                     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)
                 create_inner(archive, cache, fso)
         else:
         else:
             create_inner(None, None, None)
             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
           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.
           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
         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
         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.
         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,
         fs_group.add_argument('--files-cache', metavar='MODE', dest='files_cache_mode', action=Highlander,
                               type=FilesCacheMode, default=FILES_CACHE_MODE_UI_DEFAULT,
                               type=FilesCacheMode, default=FILES_CACHE_MODE_UI_DEFAULT,
                               help='operate files cache in MODE. default: %s' % 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',
         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 '
                               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.')
                                    'regular files. Also follows symlinks pointing to these kinds of files.')