Skip to content

nvx-rs: plan the clones of templates, and check their block geometry - #888

Open
Enrique Saurez (esaurez) wants to merge 5 commits into
esaurez/843-pr3-adapter-activate-20261024from
esaurez/844-pr1-clone-plan-geometry-20261024
Open

Enrique Saurez (esaurez) wants to merge 5 commits into
esaurez/843-pr3-adapter-activate-20261024from
esaurez/844-pr1-clone-plan-geometry-20261024

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

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.

ClonePlan

ClonePlan::new(&Store, &mut Acquired, &CloneRequest, deadline) -> Result<ClonePlan> runs under the sandbox's lock that Store::acquire returned, before the launch that the lease reserves. It takes Acquired exclusively 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 take acquired.locked afterwards. A CloneRequest holds the adapter's SandboxPlan (SandboxPlan::adapter: the clone's image over an upper in RAM or on the caller's scratch image, and its network), the image's DistroBinding, the distro policy revision that the caller accepts, the ScratchBinding of a scratch image, and the memory target. The plan checks, in this order:

  1. The request: no sandbox-profile settings; an image of the format and identity that its binding names; absolute paths that OpenVMM takes as raw images, without , or ;, and not named .vhd, .vhdx, .vmgs, or .iso; and a scratch binding exactly for an upper on a scratch image.
  2. The lease: one of this store, under the lock that acquired it, whose reserved launch has not begun (LockedStore::reserved).
  3. The generation: still indexed, with the lease's key; its key (Store::read_key, without computing its identity again) and its snapshot (Store::snapshot_dir).
  4. The template: it speaks this build's contracts, those through which its clones are restored and the boot and control contracts of the runtime that captured it; the plan has a network exactly when the template has a NIC; and the memory target is whole blocks of 128 MiB (MEMORY_BLOCK_SIZE), and of the template's memory blocks, from the template's RAM to its RAM capacity.
  5. The sandbox's store: a link or a directory under one of the names that it owns. Then the image: 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.
  6. A fresh capability, which the plan draws for the clone's launch.
  7. The upper. On a scratch image: 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: the scratch block device, which every restore attaches, gets a fresh sparse file of the template's scratch length, clone-scratch.raw in 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 Store docs 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 of FILE_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() and CheckedScratch::geometry(), on what the bindings' checks return, give the BlockGeometry that 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.raw is a file of the sandbox's store, store::CLONE_SCRATCH_NAME in FILE_NAMES, as clone.json is: callers cannot declare it, LockedStore::remove removes it, and Store::release now 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, through LockedStore::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 dev up to #933 and the chain below #941. Ports: template::files is now store::regular, from which the plan takes open_regular and was_removed; and BlockGeometry, 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, as tests/host_modules.rs requires. A new commit keeps one identity_of, in store::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:

  • Windows: every step of check-nvx-rs that runs on Windows (fmt; clippy with -D warnings in 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, and nvx.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.
  • Linux (x86_64): the same, plus the macOS target check, the guest binaries' checks, the runtime adapter's guest configuration, and the host tests of the clone plan, the platform, the supervisor, and the stores as root.
  • Each commit builds with clippy -D warnings on Linux and Windows, and passes the template, sandbox, and store tests on Windows.
  • New tests, on Linux and Windows: 18 tests of the clone plan, among them files on hugetlbfs, whose physical blocks are 2 MiB, on Linux; a creation of the sparse file that fails once it made the file; images and scratch images that are files of the sandbox's store, such as 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.
  • No VM: this PR launches nothing.
  • Restacked onto nvx-rs: capture templates into their store, and publish them #887's new head, whose base ports host paths to std::path: the clone plan now takes &Path where it took the removed PurePath, and shows paths as the base's block-device checks do, with no other change.
  • Left to CI: the full matrix, once this PR targets dev; a stacked PR runs none.

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.

🟡 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

Comment thread nvx-rs/src/host/template/clone.rs
Comment thread nvx-rs/src/host/template/clone.rs Outdated

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.

🟡 Changes recommended

Preflight still accepts block-image paths that the subsequent launch replaces, risking restore failure and image loss.

1 open issue
2 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/template/clone.rs Outdated

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.

🟡 Changes recommended

Windows filename casing can bypass ownership checks and allow lease release to delete a caller-supplied scratch image.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread nvx-rs/src/host/store/mod.rs Outdated

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.

🔵 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

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.

🔵 Needs a closer look

Cross-platform file identity, exclusive scratch claims, and crash-durable cleanup require human review because mistakes could replace or remove caller-owned images.

No open issues 🎉

Review effort: Balanced

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.

🔵 Needs a closer look

Cross-platform file identity, scratch-claim lifetimes, and crash-durable cleanup warrant human review alongside the pending CI matrix.

No open issues 🎉

Review effort: Balanced

Enrique Saurez and others added 5 commits October 27, 2026 19:27
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
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/844-pr1-clone-plan-geometry-20261024 branch from fb05296 to e5c219f Compare October 28, 2026 02:50
Copilot AI balanced review requested due to automatic review settings October 28, 2026 02:50
@esaurez
Enrique Saurez (esaurez) changed the base branch from dev to esaurez/843-pr3-adapter-activate-20261024 October 28, 2026 02:50

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 remain for the metadata-only clone-planning change.

No open issues 🎉

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants