Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 30 additions & 35 deletions src/borg/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -1242,7 +1242,7 @@ def __init__(
log_json,
iec,
file_status_printer=None,
files_changed="ctime",
files_changed="mtime" if is_win32 else "ctime",
):
self.metadata_collector = metadata_collector
self.cache = cache
Expand Down Expand Up @@ -1471,40 +1471,35 @@ def process_file(self, *, path, parent_fd, name, st, cache, flags=flags_normal,
)
self.stats.chunking_time = self.chunker.chunking_time
end_reading = time.time_ns()
if not is_win32: # TODO for win32
with backup_io("fstat2"):
st2 = os.fstat(fd)
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 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
with backup_io("fstat2"):
st2 = os.fstat(fd)
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 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:
Expand Down
17 changes: 13 additions & 4 deletions src/borg/archiver/create_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ def create_inner(archive, cache, fso):
nobirthtime=args.nobirthtime,
)
cp = ChunksProcessor(cache=cache, key=key, add_item=archive.add_item, rechunkify=False)
if is_win32 and args.files_changed == "ctime":
self.print_warning(
"--files-changed=ctime is not supported on Windows "
"(ctime is file creation time, not change time). Using mtime instead.",
wc=None,
)
args.files_changed = "mtime"
fso = FilesystemObjectProcessors(
metadata_collector=metadata_collector,
cache=cache,
Expand Down Expand Up @@ -621,8 +628,9 @@ def build_parser_create(self, subparsers, common_parser, mid_common_parser):
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.
- ctime (default on POSIX): Use ctime to detect changes. This is the safest option.
Not supported on Windows (ctime is file creation time there).
- mtime (default on Windows): 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.
Expand Down Expand Up @@ -910,8 +918,9 @@ def build_parser_create(self, subparsers, common_parser, mid_common_parser):
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",
default="mtime" if is_win32 else "ctime",
help="specify how to detect if a file has changed during backup (ctime, mtime, disabled). "
"default: ctime (on Windows: mtime, because ctime is file creation time there).",
)
fs_group.add_argument(
"--read-special",
Expand Down
14 changes: 14 additions & 0 deletions src/borg/testsuite/archiver/create_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,20 @@ def test_file_status_cs_cache_mode(archivers, request):
assert "M input/file1" in output


def test_files_changed_modes(archivers, request):
"""test that all --files-changed modes are accepted and work"""
archiver = request.getfixturevalue(archivers)
create_regular_file(archiver.input_path, "file1", size=10)
cmd(archiver, "repo-create", RK_ENCRYPTION)
# test mtime mode (works on all platforms including Windows)
cmd(archiver, "create", "test_mtime", "input", "--files-changed=mtime")
# test disabled mode
cmd(archiver, "create", "test_disabled", "input", "--files-changed=disabled")
if not is_win32:
# test ctime mode (only meaningful on POSIX, where ctime = inode change time)
cmd(archiver, "create", "test_ctime", "input", "--files-changed=ctime")


def test_file_status_ms_cache_mode(archivers, request):
"""test that a chmod'ed file with no content changes does not get chunked again in mtime,size cache_mode"""
archiver = request.getfixturevalue(archivers)
Expand Down