From abff18447306675ed19ea78f3d20e6e6b769e273 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 17 Nov 2025 22:11:39 +0100 Subject: [PATCH] archive: preserve pre-open original atime, fixes #6194 When creating an archive with --atime on platforms without O_NOATIME, opening a file for reading could update atime before we recorded it (thus we archived the updated atime, not the original one). Capture pre-open path-based-stat atime and use it if it pre-dates the atime we got from the fd AND if it refers to same fs object (avoid race condition). --- src/borg/archive.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/borg/archive.py b/src/borg/archive.py index 11deb8bd8c..4802af0b67 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -1088,7 +1088,7 @@ def __init__(self, *, noatime, noctime, nobirthtime, numeric_ids, noflags, noacl self.noxattrs = noxattrs self.nobirthtime = nobirthtime - def stat_simple_attrs(self, st, path, fd=None): + def stat_simple_attrs(self, st, path, fd=None, *, st_before_open=None): attrs = {} attrs["mode"] = st.st_mode # borg can work with archives only having mtime (very old borg archives do not have @@ -1096,7 +1096,13 @@ def stat_simple_attrs(self, st, path, fd=None): # file content changing - e.g. to get better metadata deduplication. attrs["mtime"] = safe_ns(st.st_mtime_ns) if not self.noatime: - attrs["atime"] = safe_ns(st.st_atime_ns) + # If a pre-open stat result was provided and it refers to same fs object, + # then preserve the original atime. Avoid race conditions. + if st_before_open is not None and st_before_open.st_ino == st.st_ino: + atime = min(st_before_open.st_atime_ns, st.st_atime_ns) + else: + atime = st.st_atime_ns + attrs["atime"] = safe_ns(atime) if not self.noctime: attrs["ctime"] = safe_ns(st.st_ctime_ns) if not self.nobirthtime: @@ -1391,10 +1397,14 @@ def process_file(self, *, path, parent_fd, name, st, cache, flags=flags_normal, ): # no status yet if item is None: return status + # Remember the filename-based stat result we obtained before opening the file. + # On platforms without O_NOATIME, merely opening a file for reading can update + # the atime. We want to store the original atime as seen before opening. + st_before_open = st with OsOpen(path=path, parent_fd=parent_fd, name=name, flags=flags, noatime=True) as fd: with backup_io("fstat"): st = stat_update_check(st, os.fstat(fd)) - item.update(self.metadata_collector.stat_simple_attrs(st, path, fd=fd)) + item.update(self.metadata_collector.stat_simple_attrs(st, path, fd=fd, st_before_open=st_before_open)) item.update(self.metadata_collector.stat_ext_attrs(st, path, fd=fd)) maybe_exclude_by_attr(item) # check early, before processing all the file content is_special_file = is_special(st.st_mode)