Commit Graph
10949 Commits
Author SHA1 Message Date
Thomas Waldmann 116435fa46 update CHANGES 2.0.0b23 2026-08-23 14:52:16 +02:00
Thomas Waldmann 950e966f96 build_usage build_man 2026-08-23 14:52:14 +02:00
Thomas Waldmann 3aab66de62 update .gitignore 2026-08-23 14:52:12 +02:00
TW ab32412679 Merge pull request #10077 from ThomasWaldmann/clock-skew-9870
locking: deal with clock skew
2026-08-23 02:36:10 +02:00
Thomas WaldmannandClaude Fable 5 29df5b6bd0 locking: never warn about clock skew against our own lock objects, see #9870
During a refresh the listing contains our old and our new lock object; a
storage clock step between their writes makes the old one look skewed against
our new anchor, which produced a misleading "clock skew with <our own host>"
warning right after the (correct) storage clock step warning. Skip all lock
objects carrying our own id in the skew check, not just the current one.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 01:13:15 +02:00
Thomas WaldmannandClaude Fable 5 d537868289 docs: storelocking module docstring explains the locking, see #9870
Explain lock objects, acquiring, staleness (both clock domains, store "now",
advisory-only store timestamps, deferral, mtime-less backends), the clock skew
warning and refreshing where the code lives; the internals docs keep a summary
and point there for the details.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:32:40 +02:00
Thomas WaldmannandClaude Fable 5 7441b40924 locking: warn if the storage's clock jumps between two of our lock writes, see #9870
When the mtime of a freshly created lock object is harvested, compare it with
what the previous anchor extrapolates for that instant: if our own wall and
monotonic clocks agree about the elapsed time (no suspend, no local clock step)
but the storage's mtimes do not, the storage's clock jumped. Warn once.
Diagnostic only - a backward step larger than the stale timeout can defeat the
store-domain cross-check in _is_stale_lock (documented residual risk); this at
least names the cause when a vanished lock gets investigated.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:02:30 +02:00
Thomas WaldmannandClaude Fable 5 5e6a90adbb locking: refresh the lock before judging other locks with a fresh store time, see #9870
refresh() listed first and created the new lock object afterwards, so its stale
sweep judged other locks with the store-clock anchor of the previous refresh, up
to refresh_td (15min) old. A storage clock that stepped back within that window
by more than the stale timeout could make the store-domain cross-check wrongly
confirm a skewed peer's healthy lock as stale - the hazard the cross-check exists
to prevent. Now refresh() creates the new lock object first and lists afterwards:
the listing harvests a seconds-old anchor before the sweep runs, shrinking the
exposure from 15 minutes to the write/list latency. Our old lock object is exempt
from the sweep during the refresh (it may legitimately be older than the stale
timeout, e.g. after a suspend, see #9883).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-23 00:01:34 +02:00
Thomas WaldmannandClaude Fable 5 c54046da6d locking tests: reuse _create_lock and the fslocking free_pid fixture, see #9870
write_raw_lock duplicated _create_lock's wire format (field layout, timestamp
format, sha256 key, store path), so a future format change (e.g. AEAD lock
objects) would have made the skew tests silently keep writing the old format.
_create_lock gained an explicit content timestamp parameter instead; the
helper keeps only the store-side mtime override. The free_pid fixture was a
verbatim copy of the one in fslocking_test.py - import it like
platform_test.py does (incl. the same per-file F811 ignore).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:46:02 +02:00
Thomas WaldmannandClaude Fable 5 e1ff4ef0ac locking: keep the store-clock anchor across lock deletion, see #9870
The anchor is a store-clock calibration, not a property of the lock object,
so deleting our transient lock does not invalidate it. Keeping it lets an
acquire that is blocked by a healthy-but-skewed lock veto the kill on the
first listing of every retry (2 store round-trips) instead of re-running the
defer/create/veto/delete cycle (7 round-trips plus lock churn) each time.
Safe: store times stay veto-only, so a kept anchor can never cause a kill.
To bound the mis-veto window of an anchor frozen by a suspend, _store_now
now refuses anchors older than the stale timeout, with age measured by our
wall clock (which, unlike time.monotonic(), keeps counting while suspended);
this also defuses the leftover-anchor hazard of the break_lock and
refresh-abort paths. Also document the accepted residual risk of a store
clock stepping backwards by more than the stale timeout.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:46:00 +02:00
Thomas WaldmannandClaude Fable 5 ed0c037a79 locking: keep the store-clock anchor in one atomically replaced tuple, see #9870
borg with-lock runs its LockRefresher thread without serialization against
the main thread, and terminate()'s bounded join can leave a wedged refresh()
running while the main thread releases. The anchor state (store key, content
timestamp, store mtime, monotonic) was spread over separate attributes, so
such an interleaving could raise KeyError (my_lock_key rebound between the
harvest's membership test and subscript), raise TypeError (fields nulled
between _store_now's guard and use), or silently pair one lock object's
mtime with another's monotonic/content timestamp, skewing _store_now and
_mutual_skew by up to the refresh interval. A LockAnchor namedtuple replaced
as a whole plus single-read locals makes every observed state internally
consistent; the harvest only updates an anchor still describing the same
lock object.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:45:58 +02:00
Thomas WaldmannandClaude Fable 5 de0e498440 locking: warn about clock skew only when there actually is skew, see #9870
The stale-veto path warned unconditionally, but a veto is not by itself
evidence of skew: after a suspend, our store "now" estimate lags (monotonic
clock stood still), so a genuinely stale foreign lock of a perfectly synced
client gets vetoed and produced a bogus "clock skew of ~0s" warning. The
per-listing skew check in _get_locks already covers the vetoed lock with a
proper magnitude gate, so the veto-path warning (and with it the optional-skew
calling convention of _warn_clock_skew) can just go away.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:45:56 +02:00
Thomas WaldmannandClaude Fable 5 bf9f50da23 locking: check for clock skew on every lock listing, see #9870
The skew check only ran on acquire success, but the listing that satisfies
an exclusive acquire can only contain our own lock, so exclusive commands
could never warn about a moderately skewed peer. Checking each listing in
_get_locks also warns when acquire times out on a skewed peer's lock, and
makes the last_seen_locks replay machinery unnecessary.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:45:54 +02:00
Thomas WaldmannandClaude Fable 5 6dd71b17f1 docs: internals: describe clock-skew-immune lock staleness, see #9870
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:45:51 +02:00
Thomas WaldmannandClaude Fable 5 c309a95429 locking: make stale-lock detection immune to client clock skew, fixes #9870
Lock staleness was judged by comparing a lock's content timestamp
(stamped by its writer's clock) against the reader's local clock.
A client whose clock runs >30 min ahead would thus kill another
client's healthy lock and could then e.g. run compact deleting
chunks the victim still references - a finished archive referencing
deleted chunks.

Fix: a lock may only be considered stale by age if it looks stale in
BOTH clock domains:

- writer/local clock domain: local now vs. lock content timestamp
  (the pre-existing rule), AND
- store clock domain: store "now" vs. the lock object's store-side
  mtime (new, using borgstore's ItemInfo.mtime).

Store "now" is derived from our own lock object's mtime plus elapsed
monotonic time, so all store-domain comparisons happen within the
store's own clock domain: neither the clients' nor the store's
absolute clock error matters. A client without an own lock object
defers the kill until acquire() has created one (a listing made
right afterwards confirms or vetoes the candidates).

Store-side mtimes are advisory only: they can veto a kill, but they
can never cause one on their own, so a hostile or broken store gains
no new capabilities (it can already delete locks or serve fabricated
fresh ones - lock objects are unauthenticated). For the same reason,
the process_alive() check now runs *first* and is never vetoed by
store timestamps: if the lock owner is a process on our own machine
and it is dead, we know that locally, and a store serving bogus,
always-fresh mtimes must not be able to keep an abandoned lock alive
forever and block us.

Backends without store-side mtimes (e.g. rclone: mtime == 0) keep
the previous behavior.

Additionally, since each lock object carries two timestamps of the
same write instant (content time = writer clock, mtime = store
clock), the writers' per-store clock offsets are comparable: on
acquire, borg now warns (once) if another active client's clock is
skewed by more than MAX_MUTUAL_CLOCK_SKEW (5 min) against ours -
diagnosis only, never an abort, so spoofed store timestamps cannot
block backups.

The manifest-timestamp behavior is intentionally unchanged.

ItemInfo.mtime requires borgstore 0.6.1, so the borgstore
requirement is bumped accordingly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:45:49 +02:00
TW fc74d5f643 Merge pull request #10180 from ThomasWaldmann/update-changes-master
update CHANGES
2026-08-22 22:40:24 +02:00
Thomas Waldmann f0dba9a406 update CHANGES 2026-08-22 22:35:42 +02:00
TW 2b415eedc5 Merge pull request #10179 from ThomasWaldmann/kernel-defaults-10160
pick chunker scan kernel defaults per platform
2026-08-22 22:06:08 +02:00
Thomas Waldmann 92d4cdd737 pick chunker scan kernel defaults per platform, fixes #10160
Until now every chunker ran its simplest implementation unless a
BORG_*_KERNEL env var named another one, because which kernel is fastest
is not predictable from the instruction set. It is predictable per
platform though, now that #10160 has benchmarks from a range of CPUs, so
choose accordingly instead of leaving speed on the table by default:

- fastcdc / buzhash64: neon on aarch64, scalar (the plain sequential
  loop, which the compiler folds into a single instruction there) on
  x86-64, blockwise elsewhere.
- toeplitz-aes / rabin-aes / goldilocks-aes: wider is simply faster
  here, so vaes, else aes-ni on x86-64, aes-arm64 on aarch64, and evp
  where there is no AES hardware path.

Both fall back where a build or a CPU does not have the preferred
kernel. An env var still means exactly what it says: a kernel that
cannot run here is an error, never a silent fallback.

On an Apple M3 this makes the default chunkers 1.6x (goldilocks-aes) to
2.1x (buzhash64) faster.
2026-08-22 20:48:58 +02:00
TW 5ef9b8f642 Merge pull request #10176 from ThomasWaldmann/file-digests
create, import-tar: --digests computes digests over the full file content, see #4699
2026-08-22 15:29:01 +02:00
TW b16b52315b Merge pull request #10104 from ThomasWaldmann/flags-1345
file flags: fix generic set_flags, keep unprivileged flags on EPERM, warn on failure
2026-08-22 15:06:38 +02:00
TW abd3eeb385 Merge pull request #10178 from ThomasWaldmann/remove-py312-canary
CI: remove the py312-on-ubuntu-26.04 canary, it fired
2026-08-22 14:54:16 +02:00
Thomas WaldmannandClaude Opus 5 4e4859b34a CI: remove the py312-on-ubuntu-26.04 canary, it fired
The inverted canary added in #10097 watched for actions/setup-python
gaining Python 3.12 support on the ubuntu-26.04 image. It went red on
2026-08-15 and 2026-08-16: the "Try to set up Python 3.12" step now
succeeds, which was the whole point of the probe. Its job is done, so
remove it.

Older Pythons are therefore no longer categorically unavailable on
ubuntu-26.04, so the matrix can gain multi-version coverage there once
we move to those runners.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-22 14:52:51 +02:00
TW b179ec98cf Merge pull request #10177 from ThomasWaldmann/win32-timestamp-tests
win32: no ctime, archive creation time as birthtime; run timestamp tests on win32, fixes #8730
2026-08-22 14:43:15 +02:00
Thomas Waldmann b69023ce45 create, import-tar: --digests computes digests over the full file content, see #4699
With "--digests ALGOS", borg computes hash digests over the full content of each
regular file and stores them in the item's new "digests" dict (hash algorithm name
-> digest), e.g. --digests=blake3 gives {"blake3": ...} (32 bytes). ALGOS is a
comma-separated list of hash algorithm names - the same algorithms that are available
as "borg list" format keys - or "none". Default: none, digests are opt-in.

Unlike the chunk ids, such a digest depends neither on the chunker nor on borg's key,
so it is the same digest that e.g. "b3sum" computes for that file. "borg list --format
{blake3}" uses a stored digest if the item has one, instead of reading the file content
from the repository.

The content is hashed while it is read and processed anyway: the first chunk inline,
from the second (big enough) chunk on by a background thread. Our hash implementations
release the GIL, so the hashing can overlap with chunking, id hashing, compression and
encryption instead of adding to their runtime. ContentDigester.update() takes ownership
of the chunk data and releases it after hashing, so nothing is copied for this and the
memory is still freed timely.

Measured (12 cores, medians of 5 runs, relative to --digests=none, which is as fast as
borg without this):

                                    1GiB new  1GiB known  20000 small files
    --digests=blake3                   free       free        +6..12%
    --digests=blake3,sha256            free       +10%          +16%
    --digests=blake3,sha256,sha512      +7%       +60%          +24%

One background thread hashes all requested algorithms one after the other, so their
combined throughput is the harmonic sum, while many small files are hashed inline by
the main thread. Hashing is free exactly while it is faster than the rest of the
pipeline - a single algorithm is, several may not be. That is why digests are opt-in.

Digests are not computed if borg does not read the full content: an unchanged file gets
them from the files cache and additional hard links from the first hard link, but
"borg create --reuse-from" items do not have digests. Files that already are in the
files cache without digests get them when they change (and thus are read again), so
changing --digests only affects the files that borg reads.
2026-08-22 14:17:51 +02:00
TW 7bcc496464 Merge pull request #9976 from ThomasWaldmann/try-pypy
pypy support: is_pypy flag and test expectations, see #1755
2026-08-22 14:11:03 +02:00
Thomas WaldmannandClaude Opus 5 16a1eb7c39 chunkers/reader: limit the read size in the no-readv fallback, see #1755
Without os.readv (win32) we read via os.read and copy into the scan buffer.
os.read allocates a buffer of the requested size, no matter how few bytes it
then returns, so requesting the whole free scan buffer (up to 8MiB for the
default max chunk size) is expensive for small files: one allocation of that
size per read call, several calls per file.

Measured with readv disabled on CPython, backing up 20000 small files (82.6MiB
of data): 468GiB requested in 60002 os.read calls, 1.42s spent in os.read and
4.04s total. Capping the request at 256KiB - same call count, same data - gives
0.06s in os.read and 2.42s total, close to the 2.36s of the readv path.
Chunking a 2GiB file is unaffected (the big reads there return what they ask
for). pypy showed the same effect much more strongly until it gained os.readv.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-22 13:33:42 +02:00
Thomas WaldmannandClaude Opus 5 af07f9c983 pypy support: add is_pypy, adjust test expectations, see #1755
borg works on pypy3.11: tested with a py3.11 nightly build (523222a2c5cc,
PyPy 8.0.0-alpha0), where the full test suite passes (2588 passed, 1 xfailed)
and CPython is unaffected.

The pypy bugs found while doing this are all fixed in pypy now, so no
workarounds are left. What remains are test expectations for behaviour that
is not a pypy bug:

- platformflags: add is_pypy.
- item_test: xfail test_unknown_property - setting undeclared attributes on
  cdef class instances is not blocked under cpyext.
- msgpack_test: pypy only has the pure-python msgpack, so expect it to be slow.
- lock_cmds_test: ignore stderr warnings unrelated to lock handling (the
  pure-python msgpack one and cython's collection_type one), and use
  sys.executable instead of "python3" (robustness, not pypy-specific).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-22 13:33:32 +02:00
Thomas WaldmannandClaude Opus 5 8d1a36bfd1 testsuite: run the generic timestamp tests on win32, see #8730
is_utime_fully_supported() and is_birthtime_fully_supported() probed via
os.utime, which can neither set the timestamps of a symlink itself nor the
birthtime on win32 - so they returned False there and all the tests they
guard were skipped. Probe via platform.set_times() instead, which does the
right thing on all platforms.

Also: a FILETIME has a resolution of 100ns, use that for st_mtime_ns_round
on win32 (rather than the 1s fallback).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-22 13:32:16 +02:00
Thomas WaldmannandClaude Opus 5 c99d03a209 win32: archive the file creation time as birthtime, not as ctime, see #8730
On Windows, stat().st_ctime is the file *creation* time and not the POSIX
"metadata change time", and Python 3.12 deprecated it for that reason.
Thus, do not archive it as item.ctime any more (there simply is no ctime
on Windows), but archive it as item.birthtime - which is also what
Python >= 3.12 gives us via st_birthtime_ns.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-22 13:32:00 +02:00
Thomas Waldmann 21c7849e84 docs: describe cross-platform behavior of file flags (bsdflags), #1345 2026-08-21 22:21:16 +02:00
Thomas Waldmann 338151b981 extract: warn if file flags cannot be set, #1345
Previously, a failure to set the archived flags was silently ignored.
Now it emits a warning and sets the warning exit code, like xattr
errors already do.
2026-08-21 22:21:14 +02:00
Thomas Waldmann 04a9cded21 set_flags: use masked get/set everywhere, keep unprivileged flags on EPERM
- platform.base.set_flags (used on NetBSD and generic POSIX) now also does
  the masked read-modify-write cycle instead of setting the archived value
  as-is, see #9039. The flag masks are defined once in platform.base and
  shared by the freebsd/darwin implementations.
- if setting the flags fails with EPERM (e.g. super-user-only SF_* flags
  while not running as root, or immutable/append-only on Linux without
  CAP_LINUX_IMMUTABLE), retry influencing only the unprivileged flags, so
  e.g. the nodump flag still gets restored, see #1345.
- macOS: tolerate EOPNOTSUPP like the other platforms already do.
- linux: remove the unused FS_COMPR_FL declaration.
2026-08-21 22:21:07 +02:00
TW 95fe6ccf7c Merge pull request #10172 from ThomasWaldmann/reject-unc-paths-10164
Location: reject UNC paths everywhere early with a hint, fixes #10164
2026-08-21 21:04:17 +02:00
TW 7a0a8d019a Merge pull request #10159 from ThomasWaldmann/mount-fuse-errors-8657
mount: tell why FUSE support is unavailable
2026-08-21 20:05:31 +02:00
Thomas Waldmann 0c34d2444e Location: reject UNC paths everywhere early with a hint, fixes #10164
On Windows, \\server\share\repo was accepted by the Location parser and then
failed deep inside borgstore with "Invalid or unsupported Backend Storage URL:
file://server/share/repo" (Path.as_uri() makes the server the URL authority).

UNC paths are not supported (see #3141), so reject every local path that
starts with "//" (after resolving it) in Location.parse(), on all platforms,
with a message that names the workaround: mount the share (Windows: map it to
a drive letter with "net use") and use the mounted path.

This also removes the "//" exclusion from local_re (bare //server/share/path
now gets the same message instead of a bare "Invalid location format") and
drops the old test_smb, which asserted that file:////server/share/path is
accepted on non-Windows platforms - that only ever meant something on Cygwin
and was never tested there.

Also document this in the repository URLs docs section.
2026-08-21 19:52:31 +02:00
TW 1ca2396376 Merge pull request #10173 from ThomasWaldmann/mount-dir-format-9991
mount/webdav: BORG_MOUNT_ARCHIVE_DIR_FORMAT names the archive directories
2026-08-21 19:16:31 +02:00
Thomas Waldmann 4af2719ba2 mount/webdav: BORG_MOUNT_ARCHIVE_DIR_FORMAT names the archive directories, fixes #9991
When showing a whole repository, the archive directories were named like
the archives, with -{id:.8} appended to names that are not unique (the
archives of a series). Now the BORG_MOUNT_ARCHIVE_DIR_FORMAT environment
variable gives the format of these names, using the placeholders of
borg repo-list --format (default: {name}, the same naming as before).

Names that are still not unique (or empty, "." or "..") get -{id:.8}
appended, "/" and NUL are replaced, an invalid format string is an error.
As this is implemented in the shared ArchiveVFS, it applies to borg mount
and borg webdav alike.
2026-08-21 17:31:42 +02:00
TW 06b14041b1 Merge pull request #10137 from ThomasWaldmann/map-4363
create: --map and --reuse-from for efficient block device snapshot backups
2026-08-21 15:32:43 +02:00
Thomas WaldmannandClaude Fable 5 2927349210 mount: tell why FUSE support is unavailable, see #8657
- fuse_impl: record the import error of each FUSE implementation that
  failed to load. also catch non-ImportError failures: mfusepy raises
  OSError at import time if libfuse is missing (and AttributeError /
  NotImplementedError in other cases), which previously crashed borg
  with a traceback instead of a clean error.
- borg mount: show the recorded errors, so users see the actual cause
  (e.g. a missing shared library) instead of just "no FUSE support".
- sysinfo: report mfusepy as the active FUSE implementation (it showed
  "fuse: None" although mounting worked) and include the recorded
  import errors if no implementation loaded, so debug output pasted
  into issues shows why.
- docs: note that third-party binaries might lack FUSE support.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-21 12:40:38 +02:00
Thomas Waldmann db5e706c01 tests: rename block to block_size in the --map tests, see #4363
block held a block size, not a block.
2026-08-21 12:38:06 +02:00
Thomas Waldmann d83f509fa6 create: move the --map usage notes to the end of the section, see #4363
The "--map requires exactly one input path" and "the reference archive must
contain exactly one file item" paragraphs interrupted the description of the
map file format. Move both to the end of the Input maps section and rebuild
the generated usage docs / man page.
2026-08-21 12:38:03 +02:00
Thomas WaldmannandClaude Fable 5 14d05b60b7 scripts: add lvm-thin-map.py to generate input maps from thin metadata, see #4363
Converts thin_dump XML (full mode: allocation map of one thin LV) and
thin_delta XML (delta mode: differences between two thin snapshots)
into the borg create --map format. The docstring documents the full
workflow including the reserve/release_metadata_snap steps and the
snapshot discipline needed for --reuse-from.

Also add a docs section pointing from the --read-special LVM example
to the input map based approach.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-21 12:37:58 +02:00
Thomas WaldmannandClaude Fable 5 6c395aca67 create: add --reuse-from to reuse a reference archive's chunks, see #4363
For input map ranges marked "same", reuse the chunks of the reference
archive's item instead of reading the input. Reference chunks that only
partially overlap "same" ranges are re-read completely, so the result
is correct with any chunker; with the fixed chunker, read windows align
with the reference chunk grid. Reference chunks missing from the repo
are read again (like the files cache does on a lost chunk).

Together with --map, this implements efficient incremental backups of
block device snapshots: only changed ranges need to be read, e.g. as
reported by thin_delta for LVM thin volume snapshots.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-21 12:37:55 +02:00
Thomas WaldmannandClaude Fable 5 74622607be create: add --map to give an input map, see #4363
An input map describes the content ranges of the single input file:
data ranges are read and stored, zero ranges are stored as holes
without reading them. Primary use case: backing up snapshots of large
(esp. thin-provisioned) block devices, where the storage layer knows
which ranges are in use.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-21 12:37:50 +02:00
TW d6e64fd98b Merge pull request #10169 from ThomasWaldmann/pin-argon2-bindings-25
deps: pin argon2-cffi-bindings to 25.1.0
2026-08-21 12:28:30 +02:00
Thomas Waldmann 7babd39e01 deps: pin argon2-cffi-bindings to 25.1.0, see #9602
argon2-cffi-bindings 26.1.0 switched its build backend from setuptools to
scikit-build-core, which asks for cmake as a dynamic build requirement when
it does not find one on PATH. There are no cmake wheels for the BSDs and
OmniOS, so pip builds cmake from its sdist there - that takes about half an
hour and then fails on NetBSD, OpenBSD and OmniOS, so those VM test jobs
never get as far as running any test. On OmniOS the bootstrapped cmake has
no TLS support and can not even download the CMake tarball. FreeBSD gets
through it, but pays ~44 minutes for the privilege.

25.1.0 builds with setuptools + cffi and needs no cmake: it installs from
its sdist in seconds. It also still ships abi3 wheels for the platforms that
have them.

This is meant to be temporary: argon2 is moving to OpenSSL, which removes
the argon2-cffi dependency (and this pin) completely, see #9602.
2026-08-21 11:48:39 +02:00
TW bb9eb8de51 Merge pull request #10166 from ThomasWaldmann/reader-fmap-partial
chunkers: do not re-read a file map that does not reach EOF
2026-08-20 23:16:01 +02:00
Thomas Waldmann 7bc4647952 chunkers: do not re-read a file map that does not reach EOF, see #4363
FileReader used "blockify_gen is None" for both "not started yet" and
"exhausted": _fill_buffer() cleared it on StopIteration, and read() /
readinto() take a cleared generator as their cue to build a fresh
FileFMAPReader.blockify(), which replays the fmap from its start. dread()
reads sequentially and ignores its offset argument, so such a replay
continued at the current file position and appended duplicate data. An
fmap that does not start at 0 seeks back to its start on every replay and
never terminates at all.

This stayed invisible as long as every fmap reached EOF, because the
replay then hits a 0-byte read immediately: that is true for sparsemap()
and for "create --map", which always maps the whole file. "create --map
--reuse-from" is the first caller that reads only parts of a file.

Track exhaustion in its own flag and never restart the generator.

blockify() also has to stop treating a short read as EOF, or dropping the
restart truncates the input instead: a file object may return less than
requested without being at EOF, see buzhash_self_test test_small_reads,
which passed only because of the restart.
2026-08-20 22:11:57 +02:00
TW b85ca3e8b9 Merge pull request #10162 from ThomasWaldmann/sqlite-chunker-5877
docs: FAQ about chunker params for SQLite databases
2026-08-20 17:50:16 +02:00