Repository navigation
nvx-rs: plan the clones of templates, and check their block geometry - #888
Open
Enrique Saurez (esaurez) wants to merge 5 commits into
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Scratch creation can replace a checked distro image, and compatibility validation omits the runtime’s boot and control contracts.
2 open issues
What changed in this PR
Adds metadata-only clone planning to NVX’s template infrastructure, implementing the first stage of #844 without launching a VM.
Changes:
- Introduces clone requests and plans that validate leases, images, uppers, memory targets, and template compatibility.
- Exposes checked block geometry and manages sparse scratch files for RAM-backed uppers.
- Adds validation and cleanup tests, plus API and lifecycle documentation.
| File | Description |
|---|---|
| nvx-rs/src/host/template/store/leases/tests.rs | Tests durable scratch and binding removal. |
| nvx-rs/src/host/template/store/leases.rs | Removes clone scratch files during lease release. |
| nvx-rs/src/host/template/store.rs | Adds a configurable publication helper for tests. |
| nvx-rs/src/host/template/mod.rs | Exports and documents clone planning. |
| nvx-rs/src/host/template/clone/tests.rs | Tests validation, scratch claims, and cleanup. |
| nvx-rs/src/host/template/clone.rs | Implements clone planning and input checks. |
| nvx-rs/src/host/supervisor/mod.rs | Documents planning before reserved launches. |
| nvx-rs/src/host/store/mod.rs | Registers clone scratch files and exposes reservations internally. |
| nvx-rs/src/host/store/lifecycle/tests.rs | Tests scratch cleanup during sandbox removal. |
| nvx-rs/src/host/sandbox/scratch/tests.rs | Tests checked scratch geometry. |
| nvx-rs/src/host/sandbox/scratch.rs | Exposes checked scratch geometry. |
| nvx-rs/src/host/sandbox/plan.rs | Adds internal normalized scratch-path access. |
| nvx-rs/src/host/sandbox/mod.rs | Implements shared block-geometry inspection. |
| nvx-rs/src/host/sandbox/distro/tests.rs | Tests distro geometry and filesystem block sizes. |
| nvx-rs/src/host/sandbox/distro.rs | Exposes checked distro geometry. |
| nvx-rs/README.md | Documents clone planning and path-based device checks. |
Review effort: Balanced
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Cross-platform file-identity checks, scratch-claim lifetimes, and durable lease cleanup warrant final human review despite no identified blocking findings.
No open issues 🎉
1 resolved since last review
Review effort: Balanced
Enrique Saurez (esaurez)
force-pushed
the
esaurez/844-pr1-clone-plan-geometry-20261024
branch
from
October 25, 2026 04:50
7272be1 to
a50290c
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/844-pr1-clone-plan-geometry-20261024
branch
from
October 25, 2026 05:12
a50290c to
a2d5ddf
Compare
This was referenced Oct 25, 2026
This was referenced Oct 27, 2026
Enrique Saurez (esaurez)
force-pushed
the
esaurez/841-pr2-capture-publication-20261024
branch
from
October 27, 2026 16:24
ee88472 to
0a25211
Compare
Enrique Saurez (esaurez)
force-pushed
the
esaurez/844-pr1-clone-plan-geometry-20261024
branch
from
October 27, 2026 16:58
a2d5ddf to
fb05296
Compare
This was referenced Oct 27, 2026
Enrique Saurez (esaurez)
force-pushed
the
esaurez/841-pr2-capture-publication-20261024
branch
from
October 27, 2026 19:53
0a25211 to
0b23113
Compare
Base automatically changed from
esaurez/841-pr2-capture-publication-20261024
to
dev
October 28, 2026 01:04
ClonePlan checks a clone against the generation that it leased, before its launch and under the lock that acquired the lease: the template's contracts, its NIC, a memory target in whole blocks from the template's RAM to its capacity, and the clone's image and upper. The image and the scratch device must present the block geometry that the template recorded, which CheckedDistro::geometry and CheckedScratch::geometry read through the checked files as OpenVMM presents it, and be, at the paths by which OpenVMM opens them, the files that the checks found. An upper in RAM gets a fresh sparse scratch device, clone-scratch.raw, a file of the sandbox's store that a release of the lease removes. A scratch image is claimed, and never formatted, copied, or read. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
…ch as an image A clone plan now refuses a template that a runtime of other boot or control contracts captured, since this build cannot drive its guest. It also refuses an image or a scratch image that is the sandbox's clone-scratch.raw, by any path, since a plan replaces that file and a release of the lease removes it. The plan compares the files' identities, which catches a link among the directories of the path and a hard link. Since the store creates nothing but a regular file there, the plan also refuses a link or another kind of file at that path, through which the path of an image could pass. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A clone plan refused an image or a scratch image that is the sandbox's clone-scratch.raw. Every file that the sandbox's store owns is at the same risk: a launch replaces the log, the capability, and the journal, and removes the socket, the outcome report, and the console log, a release removes the clone binding, and a removal removes them all. The plan now lists the entries that the store owns, its files, the temporary files of its records, and the staged outcome reports, through LockedStore::owned_entries. It refuses an image or a scratch image that is any of them, by any path, and a link or a directory under any of their names. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
LockedStore::owned_entries matched the directory's listed names against the store's file names, which a file system that ignores case, as Windows' do, may list in other letter cases: an image under such a name passed a clone's plan, though it is the store's file, which a release removes. It now returns the store's files at the store's own paths, which the file system resolves as it does for the store's transitions, and the temporary files under their listed names, as a removal finds them. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
The capture of a template and the plan of a clone each identified the files that they hold open with a copy of the same function. Keep one, `identity_of`, beside the other helpers of those files in `host::store::regular`, and take it from there in both. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
Enrique Saurez (esaurez)
force-pushed
the
esaurez/844-pr1-clone-plan-geometry-20261024
branch
from
October 28, 2026 02:50
fb05296 to
e5c219f
Compare
Enrique Saurez (esaurez)
changed the base branch from
dev
to
esaurez/843-pr3-adapter-activate-20261024
October 28, 2026 02:50
This branch has not been deployed
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.


Stacked on #941; this PR retargets when its parent merges.
Chain: #936 → #937 → #939 → #940 → #941 → this PR → #949 → #950 → #951, on
dev; #879, #881, #885, #886, #877, #932, #884, #887, #933, and #880 have merged.Part of #844 (PR 1 of 5: the clone plan and block geometry).
What and why
A clone restores the generation of a template that it leased, with its own image and upper. This PR adds the plan that checks those inputs against the generation before any VMM starts, from metadata alone, so that a request that the template cannot serve fails before the launch. It launches nothing: the clone's launch and restore come in PR 2.
ClonePlanClonePlan::new(&Store, &mut Acquired, &CloneRequest, deadline) -> Result<ClonePlan>runs under the sandbox's lock thatStore::acquirereturned, before the launch that the lease reserves. It takesAcquiredexclusively while it plans, since it may create a file in the sandbox's directory, as the store's transitions under its lock do, and borrows nothing of it once it returns, so the launch can takeacquired.lockedafterwards. ACloneRequestholds the adapter'sSandboxPlan(SandboxPlan::adapter: the clone's image over an upper in RAM or on the caller's scratch image, and its network), the image'sDistroBinding, the distro policy revision that the caller accepts, theScratchBindingof a scratch image, and the memory target. The plan checks, in this order:,or;, and not named.vhd,.vhdx,.vmgs, or.iso; and a scratch binding exactly for an upper on a scratch image.LockedStore::reserved).Store::read_key, without computing its identity again) and its snapshot (Store::snapshot_dir).MEMORY_BLOCK_SIZE), and of the template's memory blocks, from the template's RAM to its RAM capacity.DistroBinding::check, then the geometry of the template's placeholder, its length included, then the file at its path, which must be none of the store's files (below), by any path.ScratchBinding::check, which claims the image for the clone, then the geometry of the template's scratch, its length included, then the file at its path, which must be none of the store's files either. Nothing formats, copies, or reads the image. In RAM: thescratchblock device, which every restore attaches, gets a fresh sparse file of the template's scratch length,clone-scratch.rawin the sandbox's directory, whose geometry on the sandbox's filesystem must be the template scratch's.The check at a path is the one that #887's captures make, since OpenVMM opens a restore's block devices itself, by path, as #887's
Storedocs describe in "Block devices by path": a regular file, not a link or other reparse point, the file that the binding's check opened, of the current user or an administrator, and that no one else may change. It compares the files by the device and file ID of their seals, which on Windows is the 128-bit file ID ofFILE_ID_INFO, unique on ReFS too. The plan holds the checked image, and for a scratch image the checked image with its claim. It fails holding nothing, and removes a sparse file that it began to create, even when the creation itself failed after it made the file.Block geometry
CheckedDistro::geometry()andCheckedScratch::geometry(), on what the bindings' checks return, give theBlockGeometrythat OpenVMM presents for a checked file, the type that the template's key records for its devices. The type is defined beside the sandbox's checks, beneath the template module that builds on them, and the template re-exports it at its public path,nvx::host::template::BlockGeometry, unchanged. They read the file's metadata through the checked handle, never its content: its length, which must be a nonzero multiple of 512 bytes; logical blocks of 512 bytes; and physical blocks of the I/O block size of its filesystem (st_blksize) on Linux, and of 4096 bytes on Windows, which must be a power of two of at least 512 bytes. A restore compares the geometry of each block device with the template's, so a clone's image has the placeholder's length, the capacity of every clone's image, in this stage.The sparse scratch device's lifetime
clone-scratch.rawis a file of the sandbox's store,store::CLONE_SCRATCH_NAMEinFILE_NAMES, asclone.jsonis: callers cannot declare it,LockedStore::removeremoves it, andStore::releasenow removes it before the clone binding, once the clone's VM has exited, so that the binding's durable removal covers both. A plan replaces a file that an earlier clone of the sandbox left.The store's transitions replace or remove each of its files: a plan
clone-scratch.raw, a launch the log, the capability, the journal, the socket, the outcome report, and the console log, a release the clone binding, and a removal them all. So that none of them ever replaces or removes a caller's image, the plan lists the entries that the store owns, throughLockedStore::owned_entries(), which names the store's files by the store's own paths, so that the file system resolves them as it does for the store's transitions, without regard to case on Windows, say, and refuses an image or a scratch image that is one of them, comparing the identities of the files, which catches every path to it, through a link among its directories or a hard link. Since the store creates nothing but regular files under its names, and its control socket, the plan also refuses a link or a directory under one of them, through which the path of an image could pass.Endpoints
The plan draws the clone's capability. The control endpoint is the one that the clone's launch chooses, and the boot console stays
none, as for cold boots (--virtio-console none), so the plan holds no endpoint.Not in this PR
Validation
This PR was rebased onto #941's head, ea69821, which brings in
devup to #933 and the chain below #941. Ports:template::filesis nowstore::regular, from which the plan takesopen_regularandwas_removed; andBlockGeometry, which the sandbox's checks return, is now defined in the sandbox module, beneath the template module, which re-exports it at its unchanged public path, so that the host's modules depend on one another without cycles, astests/host_modules.rsrequires. A new commit keeps oneidentity_of, instore::regular, for the template's capture and the clone plan, which each had a copy. The gates passed at its new head, e5c219f, with OpenVMM at d2338ea2:-D warningsin CI's three configurations and for each feature; tests with all and default features and without features;tests/host_modules.rs; the OpenVMM vectors test with--ignored; artifact bundling; docs; MSRV 1.89; the runtime adapter's protocol-only and host configurations), the markdown link check, andnvx.py verify. aci_edge_sandboxes: retire the crate with its CI, commands, and docs #917 retired check-aci-edge-sandboxes, which ran here before this rebase.-D warningson Linux and Windows, and passes the template, sandbox, and store tests on Windows.clone-scratch.raw, by its path, a hard link, or a linked directory, its log, or a record's temporary file; directories, or a link to one, under the store's names, and a leftover control socket, which passes; names of the store's files in other letter cases, which are the store's files where the file system ignores case; and templates whose runtime has other boot or control contracts. Also: the geometry of checked distro layers and scratch images, and its refusals; a release that removes the sparse file with the binding, durably; and a removal of a sandbox with the sparse file.std::path: the clone plan now takes&Pathwhere it took the removedPurePath, and shows paths as the base's block-device checks do, with no other change.dev; a stacked PR runs none.