Skip to content

create: do not archive an atime we caused ourselves, fixes #6194 (1.4-maint) - #10577

Merged
ThomasWaldmann merged 2 commits into
borgbackup:1.4-maintfrom
ThomasWaldmann:atime-open-6194-14
Nov 3, 2026
Merged

ThomasWaldmann merged 2 commits into
borgbackup:1.4-maintfrom
ThomasWaldmann:atime-open-6194-14

Conversation

@ThomasWaldmann

Copy link
Copy Markdown
Member

Backport of #10111 to 1.4-maint, fixes #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 archive an atime that we caused ourselves, instead of the one the item
had before borg touched it.

stat_update_check() now returns a os.stat_result look-alike (StatOrigAtime) that has
everything from the fd-based stat, but the atime from the stat done before opening the item.
This is only done if the atime actually changed between the two stat calls; otherwise the
plain stat result is returned as before.

2nd commit: test_atime no longer creates the hardlink (it shares the inode with input/file1,
which made the test fail on cygwin).

Differences from the master PR (1.4 test layout):

  • the archiver tests are ArchiverTestCase methods in testsuite/archiver.py;
    test_atime_open_updates_atime is skipped in ArchiverTestCaseBinary (it patches objects).
  • unit tests for stat_update_check() are in testsuite/archive.py, checking st_birthtime
    instead of st_birthtime_ns (not present on older Pythons).

Testing (macOS, py3.14): the new tests fail without the fix and pass with it. Full suite:
1468 passed, 382 skipped, 2 failed (test_unix_socket: "AF_UNIX path too long", caused by the
long tmp path of the local test setup, unrelated).

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.19%. Comparing base (69552c0) to head (a70ccd1).
⚠️ Report is 7 commits behind head on 1.4-maint.

Additional details and impacted files
@@              Coverage Diff              @@
##           1.4-maint   #10577      +/-   ##
=============================================
+ Coverage      82.07%   82.19%   +0.12%     
=============================================
  Files             38       38              
  Lines          11485    11524      +39     
  Branches        1810     1817       +7     
=============================================
+ Hits            9426     9472      +46     
+ Misses          1470     1467       -3     
+ Partials         589      585       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

ThomasWaldmann and others added 2 commits November 3, 2026 21:50
…#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.

(cherry picked from commit 51cc27e)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

(cherry picked from commit 00fd667)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ThomasWaldmann
ThomasWaldmann merged commit bc0dd23 into borgbackup:1.4-maint Nov 3, 2026
15 checks passed
@ThomasWaldmann
ThomasWaldmann deleted the atime-open-6194-14 branch November 3, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant