diff --git a/doc/acceptance-case-matrix.md b/doc/acceptance-case-matrix.md
index c09b22c79..860e43fd6 100644
--- a/doc/acceptance-case-matrix.md
+++ b/doc/acceptance-case-matrix.md
@@ -143,7 +143,7 @@ The adapter's README said what each proved.
| Scenario | Fate | Coverage, or why it retires |
| --- | --- | --- |
-| `a_host_program_of_another_runtime_finds_the_vm_mismatched_and_signals_nothing` | rewritten | `nvx-rs/tests/security_checklist.rs::a_saved_vm_runs_until_its_process_exits_and_another_runtime_is_refused_without_a_signal`; `nvx-rs/src/host/vm/tests/reattach.rs::verifying_saved_values_checks_runtime_and_process_without_opening_an_endpoint` |
+| `a_host_program_of_another_runtime_finds_the_vm_mismatched_and_signals_nothing` | rewritten | `nvx-rs/src/host/vm/tests/reattach.rs::a_saved_vm_runs_until_its_process_exits_and_another_runtime_is_refused_without_a_signal`; `nvx-rs/src/host/vm/tests/reattach.rs::verifying_saved_values_checks_runtime_and_process_without_opening_an_endpoint` |
| `a_launch_tears_down_a_vm_whose_guest_agent_is_of_another_build` | retired | The adapter's guest agent and the build that its Ready reports |
| `a_restarted_host_program_reattaches_to_the_vm_and_stops_it` | rewritten | `nvx-rs/tests/vm_lifecycle.rs::a_cold_vm_is_reattached_from_saved_values_and_stopped`, which `test-microvm --scenario vm-lifecycle` runs; `nvx-rs/src/host/vm/tests/reattach.rs::reattaching_returns_the_held_owned_child_and_authenticated_connection`; `nvx-rs/src/host/vm/tests.rs::stop_forces_only_its_verified_process_after_grace` |
@@ -235,7 +235,7 @@ The adapter's README said what each proved.
| `a_forward_whose_port_another_socket_holds_fails_the_clone_as_the_host_s` | rewritten | `test-microvm --scenario platform-network-policy`; `nvx-rs/src/host/restore/tests.rs::restore_failures_attribute_the_existing_policy_free_evidence_classes` |
| `a_generation_is_eligible_by_the_clock_of_its_capture` | rewritten | `nvx-rs/src/host/snapshot/eligibility/tests.rs::within_one_boot_the_downtime_is_the_host_monotonic_clocks`; `nvx-rs/src/host/snapshot/tests.rs::snapshot_accepts_reports_early_and_expired_time_as_unavailable` |
| `a_generation_refuses_clones_of_fewer_processors_than_its_template_booted_with` | rewritten | `nvx-rs/src/host/snapshot/tests.rs::snapshot_accepts_refuses_a_blockless_root_and_each_processor_mismatch`; `test-microvm --scenario restore-processors` |
-| `a_host_program_of_another_runtime_ends_the_clone_through_its_verified_identity` | rewritten | `nvx-rs/tests/security_checklist.rs::a_saved_vm_runs_until_its_process_exits_and_another_runtime_is_refused_without_a_signal`; `nvx-rs/src/host/vm/tests.rs::stop_forces_only_its_verified_process_after_grace`. Leases retire (#1121) |
+| `a_host_program_of_another_runtime_ends_the_clone_through_its_verified_identity` | rewritten | `nvx-rs/src/host/vm/tests/reattach.rs::a_saved_vm_runs_until_its_process_exits_and_another_runtime_is_refused_without_a_signal`; `nvx-rs/src/host/vm/tests.rs::stop_forces_only_its_verified_process_after_grace`. Leases retire (#1121) |
| `a_run_in_a_template_of_a_network_finds_its_policy_s_proxy` | rewritten | `test-microvm --scenario platform-network-policy`; `test-microvm --scenario untiered-network-policy`, whose runs restore a named snapshot with a NIC under a network policy of their own, though with no proxy. The template's `run` retires (#1121) |
| `a_run_runs_one_execution_in_its_clone_then_tears_the_clone_down` | rewritten | `test-microvm --scenario snapshot-core`, whose `run --restore-snapshot` runs one workload in its restored VM and removes its sandbox. The template's `run` and leases retire (#1121) |
| `a_run_s_execution_finds_its_shares_beneath_the_root` | rewritten | `test-microvm --scenario platform-aggregate-export` |
@@ -500,7 +500,7 @@ this matrix. The README's own code is no doctest.
| `nvx-rs/src/guest/time/mod.rs - guest::time (line 9)` | kept | SL6.11c removed its capture and restore finish, which became crate-private: `guest::snapshot::capture` runs them |
| `nvx-rs/src/host/sandbox/prepared.rs - host::sandbox::prepared::PreparedLaunch (line 92)` | retired | `PreparedLaunch` leaves with the checked-file handoff (SL6.3, #1122) |
| `nvx-rs/src/host/store/mod.rs - host::store (line 72)` | retired | The sandbox store leaves (SL6.1, #1121) |
-| `nvx-rs/src/host/supervisor/mod.rs - host::supervisor (line 32)` | retired | The store-coupled supervisor leaves with the sandbox store (SL6.1, #1121), and `host::supervisor`, the transitional public path of the low-level spawn, with SL6.3 (#1122): `host::vm` spawns VMMs, and the tests of the spawn moved to `nvx-rs/src/host/spawn/tests.rs` and `nvx-rs/tests/process.rs` |
+| `nvx-rs/src/host/supervisor/mod.rs - host::supervisor (line 32)` | retired | The store-coupled supervisor leaves with the sandbox store (SL6.1, #1121), and `host::supervisor`, the transitional public path of the low-level spawn, with SL6.3 (#1122): `host::vm` spawns VMMs, and the tests of the spawn moved to `nvx-rs/src/host/spawn/tests.rs` and `nvx-rs/src/host/process/tests/orchestrator.rs` |
| `nvx-rs/src/host/template/clone.rs - host::template::clone::ClonePlan (line 247)` | retired | `ClonePlan` and `CloneRequest` leave with the old clone path (SL6.2b, #1121) |
| `nvx-rs/src/host/template/plan.rs - host::template::plan::TemplatePlan (line 137)` | retired | `TemplatePlan` leaves with the template store (SL6.2a, #1121) |
| `nvx-rs/src/host/template/store.rs - host::template::store::Store (line 473)` | retired | The template store leaves (SL6.2a, #1121) |
diff --git a/nvx-rs/README.md b/nvx-rs/README.md
index 5cb11ebd5..d9695d4ae 100644
--- a/nvx-rs/README.md
+++ b/nvx-rs/README.md
@@ -122,7 +122,7 @@ changes only when the module's purpose does.
| [`nvx::host::nvxs`](#nvxhostnvxs) | Compatibility paths for the host NVXS connection and its retained cause details |
| [`nvx::host::transport`](#nvxhosttransport) | A verified VM endpoint connection and the blocking host NVXS driver, with the existing timed transport |
| [`nvx::host::process`](#nvxhostprocess) | Verified references to OpenVMM processes, through which callers wait for them and end them |
-| [`nvx::host::platform`, `report`](#nvxhostplatform-report) | Linux and Windows primitives, and OpenVMM's outcome report |
+| [`nvx::host::report`](#nvxhostreport) | OpenVMM's outcome report |
| [`nvx::host::sandbox`](#nvxhostsandbox) | The launch profile of `nvx-rs sandbox`: its live shares and option checks; its layer and bindings are private to the CLI |
| [`nvx::guest`](#nvxguest) | The programs that run in Linux guests; see [Guest binaries](#guest-binaries) |
| [`nvx::guest::mappings`](#nvxguestmappings) | `Mapping` and `MappingTable`: host mappings, and the account of a host's mapping table, for any guest init or agent |
@@ -985,11 +985,29 @@ and another transport failure `Io`. Original I/O and protocol causes are
retained directly; attachment refusal, reset and ended-connection details
remain available as sources.
-`Transport` and `TransportHandle` re-export the existing platform types, with
-one definition beneath the OS adapters. Timed reads and writes, native-handle
-duplication and `unread` are unchanged: Linux reports Unix `SIOCOUTQ` charges,
-and Windows reports `None`. The old `session::connect`, private to the crate
-like the session, remains a compatibility wrapper for the CLI.
+`Transport` and `TransportHandle` re-export the types of the crate's private
+platform layer, with one definition beneath the OS adapters. Timed reads and
+writes, native-handle duplication and `unread` are unchanged: Linux reports
+Unix `SIOCOUTQ` charges, and Windows reports `None`. The old
+`session::connect`, private to the crate like the session, remains a
+compatibility wrapper for the CLI.
+
+A `Transport` to an OpenVMM control endpoint reads and writes with deadlines,
+and tells, through `Transport::unread`, what the endpoint has not read yet of
+what it wrote, where the platform tells: on Linux, what `SIOCOUTQ` reports of
+its Unix socket, the kernel's charge for the buffers of each write until
+OpenVMM read each whole, which a read of part of a buffer leaves as it was, and
+which is zero once OpenVMM read everything. A named pipe tells its writer
+nothing of the kind, so on Windows, and for a transport that does not tell, it
+is `None`.
+
+On Linux and Windows, `Transport::try_clone_handle` optionally returns an
+owned native handle to the same peer-verified connection, not a reconnect:
+an `OwnedFd` for the Unix socket or an `OwnedHandle` for the overlapped pipe.
+An adapter must drop the blocking transport before using or registering the
+duplicate with an I/O runtime, or changing the shared blocking mode. The
+default is `Ok(None)`, so transports without native handles need not implement
+it, and NVX does not depend on an asynchronous I/O runtime.
### `nvx::host::process`
@@ -1031,35 +1049,19 @@ holds what the process's children read once it reaped them, as the kernel
accounts them to their parent, while Windows counts the process's own reads
alone.
-### `nvx::host::platform`, `report`
+### `nvx::host::report`
-Linux and Windows primitives, and OpenVMM's outcome report. The authenticated
-control session, which carries NVX's own control protocol, `NVXC`, one frame
-per DATA record of a `HostConnection`, is private to the crate: the CLI and its
-restore driver use it, with the `cli` feature.
+OpenVMM's outcome report. The Linux and Windows primitives are private to the
+crate, in `host::platform`, and so is the authenticated control session, which
+carries NVX's own control protocol, `NVXC`, one frame per DATA record of a
+`HostConnection`: the CLI and its restore driver use it, with the `cli`
+feature.
`report::read` returns `Ok(None)` for a missing report, and an `Io` error for an
unreadable or malformed report, retaining the underlying error. It keeps today's
summary of the outcome category, status code and teardown steps, not free-form
fields.
-A `Transport` to an OpenVMM control endpoint reads and writes with deadlines,
-and tells, through `Transport::unread`, what the endpoint has not read yet of
-what it wrote, where the platform tells: on Linux, what `SIOCOUTQ` reports of
-its Unix socket, the kernel's charge for the buffers of each write until
-OpenVMM read each whole, which a read of part of a buffer leaves as it was, and
-which is zero once OpenVMM read everything. A named pipe tells its writer
-nothing of the kind, so on Windows, and for a transport that does not tell, it
-is `None`.
-
-On Linux and Windows, `Transport::try_clone_handle` optionally returns an
-owned native handle to the same peer-verified connection, not a reconnect:
-an `OwnedFd` for the Unix socket or an `OwnedHandle` for the overlapped pipe.
-An adapter must drop the blocking transport before using or registering the
-duplicate with an I/O runtime, or changing the shared blocking mode. The
-default is `Ok(None)`, so transports without native handles need not implement
-it, and NVX does not depend on an asynchronous I/O runtime.
-
### `nvx::host::sandbox`
The launch profile of `nvx-rs sandbox`, with native input validation: the
@@ -1808,8 +1810,8 @@ components alone, as `Path::components` reads them, with backslashes on
Windows, and resolved by `std::fs::canonicalize`, with the components that do
not exist yet appended. Paths that are not valid Unicode are refused. The
filesystem queries are those of `std::fs`, and the identities of files those
-of `nvx::host::platform`: the device and inode on Unix, and the volume serial
-number and the 64-bit file index on Windows.
+of the crate's private platform layer: the device and inode on Unix, and the
+volume serial number and the 64-bit file index on Windows.
Every host path becomes absolute through one function, which joins a relative
path to the current directory without consulting the filesystem, so it
@@ -2781,11 +2783,13 @@ dependencies: break a cycle by moving the item that a lower child takes from a
higher one down, and re-exporting it at its old path. So the children stack in
layers, each above those that it depends on, which the test prints with
`--nocapture`.
-The process and spawn test spawns VMM stand-ins through the low-level spawn,
-waits for them through their verified references, finds one again after the
-process that spawned it crashed, and checks that a VMM inherits only its
-standard streams and the descriptors or handles that it is allowed while
-another thread spawns processes.
+The process tests, unit tests of `nvx::host::process`, start processes of
+their own as an orchestrator does, name them by the start time that the
+crate's private platform layer reads, wait for them through their verified
+references, and find one again after the process that started it exited; the
+spawn's unit tests check that a VMM inherits only its standard streams and the
+descriptors or handles that it is allowed while another thread spawns
+processes.
On Linux, with the `cli` feature, a loopback test drives
the host's private `ControlSession` through a broker shim into the real managed
agent's session, dispatch, and execution engine, and `nvx.py` also lints and
@@ -2802,10 +2806,12 @@ The [security checklist](tests/security_checklist.rs) checks, through the
public API alone and without a VM, each security requirement that the host's
documentation states: the guest targets of an export and a read-only path
inside a writable one, the exported objects that each start checks again, a
-VM's private directory and its new private log, the exit proof of a saved VM,
-the owner of its endpoint, and its recovery from the saved values, and a
-snapshot's strict directory. An idle copy of the test binary stands in for a
-VM's process. The checks that NVX makes of a restore need a captured snapshot,
+VM's private directory and its new private log, and a snapshot's strict
+directory. The exit proof of a saved VM, the owner of its endpoint, and its
+recovery from the saved values need a process that the test names by its start
+time, which only the crate's private platform layer reads, so the unit tests of
+`nvx::host::vm` check them, with an idle copy of their test binary as the VM's
+process. The checks that NVX makes of a restore need a captured snapshot,
so the unit tests of `nvx::host::vm`, `nvx::host::snapshot`, and
`nvx::host::restore` check them. The checks of a staged image and the claim of
a scratch image, which the CLI keeps in directories that only its own account
diff --git a/nvx-rs/public-api/linux.txt b/nvx-rs/public-api/linux.txt
index 9a2af9165..3b57f757f 100644
--- a/nvx-rs/public-api/linux.txt
+++ b/nvx-rs/public-api/linux.txt
@@ -1839,17 +1839,6 @@ field nvx::host::nvxs::HostConnectionError::Rejected::0
variant nvx::host::nvxs::HostConnectionError::Reset
field nvx::host::nvxs::HostConnectionError::Reset::0
variant nvx::host::nvxs::HostConnectionError::TimedOut
-mod nvx::host::platform
-const nvx::host::platform::SUPPORTED
-trait nvx::host::platform::Transport
-method nvx::host::platform::Transport::read
-method nvx::host::platform::Transport::try_clone_handle
-method nvx::host::platform::Transport::unread
-method nvx::host::platform::Transport::write_all
-type nvx::host::platform::TransportHandle
-fn nvx::host::platform::create_private_dir
-fn nvx::host::platform::file_identity
-fn nvx::host::platform::process_start_time
mod nvx::host::process
struct nvx::host::process::BootId
method nvx::host::process::BootId::as_bytes
diff --git a/nvx-rs/public-api/windows.txt b/nvx-rs/public-api/windows.txt
index 1a4029d05..25ae22fa6 100644
--- a/nvx-rs/public-api/windows.txt
+++ b/nvx-rs/public-api/windows.txt
@@ -567,17 +567,6 @@ field nvx::host::nvxs::HostConnectionError::Rejected::0
variant nvx::host::nvxs::HostConnectionError::Reset
field nvx::host::nvxs::HostConnectionError::Reset::0
variant nvx::host::nvxs::HostConnectionError::TimedOut
-mod nvx::host::platform
-const nvx::host::platform::SUPPORTED
-trait nvx::host::platform::Transport
-method nvx::host::platform::Transport::read
-method nvx::host::platform::Transport::try_clone_handle
-method nvx::host::platform::Transport::unread
-method nvx::host::platform::Transport::write_all
-type nvx::host::platform::TransportHandle
-fn nvx::host::platform::create_private_dir
-fn nvx::host::platform::file_identity
-fn nvx::host::platform::process_start_time
mod nvx::host::process
struct nvx::host::process::BootId
method nvx::host::process::BootId::as_bytes
diff --git a/nvx-rs/src/host/mod.rs b/nvx-rs/src/host/mod.rs
index 7c41e0d81..95d81b7ed 100644
--- a/nvx-rs/src/host/mod.rs
+++ b/nvx-rs/src/host/mod.rs
@@ -22,8 +22,8 @@
//! - [`transport`] verifies a VM's endpoint and runs the host's role of the NVXS session for it,
//! for any application protocol, over a platform transport. The private `session` (feature
//! `cli`) speaks NVX's own control protocol, `NVXC`, over it for NVX's programs. [`nvxs`]
-//! retains compatibility paths. [`platform`] holds the primitives of Linux and Windows hosts.
-//! Other hosts compile but report NVX as unavailable.
+//! retains compatibility paths. The private `platform` module holds the primitives of Linux and
+//! Windows hosts. Other hosts compile but report NVX as unavailable.
//! - [`process`] names processes by verified references: their ID, start time, and host boot,
//! through which a pidfd or a process handle holds them, so that waits sleep until a process
//! exits and termination reaches only it, whether or not the caller started it.
@@ -50,7 +50,7 @@
//! caller allows, and no other descriptor or handle of the caller's.
//!
//! The children of `host` depend on one another without cycles: each builds only on children
-//! beneath it, as [`vm`] does on `spawn`, and `spawn` on [`platform`]. An item that
+//! beneath it, as [`vm`] does on `spawn`, and `spawn` on `platform`. An item that
//! a lower child needs from a higher one moves down, and the higher child re-exports it where it
//! was. `tests/host_modules.rs` reads the crate's sources and fails on a cycle, which it lists
//! with a reference for each of its dependencies.
@@ -71,7 +71,7 @@ pub mod filesystem;
pub mod network;
pub mod nvxs;
pub(crate) mod paths;
-pub mod platform;
+pub(crate) mod platform;
pub mod process;
pub mod report;
pub mod restore;
diff --git a/nvx-rs/src/host/platform/linux/identity.rs b/nvx-rs/src/host/platform/linux/identity.rs
index 18cd920f4..2612d8d65 100644
--- a/nvx-rs/src/host/platform/linux/identity.rs
+++ b/nvx-rs/src/host/platform/linux/identity.rs
@@ -14,7 +14,7 @@ use std::path::Path;
use crate::host::sys::Errno;
/// Returns the device and inode of a file or directory.
-pub fn file_identity(path: &Path) -> io::Result<(u64, u64)> {
+pub(crate) fn file_identity(path: &Path) -> io::Result<(u64, u64)> {
let metadata = fs::metadata(path)?;
Ok((metadata.dev(), metadata.ino()))
}
diff --git a/nvx-rs/src/host/platform/linux/mod.rs b/nvx-rs/src/host/platform/linux/mod.rs
index 63d87da00..db346b855 100644
--- a/nvx-rs/src/host/platform/linux/mod.rs
+++ b/nvx-rs/src/host/platform/linux/mod.rs
@@ -20,20 +20,22 @@ pub(crate) use self::endpoint::{ReadyListener, connect_endpoint, listen_ready};
pub(crate) use self::endpoint::{
connect_child_endpoint, endpoint_server_pid_until, interrupt_transfers, transport_from_handle,
};
-pub use self::identity::file_identity;
+pub(crate) use self::identity::file_identity;
#[cfg(feature = "cli")]
pub(crate) use self::identity::{
Directory, file_id, is_deleted, opened_file_id, opened_file_identity,
};
#[cfg(feature = "cli")]
pub(crate) use self::log_claim::{claim_log, log_claim_held, serves_launch};
-pub use self::private::create_private_dir;
pub(crate) use self::private::{
check_snapshot_dir, check_store_dir, create_store_dir, make_sparse, private_file_problem,
};
#[cfg(feature = "cli")]
pub(crate) use self::private::{rename_no_replace, restore_private_file_mode};
-pub use self::process::process_start_time;
+// Outside this module, only the CLI, which checks the processes that its records name, and the
+// tests, which name the processes that they start, read a process's start time.
+#[cfg(any(test, feature = "cli"))]
+pub(crate) use self::process::process_start_time;
pub(crate) use self::process::{
ProcessHandle, boot_id, host_id, monotonic_ns, open_process, own_bytes_read, pin_child,
};
@@ -41,16 +43,9 @@ pub(crate) use self::spawn::detach;
#[cfg(feature = "cli")]
pub(crate) use self::spawn::inherit_only;
-/// Whether this host can run NVX microVMs.
-pub const SUPPORTED: bool = true;
-
/// File that holds the identity of the current boot, a UUID that the kernel chooses at random.
const BOOT_ID_PATH: &str = "/proc/sys/kernel/random/boot_id";
-/// Mode of a directory that only the current user may access: read, write, and search for its
-/// owner, and nothing for anyone else.
-const PRIVATE_DIR_MODE: Mode = Mode::S_IRWXU;
-
/// Mode of the directory of a sandbox store, the exact mode that OpenVMM requires of the
/// directory of its control socket.
const STORE_DIR_MODE: Mode = Mode::S_IRWXU;
diff --git a/nvx-rs/src/host/platform/linux/private.rs b/nvx-rs/src/host/platform/linux/private.rs
index 2ff82605b..1c7cac3e9 100644
--- a/nvx-rs/src/host/platform/linux/private.rs
+++ b/nvx-rs/src/host/platform/linux/private.rs
@@ -11,27 +11,11 @@ use std::path::Path;
use nix::sys::stat::Mode;
use nix::unistd::Uid;
-use super::{GROUP_AND_OTHER_BITS, PRIVATE_DIR_MODE, STORE_DIR_MODE};
+use super::{GROUP_AND_OTHER_BITS, STORE_DIR_MODE};
#[cfg(feature = "cli")]
use crate::host::sys;
use crate::host::sys::Errno;
-/// Creates `path` if needed and restricts it to the current user.
-pub fn create_private_dir(path: &Path) -> io::Result<()> {
- match DirBuilder::new().mode(PRIVATE_DIR_MODE.bits()).create(path) {
- Ok(()) => {}
- Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
- Err(error) => return Err(error),
- }
- if !fs::symlink_metadata(path)?.is_dir() {
- return Err(io::Error::other(format!(
- "{} is not a plain directory",
- path.display()
- )));
- }
- fs::set_permissions(path, Permissions::from_mode(PRIVATE_DIR_MODE.bits()))
-}
-
/// Creates the directory `path` of a sandbox store with mode 0700, or checks that an existing one
/// is private to the current user, as [`check_store_dir`] does, and returns why it is not, if it
/// is not.
diff --git a/nvx-rs/src/host/platform/linux/process.rs b/nvx-rs/src/host/platform/linux/process.rs
index f5c344e21..2288d33bf 100644
--- a/nvx-rs/src/host/platform/linux/process.rs
+++ b/nvx-rs/src/host/platform/linux/process.rs
@@ -18,7 +18,7 @@ use crate::host::sys::{self, Errno, PidFd};
/// exits, as on Windows: a zombie whose main thread exited first still runs while its other threads
/// exit, and they keep the process's descriptors, such as an inherited log and its lock, open.
/// Zombies without other threads count as exited.
-pub fn process_start_time(pid: u32) -> io::Result