Skip to content

fix files cache rebuild from an archive without ctime - #10299

Merged
ThomasWaldmann merged 1 commit into
borgbackup:masterfrom
ThomasWaldmann:fix-files-cache-noctime
Sep 1, 2026
Merged

fix files cache rebuild from an archive without ctime#10299
ThomasWaldmann merged 1 commit into
borgbackup:masterfrom
ThomasWaldmann:fix-files-cache-noctime

Conversation

@ThomasWaldmann

Copy link
Copy Markdown
Member

Split out of #10292, where the Windows CI run turned this up. It is an independent pre-existing bug.

The bug

_build_files_cache() reads item.ctime unconditionally, but an archive item does not necessarily
have a ctime. stat_attrs() stores it only conditionally:

# borg can work with archives only having mtime (very old borg archives do not have
# atime/ctime). [...]
attrs["mtime"] = safe_ns(st.st_mtime_ns)
...
if not self.noctime and not is_win32:
    # win32: st_ctime is the file creation time, that is archived as birthtime, see #8730.
    attrs["ctime"] = safe_ns(st.st_ctime_ns)

So rebuilding the files cache from a previous archive crashes:

src/borg/cache.py:347: in _build_files_cache
    ctime_ns = item.ctime
AttributeError: attribute ctime not found. Did you mean: 'atime'?

This happens always on Windows (ctime is never archived there) and with --noctime on every
platform. It is reachable whenever the local files cache is missing while a previous archive exists
— a fresh machine, or a lost/cleared cache directory — i.e. exactly the situation the repo-side
rebuild exists for.

Reproducer on Linux/macOS:

borg create --noctime home input
rm <cache dir>/<repo id>/files.*
borg create --noctime home input   # AttributeError: attribute ctime not found

It went unnoticed because nothing exercised the repo-side rebuild path on Windows CI until #10292
added a test that removes the local files cache.

The fix

Both timestamps are read with item.get(). A timestamp the archive does not have is cached as 0,
which cannot compare equal to the timestamp seen in the file system — so if that timestamp is part
of the files cache mode, the file is considered changed and gets chunked again, which is the safe
outcome. The tracking of the newest ctime/mtime skips missing values instead of comparing them; the
two copy-pasted blocks for the two timestamps collapsed into a loop.

Tests

test_files_cache_rebuild_without_ctime uses --noctime rather than relying on Windows CI, so it
covers this on every platform. Verified it fails without the fix on both archiver and
remote_archiver.

It comes with a _remove_files_cache() test helper (removes the local files cache of a series to
force the rebuild), which #10292 then also uses.

Full test suite: 2928 passed, 981 skipped. ruff check clean.

_build_files_cache read item.ctime unconditionally, but an archive item does not
necessarily have a ctime:

- borg create --noctime omits it,
- on Windows it is never archived (st_ctime is the file creation time there and
  gets archived as birthtime, see borgbackup#8730),
- very old archives only have mtime, as the comment in stat_attrs() says.

Rebuilding the files cache from such an archive crashed:

    AttributeError: attribute ctime not found

That is reachable whenever the local files cache is missing while a previous
archive exists, i.e. after the cache directory was lost, or on a fresh machine -
always on Windows, and with --noctime everywhere.

Both timestamps are read with item.get() now. A timestamp the archive does not
have is cached as 0, which can not compare equal to the timestamp seen in the
file system, so the file is considered changed and gets chunked again if that
timestamp is part of the files cache mode. The tracking of the newest
ctime/mtime skips missing values instead of comparing them.
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.61%. Comparing base (7004e87) to head (0a31ddc).
⚠️ Report is 9 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/borg/cache.py 70.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #10299      +/-   ##
==========================================
+ Coverage   87.55%   87.61%   +0.06%     
==========================================
  Files         103      103              
  Lines       18676    18674       -2     
  Branches     2872     2872              
==========================================
+ Hits        16352    16362      +10     
+ Misses       1622     1611      -11     
+ Partials      702      701       -1     

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

@ThomasWaldmann
ThomasWaldmann merged commit a2111e3 into borgbackup:master Sep 1, 2026
24 of 25 checks passed
@ThomasWaldmann
ThomasWaldmann deleted the fix-files-cache-noctime branch September 1, 2026 07:49
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