diff --git a/src/borg/archive.py b/src/borg/archive.py index 5bbb6b7fd..fcc427e5d 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -1215,6 +1215,7 @@ class FilesystemObjectProcessors: log_json, iec, file_status_printer=None, + files_changed="ctime", ): self.metadata_collector = metadata_collector self.cache = cache @@ -1223,6 +1224,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.hlm = HardLinkManager(id_type=tuple, info_type=(list, type(None))) # (dev, ino) -> chunks or None self.stats = Statistics(output_json=log_json, iec=iec) # threading: done by cache (including progress) @@ -1445,21 +1447,37 @@ class FilesystemObjectProcessors: if not is_win32: # TODO for win32 with backup_io("fstat2"): st2 = os.fstat(fd) - if is_special_file: + if self.files_changed == "disabled" or is_special_file: # special files: # - fifos change naturally, because they are fed from the other side. no problem. # - blk/chr devices don't change ctime anyway. pass - elif st.st_ctime_ns != st2.st_ctime_ns: - # ctime was changed, this is either a metadata or a data change. - changed_while_backup = True - elif start_reading - TIME_DIFFERS1_NS < st2.st_ctime_ns < end_reading + TIME_DIFFERS1_NS: - # this is to treat a very special race condition, see #3536. - # - file was changed right before st.ctime was determined. - # - then, shortly afterwards, but already while we read the file, the - # file was changed again, but st2.ctime is the same due to ctime granularity. - # when comparing file ctime to local clock, widen interval by TIME_DIFFERS1_NS. - changed_while_backup = True + elif self.files_changed == "ctime": + if st.st_ctime_ns != st2.st_ctime_ns: + # ctime was changed, this is either a metadata or a data change. + changed_while_backup = True + elif ( + start_reading - TIME_DIFFERS1_NS < st2.st_ctime_ns < end_reading + TIME_DIFFERS1_NS + ): + # this is to treat a very special race condition, see #3536. + # - file was changed right before st.ctime was determined. + # - then, shortly afterwards, but already while we read the file, the + # file was changed again, but st2.ctime is the same due to ctime granularity. + # when comparing file ctime to local clock, widen interval by TIME_DIFFERS1_NS. + changed_while_backup = True + elif self.files_changed == "mtime": + if st.st_mtime_ns != st2.st_mtime_ns: + # mtime was changed, this is either a data change. + changed_while_backup = True + elif ( + start_reading - TIME_DIFFERS1_NS < st2.st_mtime_ns < end_reading + TIME_DIFFERS1_NS + ): + # this is to treat a very special race condition, see #3536. + # - file was changed right before st.mtime was determined. + # - then, shortly afterwards, but already while we read the file, the + # file was changed again, but st2.mtime is the same due to mtime granularity. + # when comparing file mtime to local clock, widen interval by TIME_DIFFERS1_NS. + changed_while_backup = True if changed_while_backup: # regular file changed while we backed it up, might be inconsistent/corrupt! if last_try: diff --git a/src/borg/archiver/create_cmd.py b/src/borg/archiver/create_cmd.py index 8a7aa4f82..9345264df 100644 --- a/src/borg/archiver/create_cmd.py +++ b/src/borg/archiver/create_cmd.py @@ -262,6 +262,7 @@ class CreateMixIn: 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: @@ -611,6 +612,13 @@ class CreateMixIn: 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. @@ -888,6 +896,15 @@ class CreateMixIn: 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",