create: do not archive an atime we caused ourselves, fixes #6194 - #10111
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #10111 +/- ##
==========================================
+ Coverage 86.94% 87.00% +0.05%
==========================================
Files 101 101
Lines 17983 17993 +10
Branches 2737 2738 +1
==========================================
+ Hits 15636 15654 +18
+ Misses 1639 1635 -4
+ Partials 708 704 -4 ☔ View full report in Codecov by Harness. |
ThomasWaldmann
force-pushed
the
atime-6194
branch
2 times, most recently
from
August 14, 2026 20:35
7e62246 to
9c3a5ef
Compare
…#6194 borg has to open() a fs item before it can fstat() it. On platforms without O_NOATIME support (or when we are not allowed to use it), that open() may already have updated the atime - and then we would archive that atime instead of the one the item had before borg touched it. stat_update_check() now returns a stat result look-alike that has the atime from the stat we did before opening the item, if the atime changed between the two stat calls. If it did not change (e.g. O_NOATIME worked), the fd-based stat result is returned as before.
create_test_files creates input/hardlink as a hard link to input/file1, so both share one inode and anything touching one of them also changes the atime of the other. That is not what test_atime is about, but it made the test fail on cygwin: At extraction time, borg creates the hard link via os.link(). On cygwin, os.link() updates the atime of the file it links to (at least if that file has the x bit set, as input/file1 has here). So it overwrote the atime borg had correctly restored for input/file1 just before and the test then saw "now" instead of the expected atime. Also, while backing up, borg has to open() input/file1 and that updates the atime of the shared inode. When borg then gets to input/hardlink, that already changed atime is what the stat before the open sees, so stat_update_check() can not recognize it as one we caused ourselves and archives it. The test does not look at input/hardlink, so this did not make it fail, but it is another reason why hardlinks and atime testing do not mix well. On cygwin, test_atime failed reliably before this and passes now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ThomasWaldmann
force-pushed
the
atime-6194
branch
from
August 19, 2026 15:50
b7be285 to
00fd667
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
borg has to
open()a fs item before it canfstat()it. On platforms withoutO_NOATIMEsupport (or when we are not allowed to use it), that
open()may already have updated theatime - and then we archive an atime that we caused ourselves, instead of the one the item
had before borg touched it.
stat_update_check()now returns aos.stat_resultlook-alike (StatOrigAtime) that haseverything from the fd-based stat, but the atime from the stat we did before opening the
item - as suggested in the issue. This is only done if the atime actually changed between
the two stat calls, so when
O_NOATIMEworked, the plain stat result is returned as before(no proxy object, no overhead).
Doing this in
stat_update_check()covers all its call sites at once: regular files, fifos,devices and both directory opens.
Note: this only changes what we archive - without
O_NOATIMEwe still can not avoidupdating the atime in the source filesystem.
Tests:
test_atime_open_updates_atimesimulates a platform where theopen()already updates theatime (by reading a byte in a monkeypatched
os_open) and checks that the archived atime isstill the one from before. It fails without the fix.
stat_update_check(): atime fixup, unchanged atime (returns the stat resultas is) and the two race condition checks, which had no test coverage.
2nd commit: test_atime: do not create a hardlink
create_test_filescreatesinput/hardlinkas a hard link toinput/file1, so both share oneinode and anything touching one of them also changes the atime of the other. That is not what
test_atimeis about and it made the test fail on cygwin (part of #7218):os.link(). On cygwin,os.link()updatesthe atime of the file it links to, at least if that file has the x bit set - and
create_test_filesdoeschmod(input/file1, 0o4755). Measured: mode0o644keeps the atime,0o755and0o4755lose it. So the atime borg had correctly restored forinput/file1wasoverwritten by "now" again, and the test saw the wrong value. Tracing the extraction confirms
that
set_times()is called exactly once forinput/file1, with the correct atime, and thatthe very next
os.link()clobbers it.open()input/file1, which updates theatime of the shared inode. When borg then gets to
input/hardlink, that already changed atimeis what the stat before the open sees, so
stat_update_check()can not recognize it as one wecaused ourselves and archives it. The test never looks at
input/hardlink, so this did notmake it fail, but hardlinks and atime testing simply do not mix well.
So the test now just does not create the hardlink.
Note for reviewers: on cygwin it is this 2nd commit that makes
test_atimepass - the 1st commitdoes not change anything there, because cygwin does not show the atime update in the
fstat()right after the
open(), so the fixup never triggers. The 1st commit is for the platforms whereit does.
Testing:
test_atimefailed reliably before, passes now (3 of 3 runs, botharchiverandremote_archiver). Fullextract_cmd_test.py: 59 passed, 56 skipped.