Skip to content
Closed
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
2 changes: 1 addition & 1 deletion nvx-rs/README.md

Large diffs are not rendered by default.

75 changes: 41 additions & 34 deletions nvx-rs/src/host/template/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ const SPARSE_WHAT: &str = "scratch device of the clone";
/// scratch image of an upper on one, and the memory target, which [`ClonePlan::new`] checks
/// against the generation, and the settings of the clone's activation that the template does not
/// fix, which the clone's [activation record](ClonePlan::activation) names: the workload's view
/// of its VM, the overlay's mode, and the journal policy of a scratch image.
/// of its VM, the overlay's mode, the journal policy of a scratch image, and the bound of a GPT
/// disk's first partition.
#[derive(Debug, Clone)]
pub struct CloneRequest {
/// The adapter's plan, which [`SandboxPlan::adapter`] makes, of the clone's image, its
/// `distro`, an EROFS layer, over its upper: in RAM, a tmpfs, as
/// `distro`, an EROFS layer or a GPT disk, over its upper: in RAM, a tmpfs, as
/// [`SandboxUpper::RAM`](crate::host::sandbox::SandboxUpper::RAM) plans it, or on the
/// caller's scratch image. Its network is the one of the template's NIC, which every restore
/// of the template attaches, or none for a template without a NIC.
Expand All @@ -98,6 +99,10 @@ pub struct CloneRequest {
/// Whether the filesystem of the scratch image of an upper on one may have a journal. An
/// upper in RAM has none, so the record of a clone in RAM leaves the policy out.
pub journal: Journal,
/// The most bytes that the first partition of a GPT disk may hold, at least 1, which the
/// guest checks as it reads the disk's partition table. An EROFS layer has no partitions, so
/// the record of a clone of one leaves the bound out.
pub first_partition_bound: u64,
}

/// The upper of a clone's plan, with the file of the clone's `scratch` block device, which every
Expand Down Expand Up @@ -129,13 +134,16 @@ pub enum CloneUpper {
/// compares with those that the template recorded. The plan checks each of the clone's inputs as
/// OpenVMM will find it, and holds what the checks opened:
///
/// - The image, the clone's `distro`, must pass the check of its binding,
/// [`DistroBinding::check`], and present the geometry of the template's placeholder, as its
/// key records it: the placeholder's length, and the same logical and physical block sizes,
/// which [`CheckedDistro::geometry`] reads through the checked file. At its path, it must be a
/// regular file, not a link, nor on Windows any other reparse point, that is the file that the
/// check found, that belongs to the current user or to an administrator, and that no one else
/// may change. The plan holds the file that the check opened, for reading alone.
/// - The image, the clone's `distro`, an EROFS layer or a GPT disk, as its binding names it, must
/// pass the check of its binding, [`DistroBinding::check`], and present the geometry of the
/// template's placeholder, as its key records it: the placeholder's length, and the same logical
/// and physical block sizes, which [`CheckedDistro::geometry`] reads through the checked file,
/// whatever the placeholder's format. At its path, it must be a regular file, not a link, nor on
/// Windows any other reparse point, that is the file that the check found, that belongs to the
/// current user or to an administrator, and that no one else may change. The plan holds the
/// file that the check opened, for reading alone. As at a cold launch, the guest reads and
/// checks a GPT disk's partition table, with `nvx::gpt`, before it assembles the root, so the
/// plan reads none of it.
/// - Neither the image nor a scratch image may be a file that the sandbox's store owns, by any
/// path, through a link among its directories, say, or a hard link: the store's transitions
/// replace or remove its files, as a plan replaces the file of the scratch device of a clone in
Expand All @@ -161,8 +169,8 @@ pub enum CloneUpper {
/// - The activation record, which the clone's guest applies in place of the configuration that
/// the template captured, names the restore, with a fresh transaction ID, the clone's image,
/// upper, and settings, the template's network, and the resources that the clone must have,
/// as [`activation`](Self::activation) describes. Its image must be an EROFS layer, and its
/// settings must keep the limits of a record, as [`Configuration::check`] checks them.
/// as [`activation`](Self::activation) describes. Its settings must keep the limits of a
/// record, as [`Configuration::check`] checks them.
/// - The capability is fresh: the plan draws it for the clone's launch, [`launch`](Self::launch).
///
/// The plan's checks read the metadata of the clone's files, never their content.
Expand Down Expand Up @@ -207,6 +215,7 @@ pub enum CloneUpper {
/// },
/// overlay: OverlayMode::UntrustedLowers,
/// journal: Journal::Allowed,
/// first_partition_bound: 64 * 1024,
/// };
/// let deadline = Instant::now() + Duration::from_secs(5);
/// let plan = ClonePlan::new(store, &mut acquired, &request, deadline)?;
Expand Down Expand Up @@ -292,15 +301,13 @@ impl ClonePlan {
///
/// - with [`ErrorKind::Unsupported`] for a request that no clone takes: a plan of the sandbox
/// profile, with workload settings and its shares, [`Evidence::InvalidSettings`]; a plan
/// whose image is not the format and identity that the binding names, as for a binding
/// whose identity is not a UUID in its canonical text, and an image that is a GPT disk, of
/// which this build restores no clone, since a clone's guest assembles its root only from an
/// EROFS layer, [`Evidence::InvalidFormat`]; an image or a scratch image whose path is
/// relative, which OpenVMM would resolve against the directory in which it runs, holds a
/// comma or a semicolon, which OpenVMM's block device options would split, or whose name
/// ends in `.vhd`, `.vhdx`, `.vmgs`, or `.iso`, which OpenVMM would not attach as a raw
/// image, and an upper on a scratch image without the image's binding, or one in RAM with a
/// binding, [`Evidence::InvalidSettings`];
/// whose image is not the format and identity that the binding names, as for a binding whose
/// identity is not a UUID in its canonical text, [`Evidence::InvalidFormat`]; an image or a
/// scratch image whose path is relative, which OpenVMM would resolve against the directory in
/// which it runs, holds a comma or a semicolon, which OpenVMM's block device options would
/// split, or whose name ends in `.vhd`, `.vhdx`, `.vmgs`, or `.iso`, which OpenVMM would not
/// attach as a raw image, and an upper on a scratch image without the image's binding, or one
/// in RAM with a binding, [`Evidence::InvalidSettings`];
/// - with [`ErrorKind::Other`] for a lease of another store, and a lock that is not the one
/// that acquired the lease, before the launch that it reserves began,
/// [`Evidence::HostFailure`];
Expand All @@ -322,7 +329,8 @@ impl ClonePlan {
/// guest's resolver in another form than OpenVMM writes: more than once, without a NIC, or
/// other than as one IP address in its canonical spelling, [`Evidence::MalformedGeneration`];
/// - with [`ErrorKind::Unsupported`] for settings that no activation record holds, as
/// [`Configuration::check`] finds them, such as a hostname that is empty, longer than
/// [`Configuration::check`] finds them, such as a GPT disk's first-partition bound of 0, a
/// hostname that is empty, longer than
/// [`MAX_HOSTNAME_BYTES`](crate::activation::MAX_HOSTNAME_BYTES), or holds a NUL, and a
/// workload identity whose UID or GID is 0, [`Evidence::InvalidActivate`], and with
/// [`ErrorKind::Other`] if the transaction ID of the clone's restore cannot be drawn, the
Expand Down Expand Up @@ -531,16 +539,18 @@ impl ClonePlan {
/// [`ActivationRecord::identity`], the guest's Ready reports.
///
/// The record holds the restore section, with the restore's transaction ID, 16 random bytes
/// that the plan drew, and the template that the [lease](Lease::template) names; the image,
/// an EROFS layer, with the identity of its binding and the UUID that the guest expects of its
/// filesystem, if the adapter's plan names one; the upper, a tmpfs, or the scratch image, with
/// the identity of its binding and the request's journal policy; the request's overlay mode
/// and workload view; no export; the network of the template's NIC, if it has one, with the
/// identity of the policy that OpenVMM enforces there, as the template's key records it, the
/// DNS server with which the template's guest configured its resolver at boot, if the
/// template's [command line](TemplateKey::command_line) names one, unpublished listeners, and
/// the URL of the policy's proxy as the guest sees it, if any; and the template's processors,
/// all online, and the memory target, as the resources that the clone must have.
/// that the plan drew, and the template that the [lease](Lease::template) names; the image, in
/// the format that its binding names, with the identity of its binding: an EROFS layer, with
/// the UUID that the guest expects of its filesystem, or a GPT disk, with the GUID that the
/// guest expects of the disk, each if the adapter's plan names one, and the request's bound of
/// the disk's first partition; the upper, a tmpfs, or the scratch image, with the identity of
/// its binding and the request's journal policy; the request's overlay mode and workload view;
/// no export; the network of the template's NIC, if it has one, with the identity of the
/// policy that OpenVMM enforces there, as the template's key records it, the DNS server with
/// which the template's guest configured its resolver at boot, if the template's
/// [command line](TemplateKey::command_line) names one, unpublished listeners, and the URL of
/// the policy's proxy as the guest sees it, if any; and the template's processors, all
/// online, and the memory target, as the resources that the clone must have.
///
/// The identity of a binding is the SHA-256 digest of a domain of its kind and of its serde
/// form, a few hundred bytes of metadata: the plan reads no content to compute it.
Expand Down Expand Up @@ -612,9 +622,6 @@ fn check_request(request: &CloneRequest) -> Result<(), Fault> {
)),
));
}
if matches!(binding.format(), DistroFormat::Gpt) {
return Err(Fault::new(Evidence::InvalidFormat, record::gpt_refused()));
}
check_disk_path(distro.pure_path(), "image").attribute(Evidence::InvalidSettings)?;
match (adapter.upper().pure_scratch_path(), &request.scratch) {
(Some(path), Some(_)) => {
Expand Down
3 changes: 1 addition & 2 deletions nvx-rs/src/host/template/clone/failure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ pub enum Evidence {
/// have, as the guest's
/// [`ACTIVATION_RESOURCES`](crate::protocol::status::ACTIVATION_RESOURCES) tells.
InvalidTargets,
/// The request's image is of a format that no clone restores, a GPT disk, or is not of the
/// format and identity that its binding names.
/// The request's image is not of the format and identity that its binding names.
InvalidFormat,
/// The request's settings are invalid: a plan that no clone takes, a path that OpenVMM would
/// not open as checked, a network that the template's NIC does not match, an image or a
Expand Down
32 changes: 16 additions & 16 deletions nvx-rs/src/host/template/clone/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ const SCRATCH_DOMAIN: &[u8] = b"nvx scratch binding\0";
///
/// - the restore section: a transaction ID of 16 random bytes, and the template that `lease`
/// names, its generation and the identity of its key;
/// - the image: an EROFS layer, the identity of its binding, and the UUID of its filesystem,
/// which the adapter's plan names, as the binding does;
/// - the image, in the format that its binding names: an EROFS layer, with the identity of its
/// binding and the UUID of its filesystem, or a GPT disk, with the identity of its binding, the
/// GUID of the disk, and the request's bound of its first partition; the adapter's plan names
/// the UUID or the GUID, as the binding does;
/// - the upper: a tmpfs, or the scratch, with the identity of its binding and the request's
/// journal policy;
/// - the request's overlay mode and workload view;
Expand All @@ -53,9 +55,9 @@ const SCRATCH_DOMAIN: &[u8] = b"nvx scratch binding\0";
/// - the expected resources: the template's processors, all of which a template's VM keeps
/// online, and the request's memory target.
///
/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a request whose image
/// is not an EROFS layer, [`Evidence::InvalidFormat`], and for a configuration that no record
/// holds, as [`Configuration::check`] finds it, such as a hostname that is empty or longer than
/// Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for a configuration that
/// no record holds, as [`Configuration::check`] finds it, such as a GPT disk's first-partition
/// bound of 0, a hostname that is empty or longer than
/// [`MAX_HOSTNAME_BYTES`](crate::activation::MAX_HOSTNAME_BYTES), or a workload identity whose
/// UID or GID is 0, [`Evidence::InvalidActivate`]; with [`ErrorKind::Io`](crate::ErrorKind::Io)
/// for a key whose command line names its DNS server in a form that OpenVMM does not write, as
Expand All @@ -67,12 +69,18 @@ pub(super) fn build(
lease: &Lease,
key: &TemplateKey,
) -> std::result::Result<ActivationRecord, Fault> {
let binding = distro_identity(&request.binding).on_host()?;
let expected = request.adapter.distro().image().identity;
let image = match request.binding.format() {
DistroFormat::Erofs => Image::Erofs {
binding: distro_identity(&request.binding).on_host()?,
uuid: request.adapter.distro().image().identity,
binding,
uuid: expected,
},
DistroFormat::Gpt => Image::Gpt {
binding,
disk_guid: expected,
first_partition_bound: request.first_partition_bound,
},
DistroFormat::Gpt => return Err(Fault::new(Evidence::InvalidFormat, gpt_refused())),
};
let upper = match &request.scratch {
None => Upper::Tmpfs,
Expand Down Expand Up @@ -118,14 +126,6 @@ pub(super) fn build(
})
}

/// Returns the error of a request whose image is a GPT disk, which no clone restores yet.
pub(super) fn gpt_refused() -> Error {
Error::unsupported(
"the image of a clone must be an EROFS layer: this build restores no clone of a GPT \
image",
)
}

/// Returns the identity of `binding`, the binding of a clone's image, as the [module](self)
/// describes it.
pub(super) fn distro_identity(binding: &DistroBinding) -> Result<Identity> {
Expand Down
Loading
Loading