e256f73439
iroh-blobs 0.35 fs store under <store_dir>/blobs. Add imports files or whole directory trees (as Collections, deterministic order), Materialize exports them with a real FICLONE reflink attempt and streaming-copy fallback, Gc is an explicit mark-and-sweep rooted at the pins. Pins, trusted peers and hash formats persist in meta.json (atomic writes). Store reads run on a LocalPool because iroh-blobs entry readers are not Send. Integration tests drive the real daemon binary: directory round trip byte-identical, gc keeps pinned/drops unpinned, reflink verified on the repo's own filesystem (XFS), plus a 32-case proptest round trip. Dependencies: iroh-blobs =0.35.0 (the store itself; pinned per spec), iroh-io (AsyncSliceReader traits to read store entries), reflink-copy (FICLONE with copy fallback, per spec), proptest (dev-only, round-trip property test). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3.1 KiB
3.1 KiB
Milestone 2 — Store
iroh-blobs 0.35 persistent fs store wired in under <store_dir>/blobs.
Add, Pin/Unpin (local intent only — fetch comes with milestone 3),
Materialize, Status {hash}, List, Gc all work end to end.
Decisions
- Pins and formats live in
meta.json(spec allows JSON for MVP), written atomically via temp file + rename. It records pin policies, trusted peers (used from milestone 4), and the format of hashes we've seen (rawvshash_seq) — the bytes of a hash alone don't say whether it's a directory root, and materialize needs to know. Unknown hashes default toraw. - Directories are iroh Collections (HashSeq with a metadata child),
imported by walking the tree in sorted order for deterministic roots.
File contents only; names are relative paths with
/separators. Symlinked directories are rejected (cycle risk), symlinked files are followed. Collection entry names are sanitized on export (no.., no absolute components) so a hostile collection can't escape the target directory. - Materialize is our own export loop, not iroh's
export(), for one reason: honest reflink reporting. iroh's fs export does reflink internally but doesn't say whether it happened, and its "copy" mode reflinks too, which would make ourmode: copya lie. We reflink (reflink-copycrate →FICLONE) straight from the store'sdata/<hash>.datafile when the blob is complete and file-backed (small blobs are inlined in redb and are always streamed), and fall back to a chunked streaming copy. Verified against real XFS in the test suite:reflink_when_supportedruns underCARGO_TARGET_TMPDIR(same filesystem as the repo) and degrades to verifying the copy fallback on filesystems without reflink support. - Store reads run on a
LocalPool. iroh-blobs entry readers yield non-Sendfutures; the daemon uses the same local-pool pattern iroh-blobs itself uses for its provider and GC tasks, wrapped in oneon_poolhelper. - GC is an explicit one-shot mark and sweep (the spec's
Gc {}op), not iroh's periodicgc_runloop, which only offers a recurring timer. Roots are the pins (HashSeq roots expand to their children), plus any in-flight temp tags and database tags, so a concurrentAddcan't be swept mid-import.AddwithoutPinleaves content GC-able by design — add-then-pin is the intended sequence, and GC never runs implicitly. - Property test (32 cases, 0–64 KiB, spanning the ~16 KiB inline threshold) drives the real daemon binary over the socket, not the library: add → materialize → bit-identical.
Deviations from the spec
- The spec suggested reflink via the
reflink-copycrate; we do use it, but from the store's data file rather than wrapping iroh's export — see above. Never hardlinks, store files stay immutable, and reflink still requires store and destination on one filesystem (documented). - btrfs verification specifically: CI/dev runs use whatever filesystem hosts the repo (XFS here, reflink exercised for real); a loopback btrfs mount would need root, which integration tests shouldn't assume.