16 Commits
Author SHA1 Message Date
Thomas Waldmann 97f03619c7 build_usage build_man 2026-09-27 23:02:35 +02:00
Thomas Waldmann e97cba32cc build_man ; build_usage 2026-08-29 17:42:35 +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
Thomas WaldmannandClaude Fable 5 9547a53d37 export-tar: support sparse files, fixes #2562
Add a --sparse option to export-tar (BORG and PAX tar formats only):
files whose content contains runs of all-zero chunks are written as
sparse tar members in GNU sparse format 1.0 (what GNU tar creates in
POSIX mode), storing only a hole map and the non-hole data.

The hole map is computed from the item's chunk list metadata alone,
before any data is fetched: an all-zero chunk is detected by comparing
its id against the (memoized) id of an all-zero chunk of the same size,
reusing the zeros shortcut heuristic of DownloadPipeline.fetch_many
(now extracted into a shared zero_chunk_flags function). Additionally,
the memo is warmed up for power-of-two chunk sizes and each file's last
chunk size, so single (non-repeated) zero chunks - like the only chunk
of an all-zero file or a trailing hole's tail - get detected, too.
The zero chunks of a sparse member are never fetched from the
repository at all.

Notes:
- python's tarfile module reads GNU sparse members (import-tar has
  always consumed such tarballs correctly), but cannot write them, so
  the writing side crafts the pax records and the map itself and
  streams map + data segments via tarfile.addfile().
- holes are shrunk to whole 512-byte tar blocks within a zero run
  (GNU tar's sparse reader processes data segments block-wise and
  desyncs on unaligned segments); the zero bytes shaved off the hole
  edges are emitted as literal zeros, still without fetching the
  all-zero chunks. Zero runs shorter than a block stay dense, as do
  members where the map would cost more than the holes save.
- pax record order matters for in-order readers: path (mangled
  GNUSparseFile.0/<name>, like GNU tar) comes first, GNU.sparse.name /
  GNU.sparse.realsize come last, so the real name/size win.
- no pax size record must be written for a sparse member (readers
  would desync recalculating the next-header offset from it), so the
  stored size always lives in the ustar size field: standard octal
  while it fits (< 8 GiB), else base-256 encoded (the GNU/star
  big-number encoding, which GNU tar, libarchive and python's tarfile
  all read in any tar format) - so stored data and logical file size
  (GNU.sparse.realsize) are effectively unlimited.
- without --sparse, the output is unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-18 13:36:10 +02:00
Thomas WaldmannandClaude Fable 5 eae8f6da38 export-tar/import-tar: zstd (de)compression in-process, see #10067
With --tar-filter=auto, .tar.zst/.tar.zstd/.tzst tarballs (.tzst newly
recognized) are now (de)compressed in-process using the zstd module
(Python >= 3.14 stdlib / backports.zstd) instead of piping through an
external zstd process. borg requires the zstd module anyway, libzstd
multithreading (BORG_ZSTD_MT_WORKERS) runs outside the GIL, and no
external tool needs to be installed.

Compression uses level 3 (the zstd tool's default). An explicit
--tar-filter program is still run as an external filter, also for zstd.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 13:40:20 +02:00
Thomas Waldmann 61b1b83059 make fastcdc the default chunker, fixes #9957
fastcdc is ~1.3x faster than buzhash/buzhash64 at the same deduplication
and (with normalized chunking) a tighter chunk size distribution.

It is also the better choice security-wise: its Gear table is derived
from secret key material, while the "buzhash" chunker only XORs a 32bit
seed into its table - so chunk cut points are much harder to predict
without the key (resistance against chunk-size fingerprinting attacks).

Both places using a chunker are switched:

- CHUNKER_PARAMS (file content data): fastcdc,19,23,21,2
- ITEMS_CHUNKER_PARAMS (item metadata stream): fastcdc,15,19,17,2

The metadata stream chunker was still using the 32bit seeded buzhash, so
this also closes that gap.

Also rename CHUNKER64_PARAMS -> BUZHASH64_PARAMS and add BUZHASH_PARAMS,
so the per-algorithm defaults are named consistently and CHUNKER_PARAMS
unambiguously means "the default chunker".

This is intentionally done during the beta phase so it gets plenty of
practical testing before borg2 is released for production.
2026-08-02 00:40:58 +02:00
Thomas Waldmann 4550a49b0f build_usage / build_man 2025-04-21 21:08:42 +02:00
Thomas Waldmann b50ed04ffc build_usage / build_man 2024-09-08 12:02:17 +02:00
Thomas Waldmann edfca29b97 build_usage / build_man 2024-07-19 20:40:15 +02:00
Thomas Waldmann 70602bda52 build_usage 2023-06-11 23:09:20 +02:00
Thomas Waldmann 52b9f8e7e9 build_usage 2023-02-26 21:32:42 +01:00
Thomas Waldmann 4d2eb06ed3 build_usage 2022-11-26 22:23:17 +01:00
Thomas Waldmann 18ca2ab3ce build_usage build_man 2022-09-10 18:15:45 +02:00
Thomas Waldmann 51cf85e627 build_usage / build_man / doc updates 2022-06-25 21:58:19 +02:00
Thomas Waldmann d7fe23c515 python setup.py build_usage 2022-04-14 21:25:12 +02:00
Thomas Waldmann 572e996d9a build_usage 2022-01-23 15:52:21 +01:00