Skip to content

host: read strict snapshots through the platform layer (SL6.0a) - #1479

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
feature/nvx-refactorfrom
agents/slim-sl6-0a-strict-reads
Nov 11, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
feature/nvx-refactorfrom
agents/slim-sl6-0a-strict-reads

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Part of #1120 (SL6.0a, "Strict snapshot reads survive the stores' removal"). Parent #1087, umbrella #1076.

What and why

SnapshotManifest::read and Snapshot::open read a snapshot's manifest and its key.bin with open_regular and read_at_most from host::store::regular. So the strict snapshot reads depended on file helpers and a refusal classification that the store owns, and #1121 deletes the store. This PR moves those helpers to the host's platform layer and switches the retained snapshot readers to it:

  • open_regular, read_at_most, is_refused, the private Refused type and their regular-file support (not_regular, refused_open, regular) move from host::store::regular to host::platform, which stays. The moved code is unchanged, except that refused_open and regular become pub(crate) for the store's open_regular_in.
  • regular needs is_reparse_point, which moves down from host::filesystem::host_path to host::platform and stays re-exported where it was: filesystem builds on platform, and the children of host depend on one another without cycles (tests/host_modules.rs).
  • The store keeps open_regular_in, which now builds its link and non-regular-file refusals with the moved refused_open and regular. It keeps open_regular, read_at_most and is_refused as aliases for its remaining callers until nvx-rs slimming 4.2.2: the old clone path, template store and sandbox store are gone #1121 deletes them.
  • SnapshotManifest::read, read_attachments, which shares its read, and Snapshot::open's read of key.bin use host::platform directly.

The maximum-length error, the refusal of links, directories and other files that are not regular files, and the refusal classification are unchanged. The public API doesn't change: every moved item is crate-private, and scripts/public_api.py finds both snapshots current. BOOT_CONTRACT_VERSION and CONTROL_CONTRACT_REVISION don't change. The issue's Scope names the manifest reader at its old path, host/template/manifest.rs, which SL2.12a moved to host/snapshot/manifest.rs.

Scope extension: Snapshot::open

The Scope names only SnapshotManifest::read, which was the only strict snapshot reader when #1120 was written. SL2.12b (#1440) then added Snapshot::open, which reads key.bin through the same store helpers. The slice's Objective says that retained readers use their new owner, so, as the orchestrator decided, this PR switches Snapshot::open as well: one import and two calls in host/snapshot.rs, which keeps the PR within its limits.

Not in this PR: callers to move before SL6.1

These retained callers still use the store's helpers and must move before SL6.1 (#1121) deletes host/store/. Besides the aliases, they use the store's private-file helpers in host::store::files (write_private, sync_dir, create_new), which this slice doesn't move, so they belong to a later slice:

  • finish_capture, in host/snapshot/capture.rs: open_regular and read_at_most through the aliases, and write_private and sync_dir;
  • the CLI's sandbox store, in host/cli/store/files.rs: open_regular and read_at_most through the aliases, and host::store::files;
  • the CLI's capture, in host/cli/snapshots/capture.rs: create_new;
  • the CLI's snapshot store, in host/cli/snapshots.rs: sync_dir.

The store and clone callers keep the aliases until their deletion, as the Scope says.

History: the first implementation

Enrique Saurez (@esaurez)'s first implementation is esaurez/slim-4.2.1-sl6.0a-20261108 (6231b876, checkpoint on #1120): one commit that moved the helpers and switched the manifest reader. This PR replays it as its only commit, with Enrique Saurez (@esaurez) as its author, and corrects it:

  • its tests, which reached the store's aliases from host::platform's tests, became two platform tests without store references and a store test of open_regular_in;
  • the docs of host::platform, is_refused and host::store::regular describe the new owner;
  • Snapshot::open switches too, as above.

Size

Measured against feature/nvx-refactor ab7c9b5f, as against d40228be and a53a2626 before it, since the rebases left the patch unchanged: 83 core lines (estimate 150, stop 300) in 5 counted files: host/platform/mod.rs, host/store/regular.rs, host/snapshot/manifest.rs, host/snapshot.rs and host/filesystem/host_path.rs. The two test files don't count.

Tests of the area

Every existing test is kept, unchanged. These read through the moved helpers:

  • host::snapshot::manifest::tests: read_reads_the_manifest_of_a_snapshot, read_refuses_a_missing_manifest_as_not_found, read_refuses_more_than_a_manifest_may_hold, read_refuses_a_directory, read_refuses_a_link, read_refuses_a_junction (Windows), read_refuses_a_fifo_without_waiting_for_a_writer (Unix), the_manifests_that_openvmm_writes_are_read and the the_attachments_* tests now read through host::platform.
  • host::snapshot::tests: snapshot_open_uses_the_strict_manifest_and_bounded_key_decoders, which still refuses an oversized key, and the other snapshot_open_* tests now open keys through host::platform; so does host::snapshot::key::tests::snapshot_open_refuses_other_key_contracts_and_filesystem_tags.
  • The clone path's host::template::clone::tests::keys_that_their_read_refuses_for_what_they_are_are_failures_of_the_template still classifies a link, a directory and an oversized key as refusals through the aliases, and files_that_their_paths_do_not_name_as_checked_are_refused and only_a_path_that_no_longer_names_its_supplied_file_is_the_request_s keep the refusal's message.
  • The template store's links_are_refused, records_that_are_not_regular_files_fail_at_once, payloads_are_checked_as_they_are_read and links_and_unsafe_ownership_are_refused_before_the_vm_starts keep their messages.

Added:

  • host::platform::tests::a_bounded_read_refuses_a_regular_file_that_holds_more_than_its_bound;
  • host::platform::tests::an_open_of_a_regular_file_refuses_a_link_and_a_directory (Unix);
  • host::store::tests::entries_that_are_links_or_not_regular_files_are_refused_as_the_platform_refuses_them (Linux and Windows): open_regular_in's refusals are ones that host::platform recognizes.

Validation

feature/nvx-refactor has no scripts/local_gates.py, so dev's copy selected the gates and ran them with this branch's own action commands and pinned toolchain (1.95.0; MSRV 1.89), on Windows locally and on Linux on nvx-kvm-test (KVM, virtual machine, Azure Linux 3). The full matrix ran on 67213558, based on 5947e4e8. Snapshot::open's switch came next, and the branch was then rebased onto 8b775211 (SL2.16), onto d40228be (after SL4.3 and SL6.0b), onto a53a2626 (after SL3.5a, #1466) and onto ab7c9b5f (after SL3.9, #1469), without conflicts; git range-diff shows the same patch each time, and rebase_check.py verify passed after each of the last two rebases. CI passed on 597569d5 (run 42995101590: 37 passed, 9 skipped), where cargo doc --all-features also built without warnings and a code review of the whole diff found no issue.

On the head, bb218975, based on ab7c9b5f, these ran again on Windows:

  • cargo fmt --all --check, and the five Clippy runs of the Lint step, among them cargo clippy -p nvx --lib --no-default-features -- -D warnings: passed;
  • the library tests that host::snapshot, host::platform, host::store and manifest select, with the clone path's refusal test: 217 passed with --all-features and 211 with the default features;
  • tests/host_modules.rs (22 tests), check_test_layout.py, check_test_changes.py, and public_api.py, whose Linux and Windows snapshots are unchanged: passed.

On f5436a77, the previous head, the same library tests with host::vm, host::restore and host::cli::store added passed 285 with the default features. With --all-features, 333 passed and host::restore_driver::tests::ready_has_sixty_seconds_and_start_thirty_from_ready_for_each_pending_accept_and_read (SL4.3), which the host::restore filter also selects, failed once: in that parallel run, its activate-read case expired after 59.99996 of its 60 seconds. It passed 2 runs in 2 alone, and failed the same way, after 59.9994 seconds, in the nvx-rs / windows-latest job of feature/nvx-refactor's own push CI of a53a2626 (run 42994995631). host::restore_driver uses none of the moved helpers.

CI passed on f5436a77 (run 43050110604: 34 passed, 9 skipped, Required status check among the passed). The push-triggered Copilot Setup Steps of f5436a77 (run 43050098502), which isn't part of CI, failed in its first two attempts: its native-builds and archives steps couldn't resolve azure.archive.ubuntu.com and cdimage.ubuntu.com (Temporary failure in name resolution), as every setup run that ran from 15:36 to 16:40 UTC did, dev's among them. Once other branches' setup runs passed again, its third attempt, a rerun of the failed job, passed.

CI passed on bb218975 (run 43068922503: 34 passed, 9 skipped, Required status check among the passed), and so did its Copilot Setup Steps (run 43068910907).

Gate, on 67213558 Windows Linux
cargo fmt --all --check Passed Passed
Clippy with -D warnings, the five feature sets of the Lint step Passed Passed
cargo test --locked --all-features Failed: pre-existing, below Failed: pre-existing guest::* failures, below
cargo test --locked Failed: pre-existing flake, below Passed
cargo build and cargo test --lib, --no-default-features Passed Passed
Artifact bundling Passed Passed
cargo doc --all-features with -D warnings Passed Passed
scripts/public_api.py (both snapshots) Passed, unchanged (on 597569d5) Passed, unchanged
MSRV: cargo +1.89 check Passed Passed
macOS check - Passed
nvx.py check-guest-binaries - Failed: pre-existing guest::* failures, below
nvx.py build-guest-binaries, test-guest-binaries - Passed
Runtime adapter: protocol-only and host-only, and guest-only on Linux Passed Passed
check_test_layout.py, check_markdown_links.py, check_test_changes.py Passed Passed
CLI checks (validate-nvx) Failed: pre-existing script-test errors, below Not run: the host has no pip

Every failure also fails at the base on the same host:

  • Windows. Under load from parallel builds, three timing-sensitive tests fail: host::cli::snapshots::capture::tests::a_capture_ack_and_successful_output_without_timely_exit_are_stopped_but_never_finished (a 1-second capture deadline: 3 runs in 3 at the head, 2 in 3 at the base), tests/template_capture.rs's vms_that_never_end_are_torn_down_at_the_deadline (a 2-second deadline: 5 in 5 at both, run alternately), and, once in the default-feature run, host::platform::windows::tests::children_inherit_only_their_standard_handles, which passes 3 runs in 3 alone at both. With --no-fail-fast, every other unit test, integration test and doctest passes. The CLI checks' scripts/test_microvm_tests.py has 16 errors in its scenario-script tests at the head and 18 at the base; no script changes.
  • Linux. With --all-features, the same 28 tests of guest::{entry, exec, init, managed_agent} fail at both revisions, and the guest musl suite of check-guest-binaries fails the same 26 at both. These tests reach none of the changed modules, which the guest build doesn't compile.

No microVM scenario ran: the change moves crate-private helpers without changing their behavior. CI runs the selected hypervisor suites on KVM, MSHV and WHP.

Changes to existing tests

python3 scripts/check_test_changes.py --base origin/feature/nvx-refactor lists no change to an existing test, and the 3 added tests above, so this PR has no tests-modified label.

The openvmm gitlink doesn't move.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are reported for the relocation of strict snapshot-read helpers into the platform layer.

No open issues 🎉

What changed in this PR

Moves strict snapshot file reads into host::platform, preparing for removal of the legacy stores under #1120. The refactor preserves size limits, regular-file checks, refusal classification, and the public API.

Changes:

  • Relocates bounded-read helpers and reparse-point detection to the platform layer, retaining compatibility aliases.
  • Switches snapshot manifest and key readers to use the platform helpers directly.
  • Adds tests for bounded reads, non-regular-file rejection, and shared refusal classification.
File Description
nvx-rs/​src/​host/​store/​tests.rs Tests that store-entry refusals retain platform classification.
nvx-rs/​src/​host/​store/​regular.rs Retains aliases and delegates regular-file checks to platform helpers.
nvx-rs/​src/​host/​snapshot/​manifest.rs Reads manifests through the platform layer.
nvx-rs/​src/​host/​snapshot.rs Reads snapshot keys through the platform layer.
nvx-rs/​src/​host/​platform/​tests.rs Adds bounded-read and link/directory refusal tests.
nvx-rs/​src/​host/​platform/​mod.rs Owns strict file-read helpers and refusal classification.
nvx-rs/​src/​host/​filesystem/​host_path.rs Re-exports reparse-point detection from its new platform owner.

Review effort: Balanced

SnapshotManifest::read and Snapshot::open read a snapshot's manifest and
key with open_regular and read_at_most from host::store::regular, so the
strict snapshot reads depended on file helpers and a refusal
classification that the store owns, and later pull requests delete the
store.

Move open_regular, read_at_most, is_refused, the private Refused type
and their regular-file support (not_regular, refused_open and regular)
to host::platform, which stays and already holds the host's file
helpers. regular() needs is_reparse_point, which moves down from
host::filesystem::host_path to host::platform and stays re-exported
where it was, since filesystem builds on platform and the children of
host depend on one another without cycles.

The store keeps open_regular_in, which builds its link and
non-regular-file refusals with the moved refused_open and regular, and
keeps open_regular, read_at_most and is_refused as aliases for its
remaining callers until #1121 deletes them. SnapshotManifest::read, and
read_attachments, which shares its read, and Snapshot::open's read of
key.bin use host::platform directly. The maximum-length error, the
refusal of links, directories and other files that are not regular
files, and the refusal classification stay as they were.

finish_capture, the CLI's sandbox store and the CLI's capture still
read through the aliases, since they also use the store's private-file
helpers, which this change does not move.

The manifest reader's tests, which refuse a missing manifest, one over
MAX_MANIFEST_LEN, a directory, a link, a junction and a FIFO, the
snapshot tests, and the clone path's
keys_that_their_read_refuses_for_what_they_are_are_failures_of_the_template
pass unchanged. New platform tests pin the moved reads' bound, messages
and refusal classification, and a store test checks that
open_regular_in refuses a link and a directory with refusals that
host::platform recognizes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ppenna
Pedro Henrique Penna (ppenna) merged commit 66fd431 into feature/nvx-refactor Nov 11, 2026
48 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the agents/slim-sl6-0a-strict-reads branch November 11, 2026 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants