Commit graph

3802 commits

Author SHA1 Message Date
Thomas Waldmann
710db85b20
use argon2 from openssl >= 3.2, drop argon2-cffi, fixes #7963
- src/borg/crypto/low_level.pyx: implement `argon2_hash` using OpenSSL's
  `EVP_KDF` API for ARGON2 (requires OpenSSL >= 3.2.0).
- src/borg/crypto/key.py: switch to the native `argon2_hash` implementation,
  removing `argon2-cffi` dependency.
- setup.py: require OpenSSL >= 3.2.0 for the crypto extension to ensure
  ARGON2 KDF support is available.
- pyproject.toml: drop `argon2-cffi` dependency.
- docs: update installation requirements and security documentation to
  reflect the transition to OpenSSL for Argon2.
2026-07-06 20:04:18 +02:00
Mrityunjay Raj
cbc94ae837 repository: raise PackNotFound when get_many hits a missing pack 2026-07-06 08:34:59 +05:30
Thomas Waldmann
230f6ef400
compact: only output statistics when --stats is given 2026-07-05 21:11:35 +02:00
Thomas Waldmann
2b2ceaeb2c
compact: show deduplication and compression factors, fixes #9856 2026-07-05 20:14:51 +02:00
Daniel Rudolf
88e186e053
Improve docs note about symlink behaviour 2026-07-05 18:11:12 +02:00
Daniel Rudolf
a56eb9e1db
Drop non-actionable borgfs warning about symlinks 2026-07-05 18:09:28 +02:00
TW
fc8c8e0fe9
Merge pull request #9811 from ThomasWaldmann/env-hostname-username-master
Replace create --hostname/--username with BORG_HOSTNAME/BORG_USERNAME env vars, fixes #9651
2026-07-05 14:56:52 +02:00
TW
08f91d4d62
Merge pull request #9860 from mvanhorn/docs/fix-contruct-typo
docs: fix typo in serve comment (contruct -> construct)
2026-07-05 14:55:21 +02:00
Charmi Kadi
cef78c99aa
json: support BORG_JSON_INDENT env var for JSON output formatting 2026-07-05 14:45:59 +02:00
Matt Van Horn
f59258e305
docs: fix typo in serve comment (contruct -> construct) 2026-07-05 04:26:48 -07:00
TW
caf1fe55fe
Merge pull request #9836 from mr-raj12/pack-files-get-many-grouping
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
repository: cache whole packs in get_many, replacing adjacent-read grouping
2026-07-05 08:24:49 +02:00
Mrityunjay Raj
cc098ecd5f repository: use try/except/else in get_many, fix misleading comment 2026-07-05 06:41:24 +05:30
TW
1926368ff9
Merge pull request #9857 from borgbackup/ai-findings-autofix/src-borg-testsuite-archiver-extract_cmd_test.py
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
extract tests: add missing comma between argument strings
2026-07-05 00:29:57 +02:00
Mrityunjay Raj
2e2ef6f47a repository: peek the pack cache in get(), switch to peekable helpers LRUCache 2026-07-04 22:14:55 +05:30
Mrityunjay Raj
ec62e5884c repository: use functools.lru_cache for the get_many pack cache 2026-07-04 22:14:55 +05:30
Mrityunjay Raj
f6712364df repository: cache whole packs in get_many, replacing adjacent-read grouping
Keep up to PACK_READER_CACHE_SIZE whole packs in an LRU so repeated and out-of-order ids read from memory instead of refetching from the store.
2026-07-04 22:14:55 +05:30
Mrityunjay Raj
d076788dc5 repository_test: cover get_many pack-grouping (one load per pack, order, missing ids) 2026-07-04 22:14:55 +05:30
Mrityunjay Raj
5d557e1bb6 repository: group adjacent same-pack reads in get_many into one store.load 2026-07-04 22:14:55 +05:30
TW
aba4e2dcf4
Apply suggested fix to src/borg/testsuite/helpers/parseformat_test.py from Copilot Autofix
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-07-04 12:39:43 +02:00
TW
811175f0d1
Apply suggested fix to src/borg/testsuite/archiver/extract_cmd_test.py from Copilot Autofix
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-07-04 12:37:14 +02:00
TW
cf0968ac82
Apply suggested fix to src/borg/testsuite/archiver/extract_cmd_test.py from Copilot Autofix
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-07-04 12:37:14 +02:00
TW
6529346861
Apply suggested fix to src/borg/testsuite/archiver/extract_cmd_test.py from Copilot Autofix
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-07-04 12:37:13 +02:00
TW
14f96bc0b8
Apply suggested fix to src/borg/testsuite/archiver/extract_cmd_test.py from Copilot Autofix
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-07-04 12:37:13 +02:00
TW
196625f7c4
Apply suggested fix to src/borg/testsuite/archiver/extract_cmd_test.py from Copilot Autofix
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-07-04 12:37:12 +02:00
Thomas Waldmann
5aaa2ddc4b
create: don't wrap repository writes in backup_io("read")
process_file() ran process_file_chunks() inside `with backup_io("read")`.

That block was meant to guard reading the *source* file, but the source
reads are already guarded individually by backup_io_iter(). The outer
wrapper additionally caught add_chunk()'s *repository* writes, so a critical
repository IO failure -- e.g. the repo running out of space during a pack
flush -- was wrapped into a per-file BackupOSError. Borg then only warned,
skipped the file, and continued, and create_inner() still committed the
archive via archive.save().

The result: `borg create` on an out-of-space repo printed a normal success
summary ("Error files: 0"), exited 0, and committed an archive that
references chunks which were never durably stored. `borg check` afterwards
reports "Missing file chunk detected" and `borg compact` reports "Repository
has N missing objects!" -- silent, unrestorable-backup data loss.

Drop the outer backup_io("read") wrapper. Source reads stay per-file
warnings (backup_io_iter is unchanged); repository OSErrors are now left
unwrapped and therefore critical, aborting create before archive.save()
runs, exactly as the BackupOSError docstring prescribes.

Reproduced on a space-limited macOS ramdisk (source > free space): before,
create exited 0 with a corrupt archive; after, create fails and commits
nothing, and the repository stays consistent across a create/delete/compact
churn matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 19:48:35 +02:00
TW
98bf11363d
Merge pull request #9852 from mr-raj12/pack-defaults-compact-index-fix
size-bound 50 MB packs by default, fix stale chunk index after compact pack rewrites
2026-07-03 14:47:54 +02:00
Mrityunjay Raj
a8c2c7b1ec PackWriter: move pack size default to constants.py, keep it caller-set 2026-07-03 17:37:02 +05:30
TW
64d89c8d1b
Merge pull request #9851 from ThomasWaldmann/cache-close-resource-fixes
Release cache resources properly in Cache.close()
2026-07-03 14:04:18 +02:00
Mrityunjay Raj
1930c0cead compact: fix chunk index going stale when packs are rewritten, fixes #9850
compact_pack() updated repository.chunks, but compact computes keep/drop from
and persists its own index, which kept pointing at the deleted packs. Also:
tolerate deleting already-gone packs, start rebuilt index entries as unused.
2026-07-03 16:52:32 +05:30
Mrityunjay Raj
dd1bb247a4 PackWriter: size-bound packs of 50 MB by default, fixes #9843
max_count=3 was a testing value. The default is now max_count=None,
max_size=50_000_000; the testsuite sets BORG_PACK_MAX_COUNT=3 instead,
so small test data still produces multiple multi-object packs.
2026-07-03 16:52:07 +05:30
Mrityunjay Raj
09a1627860 repository/compact: use get_cache_dir subdirs arg, pathlib for store cache dir 2026-07-03 15:55:27 +05:30
Thomas Waldmann
a200591bb4
Release files cache dict in Cache.close()
close() persists the files cache but never drops its reference to
self._files, unlike self._chunks which is cleared right after being
saved. For a backup of many files this dict can hold hundreds of MB and
stays alive for as long as the closed cache object does.

Set self._files = None after writing it, mirroring the _chunks handling.
Accessing .files after close() was never supported anyway (close() also
tears down cache_config, which _read_files_cache needs), so this only
frees memory earlier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:59:36 +02:00
Thomas Waldmann
9f5c6c5b73
Invalidate repository chunk index after clearing it in Cache.close()
AdHocWithFilesCache._chunks is the very same ChunkIndex object the
Repository holds (bound via the .chunks property). On close() we persist
it and clear the table in place (_maybe_write_chunks_cache(clear=True)),
then set self._chunks = None. But that only drops the *cache's* reference:
the Repository still points at the now-empty table and
is_chunk_index_loaded stays True.

A later access to repository.chunks in the same process (e.g.
delete_object doing self.chunks.get(id)) would then see a valid-looking
but empty index and conclude every chunk is gone, instead of rebuilding
from the repository. delete_chunkindex_from_repo() already guards its
analogous case with invalidate_chunk_index(); do the same here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:59:07 +02:00
Mrityunjay Raj
db89931645 helpers/fs: let get_*_dir helpers take extra path components
get_config/cache/data/runtime_dir now accept *subdirs and ensure_dir the
full path. get_keys_dir and get_security_dir use this instead of
hand-joining "keys"/"security", and the store cache in repository.py uses
get_cache_dir("storecache") instead of its own makedirs.
2026-07-03 08:01:11 +05:30
TW
13d6d54a13
Merge pull request #9842 from wszqkzqk/allocation-granularity
testsuite: relax sparse file SEEK_DATA check
2026-07-02 21:11:23 +02:00
Zhou Qiankang
3969aeac0b
testsuite: relax sparse file SEEK_DATA check
On filesystems with large allocation units like tmpfs with hugepages,
SEEK_DATA can report an offset before the actual first non-zero byte.

The test already verifies file contents and checks st_blocks to confirm
the file is stored sparsely, so only require that the first data region
is not past the expected content.

Signed-off-by: Zhou Qiankang <wszqkzqk@qq.com>
2026-07-03 00:42:43 +08:00
TW
a14029755c
Merge pull request #9838 from mr-raj12/pack-files-pack-cache
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
repository: env-gated writethrough cache for the packs/ namespace
2026-07-02 17:46:34 +02:00
Mrityunjay Raj
7cc2701cc0 repository: rename BORG_PACK_CACHE to BORG_STORE_CACHE and document the shared pack cache 2026-07-02 17:32:33 +05:30
Thomas Waldmann
831e47d4f1
buzhash64: update type stub for nc_level/normal_size
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 23:25:57 +02:00
Thomas Waldmann
0b7ec02b2f
chunkers: remove dead fill() error-return handling
fill() is declared 'except 0' and always returns 1: errors propagate as
exceptions, so the 'if not self.fill(): return None' branches were
unreachable (and process() returning None would have crashed __next__
anyway). Call fill() plainly instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 23:25:56 +02:00
Thomas Waldmann
f6b79761a2
ChunkerParams: better errors for wrong field counts, stricter compat mode
A wrong field count for a named algo (e.g. fastcdc,19,23,21 or
buzhash,19,23,21 with a forgotten field) used to fall through to the
old-style buzhash compat branch, which then crashed with a ValueError
from int('fastcdc') instead of raising a proper ArgumentTypeError.

- restrict the old-style compat branch to what it is for: four all-numeric
  fields without an algorithm name (19,23,21,4095 still works).
- for buzhash64 and fastcdc, a wrong field count now raises an
  ArgumentTypeError that spells out the expected format, which helps since
  buzhash64 recently gained nc_level and fastcdc has no window field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 23:25:54 +02:00
Thomas Waldmann
3a9ffd4b9a
ChunkerParams: reject params that do not fit the chunker buffer
For buzhash64 and fastcdc, chunker params like chunk_min == chunk_max passed
CLI validation, but then failed with an AssertionError when the chunker was
constructed (buzhash64 needs window_size + 2^chunk_min + 1 <= 2^chunk_max,
fastcdc needs 2^chunk_min + 1 <= 2^chunk_max). Check this at parse time and
raise a proper ArgumentTypeError instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 23:24:27 +02:00
Thomas Waldmann
f2b96e3d15
fastcdc: add FastCDC chunker with a keyed Gear hash
Add a new "fastcdc" content-defined chunker selectable via --chunker-params.
It uses the FastCDC Gear rolling hash (fp = (fp << 1) + Gear[byte]), which is
window-less and cheaper per byte than buzhash's cyclic-polynomial update, so it
chunks noticeably faster (see "borg benchmark cpu" output), while producing
the same chunk-size distribution and deduplication.

The Gear table is keyed: it is derived from the repo id key via CSPRNG (own
"fastcdc" domain), exactly like the buzhash64 table, so chunk cut points stay
unpredictable without the key (anti-fingerprinting). It implements the same
FastCDC techniques as buzhash64 (sub-minimum skipping, normalized chunking with
a required nc_level, min/max clamping); the mask uses the high bits of the hash
(Gear accumulates entropy there).

chunker-params: "fastcdc,chunk_min,chunk_max,chunk_mask,nc_level" - there is no
window field, because Gear is window-less. e.g. fastcdc,19,23,21,2

Also: borg benchmark cpu now measures the fastcdc chunker; tests in
borg.testsuite.chunkers (golden vector, size distribution, keyed gear table,
param parsing, slow fuzz); docs and changelog.

Benchmarks (scripts/chunker_bench.py, buzhash64 vs fastcdc, both nc_level=2,
incompressible data unless noted):

  5 GiB, 2 MiB target (default params):
    buzhash64: CV 0.294, 1011 MB/s
    fastcdc:   CV 0.295, 1313 MB/s   (+30%)

  64 MiB, 64 KiB target:
    buzhash64: CV 0.374, shift-resilience 0.9928,  963 MB/s
    fastcdc:   CV 0.359, shift-resilience 0.9929, 1331 MB/s   (+38%)

  Re-backup of a 2.5 GiB file after scattered single-byte edits (dedup ratio,
  0.5 = v2 fully deduplicated, lower is better):
     64 edits:  buzhash64 0.5237, fastcdc 0.5236
    320 edits:  buzhash64 0.6133, fastcdc 0.6161

  borg benchmark cpu, 1 GB: fastcdc 3.80s, buzhash 4.36s, buzhash64 8.13s,
  fixed 0.56s.

Chunk-size distribution, deduplication and shift-resilience match buzhash64
within noise; fastcdc is consistently faster.

Also: fix bug when computing the mask, one needs to use 1ULL instead of
1, so the shifting computation is done in a uint64, not in a 32bit int.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 23:24:25 +02:00
Thomas Waldmann
dbadb32700
testsuite: drop remote coverage from more transport-agnostic archiver tests
lock_cmds, repo-create, repo-delete, repo-space and tar all only exercise
the generic store list/load/store/delete forwarding that every backend
implements identically:

- lock_cmds: Lock only does list/store/delete on locks/*, and its one
  host-specific check (process_alive across hosts) isn't exercised
  differently by this test infra regardless of "kind" anyway.
- repo-create/repo-delete: store.create()/destroy() are trivial 1:1 RPC
  forwards in the REST backend; still exercised over the wire incidentally
  via every remaining remote test file's setup (repo-create) and via
  checks_test.py (repo-delete), which stays on remote.
- repo-space: grepped borg's own code - it never calls store.quota() at
  all, only generic store_list/store/delete on config/space-reserve.*.
- tar cmds: do_export_tar/do_import_tar route through the same
  Archive/Repository.get()/put() machinery as extract/create, so this
  is redundant with their remote coverage (same category as recreate).

create, extract, check and transfer keep "remote" because they exercise
backends that genuinely branch per transport (partial reads/content-hash
upload headers, server-side hashing, two simultaneous store connections).
Rewrote the kinds-picking policy comment in generate_archiver_tests() to
state that rule directly instead of "moves real archive data".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 19:51:10 +02:00
Thomas Waldmann
9dd9ab6587
testsuite: drop check_cmd's redundant remote coverage, add targeted one
check_cmd_test.py's remaining tests only exercise the generic store
list/load/delete calls also covered by other remote-tested files
(create, extract, ...), so drop remote from its kinds like #9324 did
elsewhere.

Repository.check() does have one transport-specific path though: the
REST backend computes pack hashes server-side (nothing downloaded),
unlike other backends. No existing test actually exercised a plain
--repository-only check catching a corrupted pack via that hash
comparison (existing corruption tests all go through --verify-data,
which decrypts client-side instead). Add that as its own local+remote
test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 19:38:37 +02:00
Mrityunjay Raj
260e21a3c5 repository: add env-gated writethrough cache for the packs/ namespace
BORG_PACK_CACHE enables it; BORG_PACK_MAX_COUNT/SIZE override pack sizing.
2026-07-01 13:28:16 +05:30
Thomas Waldmann
2c26091a1d testsuite: don't run transport-agnostic archiver tests over rest:// (#9324)
The remote (rest://) archiver variant spawns a borgstore-server-rest
subprocess and does synchronous HTTP-over-stdio round trips per object,
so a remote test run costs several times its local twin (e.g.
test_prune_repository_example: ~2.8s local vs ~12.2s remote). Profiling
the suite showed every one of the 40 slowest tests was a remote_archiver
variant.

Most of those tests exercise pure command logic that operates on
metadata or only formats output (prune scheduling, diff/list/info
rendering, rename, delete/undelete, recreate, key handling, debug,
return codes, fuse mount) - this behaves identically regardless of
transport, and the data path over rest is already covered by the
create/extract/check/compact/repo/lock/transfer tests.

Switch those files to kinds="local,binary" and document the policy for
picking kinds in generate_archiver_tests().

Effect (local, -n auto, no coverage): remote_archiver invocations
374 -> 178; full suite 242s -> 154s (-36%), no test failures. The win
on the overloaded macOS CI runners (cold subprocess spawns + coverage)
should be relatively larger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 23:44:22 +02:00
TW
8addc58416
Merge pull request #9823 from ThomasWaldmann/buzhash64-normalized-chunking
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
buzhash64: add FastCDC-style normalized chunking
2026-06-30 11:58:42 +02:00
TW
52fdfc7596
Merge pull request #9828 from mr-raj12/chunkindex-pending-flag
Some checks are pending
Lint / lint (push) Waiting to run
CI / lint (push) Waiting to run
CI / security (push) Waiting to run
CI / asan_ubsan (push) Blocked by required conditions
CI / native_tests (push) Blocked by required conditions
CI / vm_tests (NetBSD, false, netbsd, 10.1) (push) Blocked by required conditions
CI / vm_tests (OmniOS, false, omnios, r151056) (push) Blocked by required conditions
CI / vm_tests (OpenBSD, false, openbsd, 7.8) (push) Blocked by required conditions
CI / vm_tests (borg-freebsd-14-x86_64-gh, FreeBSD, true, freebsd, 14.3) (push) Blocked by required conditions
CI / windows_tests (push) Blocked by required conditions
CodeQL / Analyze (push) Waiting to run
hashindex: track unresolved pack location with F_PENDING flag
2026-06-30 10:03:52 +02:00
Mrityunjay Raj
1206808449 hashindex: make F_PENDING a user flag, drop redundant pending check
F_PENDING moves to the user flag range so it is set/cleared via normal
assignment; flush()'s store-failure path no longer re-checks is_pending.
2026-06-30 08:44:02 +05:30