feat(#83): archive VFS backend - read-only FileSystem over one container - #455
Merged
Conversation
Implements the @xff_extras_api seam inside @xff_archive (never the core), so the
whole expression grammar matches an archive's members at virtual paths spelled by
--archive-separator / --archive-prefix.
The decisions, each pinned by a test over a real tar the test writes itself:
- Implicit parent directories are SYNTHESIZED. The fixture deliberately stores
`dir/sub/deep.txt` with no entry for `dir/` or `dir/sub/`, exactly as real tar
streams do; without synthesis a walk finds nothing to descend into.
- Every member carries read_only = true and Source::kArchiveMember - the pair that
makes -delete and the exec family REFUSE members instead of skipping them.
- Remove returns PermissionDenied, never silent success (which would make -delete
look like it worked). Access is never writable and otherwise follows the stored
mode bits. ReadLink resolves a symlink member and refuses a regular one. FsType
is "archive"; IsCaseSensitive is true, since stored names are bytes.
- A path outside the container, a missing member, and "not a directory" are three
distinct errors rather than an empty listing.
- The CONFIGURED separator is used for rendering AND parsing, so a path the
backend printed is one it can stat back (tested with `#`, not just the default).
- ReadContent returns Unimplemented on purpose: the reader lists members but does
not extract data, and an empty string would make -grep / -content silently
match nothing. The next slice adds member extraction.
Two things found while writing it, both recorded rather than smoothed over:
- Inside `namespace xff::archive`, unqualified `archive` resolves to the NAMESPACE,
not libarchive's `struct archive` - the test needs `::archive`.
- Reader asymmetry: ListMembers("") rejects empty input, but ListMembersOfFile on
an empty FILE opens fine and reports zero members. A test pins the current
behaviour and TODO.md flags that the two entry points should probably agree.
Verified: 14 backend tests plus the reader's own pass under --//xff:xff_archive,
//... is 94 green, pre-commit clean.
This was referenced Aug 11, 2026
helly25
enabled auto-merge (squash)
August 11, 2026 00:46
helly25
added a commit
that referenced
this pull request
Aug 11, 2026
Two problems in one section, both mine.
STRUCTURE: my sub-bullets used 4-6 space indents, which prettier reads as
continuation PROSE rather than list items, so ~23 intended bullets were reflowed
into run-on lines ("...split point. - **NEXT SLICE..."). Rewritten with at most
two bullet levels - top level at column 0, children at exactly 2 spaces, long
explanations as continuation paragraphs at 4 - which is a shape prettier keeps.
A mechanical re-split was tried first and DISCARDED: splitting on " - `" cut
sentences in half, because that sequence is also a spaced-hyphen separator in
prose. It scored perfectly on the checks I had written ("0 joined bullets", stable
trunk fmt) while the text had become nonsense - the checks were measuring the
wrong property, so this rewrite was verified by READING it.
CONTENT: pruned what later decisions had superseded, 129 lines out for 95 in:
- the long "member path spelling is TBD / here are the candidate conventions"
debate, now that both flags have shipped - kept as one decision statement;
- the separate "correcting an earlier note" entries about the URI authority rule
and about `/` being lossy - the erroneous claims themselves are gone, so the
corrections have nothing to correct;
- the nine-method "NEXT SLICE, mapped onto the seam" mapping, now that the backend
is built (#455) - replaced by what its tests actually pin;
- duplicate statements of the concatenation / first-separator rules.
Kept whole: the libarchive decision and NOTICE obligations, the ratified
--archive/-z control surface and flavor defaults, dual container identity,
--archive-depth, detection and sniff-gating, read-only member semantics, and the
two genuinely OPEN items (per-format schemes + AUTO separator, phar), each now
naming its task (#177, #176).
Also fixes one more joined bullet outside this section (the span-diagram entry),
same cause. `trunk fmt` is idempotent afterwards and zero joins remain.
helly25
added a commit
that referenced
this pull request
Aug 11, 2026
Conflict: #455 (archive VFS backend) and this branch each appended targets to extra_modules/archive/BUILD.bazel. Both sets are kept. Also does what this PR promised once #457 landed: `archive_reader.cc` drops its local copy of the member-name normalization and uses the shared `NormalizeMemberName` from the member-path library, so the rule exists once for both readers rather than being duplicated on main.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the
@xff_extras_apiVFS seam inside@xff_archive(never the core), spelling member paths throughmember_path_cc, so the whole expression grammar matches an archive's members at virtual paths.Each decision is pinned by a test over a real tar the test writes itself:
dir/sub/deep.txtwith no entry fordir/ordir/sub/, exactly as real tar streams do - without synthesis a walk finds nothing to descend into.read_only+Source::kArchiveMemberon every member - the pair that makes-deleteand the exec family REFUSE members instead of silently skipping them.RemovereturnsPermissionDenied, never silent success (which would make-deletelook like it worked).Accessis never writable and otherwise follows the stored mode bits.ReadLinkresolves a symlink member and refuses a regular one.FsTypeis"archive";IsCaseSensitiveis true, since stored names are bytes.#, so a path the backend printed is one it can stat back.ReadContentreturnsUnimplementedon purpose: the reader lists members but does not extract data, and an empty string would make-grep/-contentsilently match nothing. Member extraction is the next slice.Two things found while writing it, both recorded rather than smoothed over:
namespace xff::archive, unqualifiedarchiveresolves to the namespace, not libarchive'sstruct archive- the test needs::archive.ListMembers("")rejects empty input asInvalidArgument, butListMembersOfFileon an empty FILE opens fine and reports zero members (libarchive hits EOF immediately). A test pins today's behaviour; the two entry points should probably agree, which is a reader-level call.Verified: 14 backend tests plus the reader's own pass under
--//xff:xff_archive;//...is 94 green;pre-commit run --all-filesclean.Left for follow-ups: engine-side mounting when
--archiveselects a container (where--archive-depthand sniff-gating land), andReadMemberOfFile.NOTE: not armed - it queues behind #454 and #450 per your ordering. Its TODO.md note is intentionally absent here; it goes in the TODO.md repair PR, since that file needs a structural fix first (my nested bullets were being reflowed by prettier).