Skip to content

Commit b27df15

Browse files
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.
1 parent e40298e commit b27df15

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

src/borg/archive.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,7 @@ def __init__(
12151215
log_json,
12161216
iec,
12171217
file_status_printer=None,
1218+
files_changed="ctime",
12181219
):
12191220
self.metadata_collector = metadata_collector
12201221
self.cache = cache
@@ -1223,6 +1224,7 @@ def __init__(
12231224
self.process_file_chunks = process_file_chunks
12241225
self.show_progress = show_progress
12251226
self.print_file_status = file_status_printer or (lambda *args: None)
1227+
self.files_changed = files_changed
12261228

12271229
self.hlm = HardLinkManager(id_type=tuple, info_type=(list, type(None))) # (dev, ino) -> chunks or None
12281230
self.stats = Statistics(output_json=log_json, iec=iec) # threading: done by cache (including progress)
@@ -1445,21 +1447,37 @@ def process_file(self, *, path, parent_fd, name, st, cache, flags=flags_normal,
14451447
if not is_win32: # TODO for win32
14461448
with backup_io("fstat2"):
14471449
st2 = os.fstat(fd)
1448-
if is_special_file:
1450+
if self.files_changed == "disabled" or is_special_file:
14491451
# special files:
14501452
# - fifos change naturally, because they are fed from the other side. no problem.
14511453
# - blk/chr devices don't change ctime anyway.
14521454
pass
1453-
elif st.st_ctime_ns != st2.st_ctime_ns:
1454-
# ctime was changed, this is either a metadata or a data change.
1455-
changed_while_backup = True
1456-
elif start_reading - TIME_DIFFERS1_NS < st2.st_ctime_ns < end_reading + TIME_DIFFERS1_NS:
1457-
# this is to treat a very special race condition, see #3536.
1458-
# - file was changed right before st.ctime was determined.
1459-
# - then, shortly afterwards, but already while we read the file, the
1460-
# file was changed again, but st2.ctime is the same due to ctime granularity.
1461-
# when comparing file ctime to local clock, widen interval by TIME_DIFFERS1_NS.
1462-
changed_while_backup = True
1455+
elif self.files_changed == "ctime":
1456+
if st.st_ctime_ns != st2.st_ctime_ns:
1457+
# ctime was changed, this is either a metadata or a data change.
1458+
changed_while_backup = True
1459+
elif (
1460+
start_reading - TIME_DIFFERS1_NS < st2.st_ctime_ns < end_reading + TIME_DIFFERS1_NS
1461+
):
1462+
# this is to treat a very special race condition, see #3536.
1463+
# - file was changed right before st.ctime was determined.
1464+
# - then, shortly afterwards, but already while we read the file, the
1465+
# file was changed again, but st2.ctime is the same due to ctime granularity.
1466+
# when comparing file ctime to local clock, widen interval by TIME_DIFFERS1_NS.
1467+
changed_while_backup = True
1468+
elif self.files_changed == "mtime":
1469+
if st.st_mtime_ns != st2.st_mtime_ns:
1470+
# mtime was changed, this is either a data change.
1471+
changed_while_backup = True
1472+
elif (
1473+
start_reading - TIME_DIFFERS1_NS < st2.st_mtime_ns < end_reading + TIME_DIFFERS1_NS
1474+
):
1475+
# this is to treat a very special race condition, see #3536.
1476+
# - file was changed right before st.mtime was determined.
1477+
# - then, shortly afterwards, but already while we read the file, the
1478+
# file was changed again, but st2.mtime is the same due to mtime granularity.
1479+
# when comparing file mtime to local clock, widen interval by TIME_DIFFERS1_NS.
1480+
changed_while_backup = True
14631481
if changed_while_backup:
14641482
# regular file changed while we backed it up, might be inconsistent/corrupt!
14651483
if last_try:

src/borg/archiver/create_cmd.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ def create_inner(archive, cache, fso):
262262
log_json=args.log_json,
263263
iec=args.iec,
264264
file_status_printer=self.print_file_status,
265+
files_changed=args.files_changed,
265266
)
266267
create_inner(archive, cache, fso)
267268
else:
@@ -611,6 +612,13 @@ def build_parser_create(self, subparsers, common_parser, mid_common_parser):
611612
it had before a content change happened. This can be used maliciously as well as
612613
well-meant, but in both cases mtime based cache modes can be problematic.
613614
615+
The ``--files-changed`` option controls how Borg detects if a file has changed during backup:
616+
- ctime (default): Use ctime to detect changes. This is the safest option.
617+
- mtime: Use mtime to detect changes.
618+
- disabled: Disable the "file has changed while we backed it up" detection completely.
619+
This is not recommended unless you know what you're doing, as it could lead to
620+
inconsistent backups if files change during the backup process.
621+
614622
The mount points of filesystems or filesystem snapshots should be the same for every
615623
creation of a new archive to ensure fast operation. This is because the file cache that
616624
is used to determine changed files quickly uses absolute filenames.
@@ -888,6 +896,15 @@ def build_parser_create(self, subparsers, common_parser, mid_common_parser):
888896
default=FILES_CACHE_MODE_UI_DEFAULT,
889897
help="operate files cache in MODE. default: %s" % FILES_CACHE_MODE_UI_DEFAULT,
890898
)
899+
fs_group.add_argument(
900+
"--files-changed",
901+
metavar="MODE",
902+
dest="files_changed",
903+
action=Highlander,
904+
choices=["ctime", "mtime", "disabled"],
905+
default="ctime",
906+
help="specify how to detect if a file has changed during backup (ctime, mtime, disabled). default: ctime",
907+
)
891908
fs_group.add_argument(
892909
"--read-special",
893910
dest="read_special",

0 commit comments

Comments
 (0)