Repository navigation
nvx-rs: serve shorter images, and restore clones in RAM without a scratch file - #925
Merged
Pedro Henrique Penna (ppenna) merged 2 commits intoOct 29, 2026
Conversation
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The stacked restore changes cross device and guest boundaries, with the final rebased head and shorter-image/absent-scratch VM scenarios still requiring validation on KVM, MSHV, and WHP.
No open issues 🎉
What changed in this PR
This PR implements #848 in the NVX host library, using OpenVMM’s zero-fill support so one template can serve shorter images and restore RAM-backed clones without creating scratch files.
Changes:
- Accept images up to the template’s capacity while preserving block-size checks and exact scratch-image geometry requirements.
- Replace RAM clones’ sparse scratch files with
--restore-absent-scratch, updating the public API and store cleanup. - Expand unit and VM scenarios for shorter images, oversized-image refusals, and file-free scratch devices; update documentation.
| File | Description |
|---|---|
tests/runtime-adapter/tests/vm_clone.rs |
Tests shorter images, presented capacities, and absent scratch backing. |
tests/runtime-adapter/src/clone.rs |
Documents the relaxed image-length contract. |
tests/runtime-adapter/src/bin/runtime-adapter-host.rs |
Updates command documentation for shorter images and RAM uppers. |
tests/runtime-adapter/README.md |
Documents restore behavior and revised VM scenarios. |
nvx-rs/src/host/template/store/leases/tests.rs |
Removes obsolete scratch-file cleanup expectations. |
nvx-rs/src/host/template/store/leases.rs |
Updates lease-release documentation. |
nvx-rs/src/host/template/store.rs |
Documents restores with no scratch backing file. |
nvx-rs/src/host/template/clone/tests/launch.rs |
Checks absent-scratch arguments and scratch-image launches. |
nvx-rs/src/host/template/clone/tests.rs |
Tests capacity admission, geometry refusals, and file-free RAM plans. |
nvx-rs/src/host/template/clone/launch.rs |
Adds the absent-scratch restore flag for RAM uppers. |
nvx-rs/src/host/template/clone.rs |
Relaxes image-length validation and removes RAM scratch-file creation. |
nvx-rs/src/host/store/tests.rs |
Updates clone-file cleanup tests. |
nvx-rs/src/host/store/mod.rs |
Removes the clone scratch filename, path API, and cleanup entry. |
nvx-rs/src/host/store/lifecycle/tests.rs |
Updates removal tests for the reduced store file set. |
nvx-rs/src/host/sandbox/distro.rs |
Documents image capacity and block-size requirements. |
nvx-rs/README.md |
Updates the host-library overview for both restore changes. |
Review effort: Balanced
Enrique Saurez (esaurez)
changed the base branch from
esaurez/847-pr2-start-run-suite-20261024
to
esaurez/848-base-snapshot-20261028
October 28, 2026 17:31
Enrique Saurez (esaurez)
marked this pull request as ready for review
October 28, 2026 17:32
6 tasks
Enrique Saurez (esaurez)
force-pushed
the
esaurez/848-shorter-images-absent-scratch-20261026
branch
from
October 29, 2026 09:01
f663a1f to
1c77b97
Compare
Enrique Saurez (esaurez)
changed the base branch from
esaurez/848-base-snapshot-20261028
to
esaurez/847-pr2-start-run-suite-20261024
October 29, 2026 09:01
This was referenced Oct 29, 2026
Enrique Saurez (esaurez)
force-pushed
the
esaurez/847-pr2-start-run-suite-20261024
branch
2 times, most recently
from
October 29, 2026 15:34
6863d47 to
f43428e
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/848-shorter-images-absent-scratch-20261026
branch
from
October 29, 2026 15:39
1c77b97 to
ce7932d
Compare
Pedro Henrique Penna (ppenna)
force-pushed
the
esaurez/847-pr2-start-run-suite-20261024
branch
from
October 29, 2026 19:49
f43428e to
41a4181
Compare
…atch file A template's restore presents the capacity that its placeholder fixed for the `distro` block device, and OpenVMM reads zeros past the end of a shorter image, so a clone's image need no longer be exactly the placeholder's length. `ClonePlan` admits an image of either format no longer than the placeholder, with its logical and physical block sizes, and refuses a longer one, or one of other block sizes, as before any VMM starts. On Linux a file's physical block size is the I/O block size of its filesystem, so the image must lie on a filesystem of the placeholder's. A scratch image still presents the template's scratch exactly. The restore of a clone in RAM declares the template's scratch absent, with `--restore-absent-scratch`, so that OpenVMM attaches it with no file: its reads return zeros and its writes fail, and the clone's guest never uses it. The host no longer creates the sparse `clone-scratch.raw` in the sandbox's directory, nor does a launch check it: `CloneUpper::Tmpfs` holds nothing, `ClonePlan::scratch_path` returns `None` for a clone in RAM, and the sandbox store loses `clone_scratch_path`. The store still counts `CLONE_SCRATCH_NAME` among its files, so that a release and a removal remove one that an earlier version left for a clone in RAM. The guest needs no change: it rereads a GPT disk's partition table before it assembles the root, `nvx::gpt` requires only that a disk's backup header lie inside the device, and a clone in RAM looks for no scratch device. The runtime adapter's GPT template now has a placeholder 16 MiB longer than the platform's image, so the clones of either format that it serves are all shorter than the capacity: they find their image's disk at the capacity and their scratch device at the template's scratch length, and read each to its end; nothing in the sandbox of a clone in RAM backs its scratch device, and on Linux its VMM declares the scratch absent. A start refuses an EROFS image and a GPT disk longer than the placeholder, in place of the shorter image that the exact length refused. Part of #848. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 053388a9-90ee-4632-8cd4-711de08d267e
The template module's overview still said that a clone's plan refuses an image of another length than the placeholder, `SuppliedLength` that a supplied file has another length than the template's device, and `IncompatibleHost` that the sandbox's filesystem may present another geometry than the template's scratch, which only the removed sparse scratch file could. Describe the capacity rule instead: an image longer than the placeholder, or of other block sizes, and a scratch image of another length than the template's scratch. The README's launch also names `--restore-absent-scratch` for a clone in RAM. Part of #848. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 053388a9-90ee-4632-8cd4-711de08d267e
Enrique Saurez (esaurez)
force-pushed
the
esaurez/848-shorter-images-absent-scratch-20261026
branch
from
October 29, 2026 19:59
ce7932d to
99fe9a6
Compare
Base automatically changed from
esaurez/847-pr2-start-run-suite-20261024
to
dev
October 29, 2026 20:29
Pedro Henrique Penna (ppenna)
deleted the
esaurez/848-shorter-images-absent-scratch-20261026
branch
October 29, 2026 20:36
This was referenced Oct 29, 2026
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.
Part of #848.
Stacked on #963.
Chain: #951 → #954 → #957 → #959 → #960 → #955 → #958 → #962 → #963 → this PR, on
dev; #879, #881, #885, #886, #877, #932, #884, #887, #933, #880, #936, #937, #939, #940, #941, #888, #949, and #950 have merged.What and why
Until now, a clone's image had to be exactly as long as the template's placeholder, and each clone in RAM got a sparse scratch file of the template's scratch length, which it never used. OpenVMM now presents the
distrocapacity that aplatformsnapshot recorded over a shorter image, reading zeros past its end, and lets a restore declare a fresh scratch absent (microsoft/openvmm#4749, indev's OpenVMM pin since #901). So one template now serves images of any length up to its capacity, in either format, and clones in RAM restore with no scratch file at all.Images shorter than a template's capacity
ClonePlan::newadmits an image no longer than the template's placeholder (TemplateKey::distro), with its logical and physical block sizes, and refuses, before any VMM starts:supplied-geometry, whatever its length;supplied-length: "the image PATH is N bytes long, longer than the template's placeholder, whose M bytes are the capacity of every clone's image".A file whose length is not a nonzero multiple of 512 bytes stays refused, by
supplied-geometry. On Linux a file's physical block size is its filesystem's I/O block size (st_blksize), so the image must lie on a filesystem of the placeholder's; on Windows it is always 4096 bytes. A scratch image keeps the exact rule: the template's scratch geometry, its length included. Nothing reads, pads, or resizes the image.Clones in RAM without a scratch file
Every restore attaches the template's
scratchblock device, which the template's guest negotiated. For a clone in RAM, the restore now passes--restore-absent-scratchand noscratchblock device, so OpenVMM attaches the scratch at the recorded geometry with no file: reads return zeros, writes fail, and the clone's guest, whose upper is a tmpfs, never uses it. A clone over a scratch image still passes the image as thescratchblock device. So the host creates nothing for a clone in RAM:CloneUpper::Tmpfsis now a unit variant, andClonePlan::scratch_path()returnsOption<&Path>,Nonein RAM.SandboxStore::clone_scratch_path()is gone, and nothing creates the clone's scratch file,clone-scratch.raw, any more. The store still counts its name,nvx::host::store::CLONE_SCRATCH_NAME, among its files,FILE_NAMES, and among the files of a clone that a release removes, so that a release and a removal remove one that an earlier version left for a clone in RAM, which a removal would otherwise report as foreign. A plan still refuses it as an image, as it refuses every file of the sandbox's store.--restore-absent-scratch, a clone in RAM has no scratch file, so the check guards nothing.A template's capture keeps its own dormant scratch file, since a cold boot cannot declare it absent.
The guest
No change, as #848 expects. For a GPT disk, the guest asks the kernel to reread its partition table before it assembles the root (#845), and
nvx::gptrequires only that the primary header's alternate LBA lie inside the device (read_headerinnvx-rs/src/gpt/mod.rs), which holds for a disk shorter than the device; it never reads the backup header. What a clone's activation record expects of a GPT disk, its GUID and a bound on its first partition, concerns the disk's primary header and first partition, which a shorter disk keeps. A clone in RAM looks for no scratch device, and its upper, a tmpfs that the assembly mounts empty, always passes the check that a clone's upper is fresh, which still refuses a used upper on a scratch image.Tests
nvx-rs unit tests:
supplied-length, and ragged lengths bysupplied-geometry. A template whose placeholder presented other physical blocks serves no image of this filesystem, whatever its length.supplied-geometry.--restore-absent-scratchand noscratchblock device; a clone over a scratch image has no such flag.clone-scratch.rawthat an earlier version left is refused as an image. The store's tests of a removal and of a release's retried removal stage it among a clone's files, and remove it.a_clone_launches_only_as_the_launch_that_its_durable_binding_reservesno longer launches a stale plan in RAM whose scratch file a later, failed plan removed: a clone in RAM has no scratch file, so that section guarded nothing, and the test's other sections stay.a_release_makes_the_removal_of_its_binding_durable_before_it_removes_its_recordnow stages the record of the clone's failure in place of that file, as a file of the clone that a release removes only once it ended the lock's reservation.Runtime adapter VM scenarios, in
vm_clone, on every backend:one_generation_serves_clones_of_either_format_over_either_upper: the GPT template's placeholder is now 16 MiB longer than the platform's EROFS image, so its four clones at once, the EROFS image over a scratch image and in RAM, a GPT disk of the EROFS image's length in RAM, and one as short as its layout allows over a scratch image, whose first partition meets the clone's bound exactly, are all shorter than the capacity. Each finds its image's disk at the capacity and its scratch device at the template's scratch length, and reads both to their ends. No file in the sandbox of a clone in RAM is as long as the template's scratch, and on Linux its VMM's arguments hold--restore-absent-scratchand noscratchblock device.a_start_refuses_what_the_template_cannot_serve_before_any_vmm_starts: an EROFS image and a GPT disk 4 KiB longer than the placeholder are refused, bysupplied-length, in place of the image 4 KiB shorter that the exact rule refused.The other scenarios' clones in RAM restore the same way, with no scratch file.
An image on a filesystem of another block size has no VM scenario: no CI host offers the suite a second filesystem of another I/O block size without a privileged mount, and Windows presents 4096-byte physical blocks for every file. The nvx-rs tests above cover that refusal.
Guide (#591's numbering)
This PR leaves
doc/designunchanged; #591 takes:platformcapture'sdistropresents the placeholder's length, the capacity of every image that a restore supplies, apart from the image's own length; and every block of a snapshot-capable machine is backed by a file, except a fresh scratch that a restore declares absent.Validation
At this PR's head 99fe9a6, the same two commits as the earlier head ce7932d, restacked onto #963's head 41a4181, which is #963 rebased onto
devat f8469b8. ce7932d held the same two commits as the earlier head 1c77b97; the rest of this section describes 1c77b97, on #963's earlier head 918e6fc.At 1c77b97, on #963's head 918e6fc:
nvx.py verify; the steps of check-nvx-rs that run there (fmt; clippy with all features, of the library, and without features; tests with all and default features, and of the library without features; the check of artifact bundling; docs with-D warnings; MSRV 1.89); the OpenVMM vectors test; the runtime adapter's protocol-only and host-only configurations;test-runtime-adapter --check-only; ruff and the Markdown link check; and validate-nvx's Python suites, 1425 tests, run with Git for Windows'bash, and its adversarial, host-connect, and--helpchecks: all pass.check-guest-binaries,build-guest-binariesandtest-guest-binaries, and the runtime adapter's guest-only configuration; as root, the 13 tests that need it, inguest::clone,guest::root, andguest::mappings; Specula's tests; and validate-nvx's Python suites, but for the 24 tests that list the files of a Git checkout, which the tree there, an export, was not, and which pass on Windows: all pass.nvx.py test-runtime-adapter, on WHP, one test at a time, and on Linux with MSHV, as root: all pass on each. OpenVMM is the pin, 832d5bb4, from the development release of 2d14547, with that release's kernel and Ubuntu EROFS layer; the initramfs and the guest agent are built from this head.nvxcrate's packaging and publication, which run fordevalone.ClonePlan::launch's documentation that still described the check of a clone's scratch file in RAM, which this head removes; its other rounds found nothing.