Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions nvx-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ changes only when the module's purpose does.
| [`nvx::guest::exec`](#nvxguestexec) | `Execution`: drives one program as an execution, for any guest init or agent |
| [`nvx::guest::exec::binding`](#nvxguestexecbinding) | `Binding`: an `nvx::exec::Runner` bound to an `Execution`, the glue that a guest adapter would otherwise write |
| [`nvx::guest::time`](#nvxguesttime) | x86_64: the steps of NVX time ABI v1 as typed calls in the caller's process, for any guest init or agent |
| [`nvx::guest::snapshot`](#nvxguestsnapshot) | x86_64: bounded Fresh capture, staged failure dispositions, and MMIO device mount checks, without execution control |
| [`nvx::guest::snapshot`](#nvxguestsnapshot) | x86_64: bounded Fresh capture with inline restore repair and staged failure dispositions, without execution control |
| [`nvx::guest::activation`](#nvxguestactivation) | x86_64: fence-free activation, with the retained template capture and clone repair interface |
| [`nvx::guest::reseed`](#nvxguestreseed) | A restore's fresh entropy, and the reseed of the kernel's random number generator with it |
| [`nvx::guest::root`](#nvxguestroot) | The operations of a layered root: its bootstrap, assembly, shares, export, mappings, and teardown |
Expand Down Expand Up @@ -1766,15 +1766,20 @@ A strict time violation preserves its actual status. A `PortRelease` remains
the cause even if detector rollback also fails. Request and release failures
are final.

The time core performs its existing clock correction after a restore, but
this interface does not complete the clone's repair and returns `MustEnd`.
After the time core's existing clock correction, `capture` runs the clone's
existing repair inline. `Captured::Restored(Repaired)` returns only once its
processor and memory targets, reseed and finish completed. `Repaired` reports
the generation ID, CPU count, memory size and acknowledgement instant; every
repair failure is `MustEnd` with the existing `RepairError`'s status and detail.
Fresh platform restore packets must carry no network settings; even an
otherwise empty network policy is explicitly refused.

The caller owns its transport. After answering the capture request, send
nothing until `capture` returns. After `Continued` or a `Resumed` failure,
report on the same session. The activation module retains the legacy
Device-shaped capture interface for existing callers.
report on the same session. After `Restored`, take the session's RESET, then
call `GuestConnection::acknowledge(window)` on that session before receiving
the activation record. The activation module retains its legacy Device-shaped
capture and repair interfaces for the test adapter until its migration.

### `nvx::guest::activation`

Expand Down
68 changes: 68 additions & 0 deletions nvx-rs/public-api/linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,8 @@ field nvx::guest::activation::CaptureCause::Io::0
variant nvx::guest::activation::CaptureCause::Mounted
field nvx::guest::activation::CaptureCause::Mounted::0
variant nvx::guest::activation::CaptureCause::Pending
variant nvx::guest::activation::CaptureCause::Repair
field nvx::guest::activation::CaptureCause::Repair::0
variant nvx::guest::activation::CaptureCause::Time
field nvx::guest::activation::CaptureCause::Time::0
struct nvx::guest::activation::CaptureError
Expand All @@ -702,6 +704,7 @@ variant nvx::guest::activation::CaptureStage::Fence
variant nvx::guest::activation::CaptureStage::GenerationId
variant nvx::guest::activation::CaptureStage::Mounts
variant nvx::guest::activation::CaptureStage::Release
variant nvx::guest::activation::CaptureStage::Repair
variant nvx::guest::activation::CaptureStage::Suppression
enum nvx::guest::activation::Captured
variant nvx::guest::activation::Captured::Continued
Expand Down Expand Up @@ -918,6 +921,8 @@ field nvx::guest::clone::CaptureCause::Io::0
variant nvx::guest::clone::CaptureCause::Mounted
field nvx::guest::clone::CaptureCause::Mounted::0
variant nvx::guest::clone::CaptureCause::Pending
variant nvx::guest::clone::CaptureCause::Repair
field nvx::guest::clone::CaptureCause::Repair::0
variant nvx::guest::clone::CaptureCause::Time
field nvx::guest::clone::CaptureCause::Time::0
struct nvx::guest::clone::CaptureError
Expand All @@ -935,6 +940,7 @@ variant nvx::guest::clone::CaptureStage::Fence
variant nvx::guest::clone::CaptureStage::GenerationId
variant nvx::guest::clone::CaptureStage::Mounts
variant nvx::guest::clone::CaptureStage::Release
variant nvx::guest::clone::CaptureStage::Repair
variant nvx::guest::clone::CaptureStage::Suppression
enum nvx::guest::clone::Captured
variant nvx::guest::clone::Captured::Continued
Expand Down Expand Up @@ -1741,6 +1747,8 @@ field nvx::guest::snapshot::CaptureCause::Io::0
variant nvx::guest::snapshot::CaptureCause::Mounted
field nvx::guest::snapshot::CaptureCause::Mounted::0
variant nvx::guest::snapshot::CaptureCause::Pending
variant nvx::guest::snapshot::CaptureCause::Repair
field nvx::guest::snapshot::CaptureCause::Repair::0
variant nvx::guest::snapshot::CaptureCause::Time
field nvx::guest::snapshot::CaptureCause::Time::0
struct nvx::guest::snapshot::CaptureFailure
Expand All @@ -1755,9 +1763,12 @@ variant nvx::guest::snapshot::CaptureStage::Fence
variant nvx::guest::snapshot::CaptureStage::GenerationId
variant nvx::guest::snapshot::CaptureStage::Mounts
variant nvx::guest::snapshot::CaptureStage::Release
variant nvx::guest::snapshot::CaptureStage::Repair
variant nvx::guest::snapshot::CaptureStage::Suppression
enum nvx::guest::snapshot::Captured
variant nvx::guest::snapshot::Captured::Continued
variant nvx::guest::snapshot::Captured::Restored
field nvx::guest::snapshot::Captured::Restored::0
struct nvx::guest::snapshot::Devices
field nvx::guest::snapshot::Devices::distro
field nvx::guest::snapshot::Devices::filesystem_tag
Expand All @@ -1777,6 +1788,63 @@ variant nvx::guest::snapshot::Origin::Distro
variant nvx::guest::snapshot::Origin::Filesystem
variant nvx::guest::snapshot::Origin::Overlay
variant nvx::guest::snapshot::Origin::Scratch
enum nvx::guest::snapshot::RangeFault
variant nvx::guest::snapshot::RangeFault::Aperture
variant nvx::guest::snapshot::RangeFault::Extra
variant nvx::guest::snapshot::RangeFault::Gap
variant nvx::guest::snapshot::RangeFault::Misaligned
variant nvx::guest::snapshot::RangeFault::Overlap
enum nvx::guest::snapshot::RepairCause
variant nvx::guest::snapshot::RepairCause::Activation
field nvx::guest::snapshot::RepairCause::Activation::online
field nvx::guest::snapshot::RepairCause::Activation::target
variant nvx::guest::snapshot::RepairCause::BlockSize
field nvx::guest::snapshot::RepairCause::BlockSize::0
variant nvx::guest::snapshot::RepairCause::CapturedRam
variant nvx::guest::snapshot::RepairCause::Finished
variant nvx::guest::snapshot::RepairCause::Incomplete
variant nvx::guest::snapshot::RepairCause::Io
field nvx::guest::snapshot::RepairCause::Io::0
variant nvx::guest::snapshot::RepairCause::Missing
field nvx::guest::snapshot::RepairCause::Missing::0
variant nvx::guest::snapshot::RepairCause::NoSeed
variant nvx::guest::snapshot::RepairCause::Offline
field nvx::guest::snapshot::RepairCause::Offline::block
field nvx::guest::snapshot::RepairCause::Offline::state
variant nvx::guest::snapshot::RepairCause::OnlineCpus
field nvx::guest::snapshot::RepairCause::OnlineCpus::0
variant nvx::guest::snapshot::RepairCause::ProcessorTarget
field nvx::guest::snapshot::RepairCause::ProcessorTarget::online
field nvx::guest::snapshot::RepairCause::ProcessorTarget::target
variant nvx::guest::snapshot::RepairCause::Range
field nvx::guest::snapshot::RepairCause::Range::fault
field nvx::guest::snapshot::RepairCause::Range::index
variant nvx::guest::snapshot::RepairCause::Reseed
field nvx::guest::snapshot::RepairCause::Reseed::0
variant nvx::guest::snapshot::RepairCause::Time
field nvx::guest::snapshot::RepairCause::Time::0
variant nvx::guest::snapshot::RepairCause::UntargetedRanges
struct nvx::guest::snapshot::RepairError
method nvx::guest::snapshot::RepairError::cause
method nvx::guest::snapshot::RepairError::progress
method nvx::guest::snapshot::RepairError::stage
method nvx::guest::snapshot::RepairError::status
struct nvx::guest::snapshot::RepairProgress
method nvx::guest::snapshot::RepairProgress::added
method nvx::guest::snapshot::RepairProgress::finish_started
method nvx::guest::snapshot::RepairProgress::onlined
method nvx::guest::snapshot::RepairProgress::reseeded
enum nvx::guest::snapshot::RepairStage
variant nvx::guest::snapshot::RepairStage::Finish
variant nvx::guest::snapshot::RepairStage::Memory
variant nvx::guest::snapshot::RepairStage::Processors
variant nvx::guest::snapshot::RepairStage::Reseed
variant nvx::guest::snapshot::RepairStage::Restore
struct nvx::guest::snapshot::Repaired
method nvx::guest::snapshot::Repaired::acknowledged_at
method nvx::guest::snapshot::Repaired::cpus
method nvx::guest::snapshot::Repaired::generation_id
method nvx::guest::snapshot::Repaired::memory
fn nvx::guest::snapshot::capture
mod nvx::guest::spawn
struct nvx::guest::spawn::Barrier
Expand Down
19 changes: 13 additions & 6 deletions nvx-rs/src/guest/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,6 @@
//! writes the machine ID's and the resolver's files in the runtime directory, whose binds over
//! the root's `/etc/machine-id` and `/etc/resolv.conf` need Linux 6.8 or later.

mod repair;
mod state;
#[cfg(test)]
mod tests;
Expand All @@ -293,9 +292,6 @@ use super::time::{
CaptureOutcome, CaptureRequest, PreCaptureError, Restore, ScratchPolicy, TimeAbi, TimeError,
};

pub use self::repair::{
RangeFault, Repair, RepairCause, RepairError, RepairProgress, RepairStage, Repaired, repair,
};
pub use self::state::{
Activation, ActivationAction, ActivationCause, ActivationFailure, ActivationInput,
ActivationState, MACHINE_ID_FILE, RESOLVER_FILE,
Expand All @@ -304,7 +300,11 @@ pub use self::state::{
pub use self::state::{
Applied, AppliedTeardownError, ApplyError, NetworkError, RootTemplate, apply,
};
pub use super::snapshot::repair::{Repair, repair};
pub use super::snapshot::{MOUNT_TABLE_LIMIT, Mounted, Origin};
pub use super::snapshot::{
RangeFault, RepairCause, RepairError, RepairProgress, RepairStage, Repaired,
};

/// The mount table of the calling process's mount namespace, below procfs.
const MOUNT_TABLE: &str = "self/mountinfo";
Expand Down Expand Up @@ -653,6 +653,8 @@ pub enum CaptureStage {
Capture,
/// Restoring the stall detectors' settings, as the barrier releases them.
Release,
/// Repairing the clone's resources, entropy and acknowledgement after a restore.
Repair,
}

impl fmt::Display for CaptureStage {
Expand All @@ -666,6 +668,7 @@ impl fmt::Display for CaptureStage {
Self::Suppression => "suppress the stall detectors",
Self::GenerationId => "read the VM generation ID",
Self::Capture => "request the capture",
Self::Repair => "repair the restored clone",
Self::Release => "restore the stall detectors",
})
}
Expand All @@ -689,6 +692,8 @@ pub enum CaptureCause {
/// A call of the time ABI failed: a violation that a strict or report-only call reports, or a
/// failure of its ports, its time state, or its entropy file.
Time(TimeError),
/// The inline repair of a restored clone failed, with its existing stage and progress.
Repair(RepairError),
}

impl fmt::Display for CaptureCause {
Expand All @@ -698,6 +703,7 @@ impl fmt::Display for CaptureCause {
Self::Mounted(mounted) => mounted.fmt(formatter),
Self::Pending => formatter.write_str("a capture is already pending"),
Self::Time(error) => error.fmt(formatter),
Self::Repair(error) => error.fmt(formatter),
}
}
}
Expand Down Expand Up @@ -747,6 +753,7 @@ impl std::error::Error for CaptureError {
match &self.cause {
CaptureCause::Io(error) => Some(error),
CaptureCause::Time(error) => Some(error),
CaptureCause::Repair(error) => Some(error),
CaptureCause::Mounted(_) | CaptureCause::Pending => None,
}
}
Expand Down Expand Up @@ -803,7 +810,7 @@ pub(super) fn capture_system(deadline: Option<Instant>) -> Box<dyn System> {

/// Returns the time ABI of the capture and of the repair: in strict mode, unless the kernel
/// command line makes it report-only.
fn live_abi() -> TimeAbi {
pub(super) fn live_abi() -> TimeAbi {
TimeAbi::new().strict()
}

Expand Down Expand Up @@ -880,7 +887,7 @@ impl System for LiveSystem {

/// Returns `error`, of an operation on `path`, as an error of the same kind whose text names the
/// path, such as `/proc/self/mountinfo: Permission denied`.
fn at(path: &Path, error: &io::Error) -> io::Error {
pub(super) fn at(path: &Path, error: &io::Error) -> io::Error {
io::Error::new(
error.kind(),
format!("{}: {}", path.display(), sys::describe(error)),
Expand Down
67 changes: 50 additions & 17 deletions nvx-rs/src/guest/snapshot.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! A Fresh platform capture in the calling guest process, without execution control.
//! A Fresh platform capture and restore repair in the calling guest process.
//!
//! [`capture`] checks mounts and the time ABI, suppresses the stall detectors, reads the
//! generation ID, and requests capture. All pre-request steps, locks, waits and rollback
Expand All @@ -7,10 +7,16 @@
//!
//! The caller keeps its executions still. After answering the capture request, its transport
//! sends nothing until this call returns. After [`Captured::Continued`] or a failure with
//! [`Disposition::Resumed`], it reports on the same session.
//! [`Disposition::Resumed`], it reports on the same session. After [`Captured::Restored`], it
//! takes RESET and calls `GuestConnection::acknowledge(window)` before receiving activation.
//!
//! The time core repairs a restore's clock, but this interface does not complete the clone's
//! repair, so a restored guest must end. Fresh restore network settings are explicitly refused.
//! On a restore, the existing repair runs before this call returns: it checks the restore and
//! its seed, activates its processor target and then its memory target, reseeds the kernel's
//! random number generator, drops the seed, and finishes the restore with its acknowledgement.
//! The finish hands deferred time work, including detector release, to the time daemon. The
//! returned [`Repaired`] reports the generation ID, online CPUs and memory, and acknowledgement
//! instant. A repair failure is always [`Disposition::MustEnd`], with [`RepairError::status`].
//! Fresh restore network settings are explicitly refused.
//!
//! Each line of `/proc/self/mountinfo` describes one mount with fields separated by spaces: its
//! ID, its parent's ID, the device number of its file system, `MAJ:MIN`, the root of the mount
Expand All @@ -19,6 +25,8 @@
//! kernel writes a space, a tab, a newline, or a backslash within a field as a backslash and
//! three octal digits.

pub(super) mod repair;

use std::ffi::OsString;
use std::fmt;
use std::io;
Expand All @@ -32,6 +40,9 @@ use super::time::{CaptureOutcome, CaptureRequest, PreCaptureError, ScratchPolicy
use crate::guest::parse::parse_decimal;
use crate::protocol::status;

pub use self::repair::{
RangeFault, RepairCause, RepairError, RepairProgress, RepairStage, Repaired,
};
pub use super::activation::{CaptureCause, CaptureStage};

/// The most bytes of the caller's mount table that capture accepts: 1 MiB.
Expand Down Expand Up @@ -62,6 +73,8 @@ pub enum Disks {
pub enum Captured {
/// No snapshot was taken; the stall detectors were restored and this guest may continue.
Continued,
/// A restore started this clone, and its repair completed before capture returned.
Restored(Repaired),
}

/// Whether the caller may resume the guest or must end it.
Expand Down Expand Up @@ -106,6 +119,17 @@ impl CaptureFailure {
}
}

/// Retains the existing repair's detail and the status with which the guest must end.
fn unrepaired(error: RepairError) -> Self {
Self {
stage: CaptureStage::Repair,
disposition: Disposition::MustEnd {
status: error.status(),
},
cause: CaptureCause::Repair(error),
}
}

/// Restores only this call's suppression, retaining a port-release cause and fatal status.
fn cleanup(mut self, system: &mut dyn System, suppressed: bool) -> Self {
if suppressed
Expand Down Expand Up @@ -134,15 +158,18 @@ impl std::error::Error for CaptureFailure {
match &self.cause {
CaptureCause::Io(error) => Some(error),
CaptureCause::Time(error) => Some(error),
CaptureCause::Repair(error) => Some(error),
CaptureCause::Mounted(_) | CaptureCause::Pending => None,
}
}
}

/// Captures a Fresh template without freezing or inspecting the caller's executions.
///
/// The deadline bounds pre-request work only. This call starts no helper, drives no transport,
/// completes no clone repair, and never powers off the VM; failures return its disposition.
/// The deadline bounds pre-request work only. This call drives no transport and never powers off
/// the VM. A restore is repaired inline, and failures return its disposition. Only a report-only
/// finish without a running time daemon starts helper processes, as
/// [`restore_finish`](super::time::TimeAbi::restore_finish) describes.
pub fn capture(
devices: &Devices,
disks: Disks,
Expand All @@ -160,6 +187,7 @@ pub fn capture(
activation::capture_system(Some(deadline)).as_mut(),
devices,
deadline,
repair::live_system(),
)
}

Expand All @@ -168,6 +196,7 @@ fn capture_with(
system: &mut dyn System,
devices: &Devices,
deadline: Instant,
repair_system: Box<dyn repair::RepairSystem>,
) -> Result<Captured, CaptureFailure> {
let mut suppressed = false;
let prepared: Result<_, CaptureFailure> = (|| {
Expand Down Expand Up @@ -203,16 +232,20 @@ fn capture_with(
Ok(Captured::Continued)
}
CaptureOutcome::Restored(restore) => {
let detail = if restore.network().is_some() {
"a Fresh platform restore must not carry network settings"
} else {
"this capture interface does not repair restored guests"
};
Err(CaptureFailure::new(
CaptureStage::Capture,
CaptureCause::Io(io::Error::new(io::ErrorKind::Unsupported, detail)),
)
.cleanup(system, suppressed))
if restore.network().is_some() {
return Err(CaptureFailure::new(
CaptureStage::Capture,
CaptureCause::Io(io::Error::new(
io::ErrorKind::Unsupported,
"a Fresh platform restore must not carry network settings",
)),
)
.cleanup(system, suppressed));
}
repair::Repair::with_system(restore, repair_system)
.run()
.map(Captured::Restored)
.map_err(CaptureFailure::unrepaired)
}
}
}
Expand Down Expand Up @@ -519,4 +552,4 @@ pub(super) fn preparation_failure(error: PreCaptureError) -> PreparationFailure
}

#[cfg(test)]
mod tests;
pub(super) mod tests;
Loading
Loading