diff --git a/doc/acceptance-case-matrix.md b/doc/acceptance-case-matrix.md index 88890c696..52def558e 100644 --- a/doc/acceptance-case-matrix.md +++ b/doc/acceptance-case-matrix.md @@ -478,16 +478,16 @@ this matrix. The README's own code is no doctest. | `nvx-rs/src/activation/mod.rs - activation (line 130)` | kept | | | `nvx-rs/src/exec/mod.rs - exec (line 113)` | retired | The exec engine leaves (SL6.11a) | | `nvx-rs/src/gpt/mod.rs - gpt (line 43)` | kept | | -| `nvx-rs/src/guest/activation.rs - guest::activation (line 157)` | kept | SL6.11c rewrote it on `guest::snapshot::capture`, when its capture preparation and admission fence left (#1087) | -| `nvx-rs/src/guest/activation/state.rs - guest::activation::state::Activation (line 56)` | kept | | +| `nvx-rs/src/guest/activation.rs - guest::activation (line 151)` | kept | SL6.11c rewrote it on `guest::snapshot::capture`, when its capture preparation and admission fence left (#1087) | +| `nvx-rs/src/guest/activation/state.rs - guest::activation::state::Activation (line 54)` | kept | SL6.13b (#1127) rewrote it without the guest's space, which became private: its agent applies the record in its own space | | `nvx-rs/src/guest/cgroup.rs - guest::cgroup (line 36)` | retired | `guest::cgroup` leaves (SL6.11b) | | `nvx-rs/src/guest/entry.rs - guest::entry (line 90)` | retired | `guest::entry` leaves (SL6.11b) | | `nvx-rs/src/guest/exec.rs - guest::exec (line 79)` | retired | `guest::exec` leaves (SL6.11b) | | `nvx-rs/src/guest/exec.rs - guest::exec::Execution::adopt (line 619)` | retired | `guest::exec` leaves (SL6.11b) | -| `nvx-rs/src/guest/gpt.rs - guest::gpt (line 18)` | kept | The guest's space becomes private (#1127), whose PR moves it to the space's unit tests | +| `nvx-rs/src/guest/gpt.rs - guest::gpt (line 18)` | kept | SL6.13b (#1127) moved it to the module's unit tests, `nvx-rs/src/guest/gpt/tests.rs::a_disk_s_steps_compile`, since the guest's space became private | | `nvx-rs/src/guest/mappings/mapper.rs - guest::mappings::mapper (line 107)` | kept | SL6.11d (#1123) moved it to the mapper's unit tests, `nvx-rs/src/guest/mappings/mapper/tests.rs::a_mapper_s_steps_compile`, which map and unmount through the seams that NVX's programs use, since the mapper's `apply` and `teardown`, which only tests used, left | | `nvx-rs/src/guest/mappings/table.rs - guest::mappings::table::MappingTable (line 47)` | kept | | -| `nvx-rs/src/guest/mod.rs - guest (line 34)` | kept | The guest-agent guidelines quote it | +| `nvx-rs/src/guest/mod.rs - guest (line 33)` | kept | The guest-agent guidelines quote it | | `nvx-rs/src/guest/nvxs.rs - guest::nvxs (line 22)` | kept | | | `nvx-rs/src/guest/process.rs - guest::process (line 12)` | retired | `guest::process` leaves (SL6.11b) | | `nvx-rs/src/guest/process.rs - guest::process (line 39)` | retired | `guest::process` leaves (SL6.11b) | diff --git a/doc/guest-agent-guidelines.md b/doc/guest-agent-guidelines.md index 7cb5b9763..f696b46cf 100644 --- a/doc/guest-agent-guidelines.md +++ b/doc/guest-agent-guidelines.md @@ -61,9 +61,8 @@ nvx-init's time daemon runs for the VM's life. NVX's space is internal reference code that nvx-init implements, not public API: the modules that hold it, `guest::root`, `guest::view`, `guest::gpt`, the mapper of `guest::mappings`, and `guest::activation::apply` with its types, -are hidden from the documentation, unstable, and become private to the crate -once nothing outside NVX uses them. Another init builds its own space, or -adapts this code; it builds on no space module of `nvx`. +are private to the crate. Another init builds its own space, or adapts this +code; it builds on no space module of `nvx`. The space's inputs are public contracts: diff --git a/nvx-rs/README.md b/nvx-rs/README.md index d29647494..981d6f9f4 100644 --- a/nvx-rs/README.md +++ b/nvx-rs/README.md @@ -130,9 +130,6 @@ changes only when the module's purpose does. | [`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 of each clone that its capture repaired | | [`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 assembly, shares, export, and teardown | -| [`nvx::guest::view`](#nvxguestview) | The VM-wide hostname, machine ID, and single-server resolver, whose binds the layered root owns | -| [`nvx::guest::gpt`](#nvxguestgpt) | `read_disk`: the partition table of a GPT disk, read and checked through its block device | GPT lower layers use `noload,errors=remount-ro`: the kernel neither loads nor replays their journals, because even a journal whose file system does not @@ -205,8 +202,8 @@ it, `virtfs_tag=` the device's tag, `virtfs_mode=` `ro` or `rw`, an `HostExport::parse` reads them as the guest's init splits the command line: all four tokens or none, each once and in its form, with the checks of NVX's shell init, and no `nvx_share=` token, which binds a live share. It returns the -`HostExport`, the device's tag and mode, which -`nvx::guest::root::Root::mount_export` mounts, or `None`. +`HostExport`, the device's tag and mode, which nvx-init's space mounts, or +`None`. `tests/fixtures/export-tokens.json` holds its cases. ### `nvx::protocol::nvxs` @@ -1092,12 +1089,11 @@ The programs that run in Linux guests; see [Guest binaries](#guest-binaries). ### `nvx::guest::mappings` The mapping values, `Mapping`, `Access`, `Kind`, `InvalidMapping`, and -`MAX_PATH_BYTES`, and `MappingTable` are public. The mapper, in the hidden -`mapper` module, whose items keep their paths here, hidden too, is NVX's -internal reference code of the guest's space: unstable, and crate-private once -nothing outside NVX uses it. +`MAX_PATH_BYTES`, and `MappingTable` are public. The mapper, in the `mapper` +module, is part of the guest's space, nvx-init's internal reference code, +private to the crate, which re-exports the items that NVX's programs use here. -`Mapper`: applies host mappings for any guest init or agent beneath the +`Mapper`: applies host mappings beneath the descriptor of a `TargetRoot`, the root of a mount of a whole file system, which `statmount` tells from a bind of a directory below it, such as a layered root's overlay or a guest's `/`, from a `SourceRoot`, such as the guest's mount of the @@ -1137,7 +1133,7 @@ decode, holds no entry, does not continue the table, ends past the announcement, or maps the export's root, which an empty source names, as a `TableError::Refused` at the entry that it expects next, which changes nothing. It hands the part's mappings to the caller, which applies them beneath the root -of its choice, such as through a `Mapper`; the first that +of its choice, such as through its own mapper; the first that fails latches the table, which reports it as a `TableError::Failed` with its entry, and refuses every later part. The table is complete once every announced mapping is applied, and none failed, and the caller's teardown tears it down. @@ -1145,8 +1141,8 @@ mapping is applied, and none failed, and the caller's teardown tears it down. Linux 6.8 or later. NVX's managed agent applies its host mapping table through a `MappingTable` and a mapper, beneath `/` in direct mode; the table refuses an empty source, since the export is an aggregate, whose root lists its children, -and NVX's hosts, whose exports are all aggregates, send none. A layered `Root` -mounts its live shares through a mapper. +and NVX's hosts, whose exports are all aggregates, send none. nvx-init's +layered root mounts its live shares through a mapper. ### `nvx::guest::nvxs` @@ -1276,44 +1272,41 @@ accepts only a clone's, with a restore section, that expects the CPU count and the memory size that the repair reports, before any side effect, while an Activate whose record the adapter's framing refused unread, as longer than a record can be, fails as a record that long does; the adapter applies the record -with `apply`, as at a cold boot: it assembles the root as `root::assemble` does, -with the record's image, an EROFS image whose UUID it checks if the record -expects one, or a GPT disk whose GUID it checks if the record expects one, its -upper, a tmpfs or the scratch with the record's journal policy, and its overlay -mode, and with the `RootTemplate` that the guest's own configuration holds, -which its template captured: the runtime directory, the block devices, the -timeout, the host filesystem device's virtio-fs tag, and the gateway of the -template's NIC, if it has one, but waits for no device past the activation's -deadline, which the adapter passes, so that the application ends by then. -`apply`, `RootTemplate`, `Applied`, `ApplyError`, `AppliedTeardownError`, and -`NetworkError` are hidden: NVX's internal reference code of the guest's space, -unstable, and crate-private once nothing outside NVX uses it. +in its own space, by the activation's deadline. + +nvx-init applies it with its own space, NVX's internal reference code, private +to the crate, as at a cold boot: it assembles the root with the record's image, +an EROFS image whose UUID it checks if the record expects one, or a GPT disk +whose GUID it checks if the record expects one, its upper, a tmpfs or the +scratch with the record's journal policy, and its overlay mode, and with the +inputs that the guest's own configuration holds, which its template captured: +the runtime directory, the block devices, the timeout, the host filesystem +device's virtio-fs tag, and the gateway of the template's NIC, if it has one, +but waits for no device past the activation's deadline, so that the +application ends by then. For a GPT disk, it first asks the kernel to reread the disk's partition table with `BLKRRPART`, since the kernel created the block devices of partitions for the template's placeholder. Whatever the image, it requires a fresh upper, whose `upper` and `work` directories are missing or empty, in a bounded check that reads no file, while a cold sandbox's scratch keeps its upper across boots. It -uses no execution identity from the record, which carries none: each execution names its -own identity, which `Entry::check` checks, as in a sandbox without a workload -identity at a cold boot. With the record's export, it mounts the host export through that -filesystem device with `Root::mount_export`, read-write, at `hostfs/root` in the -runtime directory, outside the root, since the record names no mode and OpenVMM -limits each child to its own access. It applies the record's VM-wide view with -`view::apply`, passing the record's own DNS server. The view sets the hostname -and, when requested, generates a machine ID after repair's reseed. The root -owns the machine-ID and resolver binds, including those made before a later -failure. Application starts no announcer and no other process. It returns -the `Applied` root and workload, from which `take_export` hands the adapter the -export's `SourceRoot`, to map the host's mapping table from once the clone is -ready, `proxy` the network's proxy endpoint, which the adapter tells its -workloads as at a cold boot, and `publication` and `gateway`, from which its -caller starts and supervises ingress. Its `teardown` closes the export's root, -then tears the root down with its view binds. A switched root unmounts -nothing. A failure returns an -`ApplyError`, `Export` or `Network` among them, after which nothing that it -mounted or bound stays mounted unless the error reports the unmounts that -failed. +uses no execution identity from the record, which carries none: each execution +names its own identity, as in a sandbox without a workload identity at a cold +boot. With the record's export, it mounts the host export through that +filesystem device, read-write, at `hostfs/root` in the runtime directory, +outside the root, since the record names no mode and OpenVMM limits each child +to its own access. It applies the record's VM-wide view, with the record's own +DNS server. The view sets the hostname and, when requested, generates a machine +ID after repair's reseed. The root owns the machine-ID and resolver binds, +including those made before a later failure. Application starts no announcer +and no other process. nvx-init keeps the root and workload that it activated: +the export's root, from which it maps the host's mapping table once the clone +is ready, the network's proxy endpoint, which it tells its workloads as at a +cold boot, and the publication and gateway, from which it starts and supervises +ingress. Their teardown closes the export's root, then tears the root down with +its view binds. A switched root unmounts nothing. After a failure, of the +export or the network among others, nothing that the application mounted or +bound stays mounted unless the failure reports the unmounts that failed. Once the adapter passed the activation what the session received while the application ran, which came before Ready, `Activation::complete` returns the @@ -1341,130 +1334,6 @@ it, in the caller's process. A `Seed` keeps its bytes in one allocation, which no move copies, zeroes them with volatile writes when it is dropped, can't be cloned, and formats none of them. `nvx-reseed` runs them. -### `nvx::guest::root` - -The operations of a layered root, for a guest init or agent, whether or not it -is PID 1. The module is hidden: NVX's internal reference code of the guest's -space, unstable, and crate-private once nothing outside NVX uses it. - -`assemble`: the `Assembly`'s block devices, found by their MMIO windows, checked -against their roles' access modes and their expected sizes, and flushed, and the -`Root` that it mounts, at a runtime directory, a canonical path that no symbolic -link leads through, that it holds exclusively until its teardown, where nothing -is mounted already, once it has checked everything: the read-only layers of the -`Image` that the caller names, which it never probes: an EROFS image, whose -expected `nvx::uuid::Uuid` it checks in its superblock, or a GPT disk whose -partition table it reads and checks with `nvx::guest::gpt::read_disk`, against -the caller's `nvx::gpt::Expected` values, whose first partition it reads, up to -the caller's bound, as opaque bytes that it never mounts, and whose later -partitions, at least one, hold ext4 layers that it mounts `ro,nosuid,nodev` with -`errors=remount-ro`, so that an error that the kernel finds in a layer fails the -operation that found it, rather than panic the guest, as the layer's superblock -may ask, from the block devices that the kernel created when it found the disk, -once their superblocks show that no journal needs a recovery, which their -read-only device can't take, partition 2's at the overlay's base and the last -partition's at its top, under an overlay mounted `nosuid,nodev` with the one -option set of the caller's `OverlayMode`, `metacopy=on,xino=on` or, for lowers -that may carry untrusted overlay xattrs, `metacopy=off,redirect_dir=nofollow`, -with no fallback, whose upper and work directories are on the `Upper`: an ext4 -scratch mounted `nosuid,nodev`, whose superblock it checks for a journal that -the caller's `Journal` policy forbids, or a tmpfs mounted `nosuid,nodev` with -`size=N%` for the caller's cap of N percent of the guest's memory, which it -refuses outside 1 to 90 before anything changes, or else at the kernel's -default size. It refuses overlay options longer than -`MOUNT_OPTIONS_LIMIT`, the most that a mount passes, which, below `/run/nvx`, -fit up to 195 layers of a GPT disk, and an overlay that the kernel's mount table -shows read-only or without the mode's options in effect, since the kernel mounts -one so, rather than fail, when its upper can't hold its work directory or its -extended attributes. - -`Root::mount_share` and `Root::mount_aggregate`: virtio-fs live shares at -targets in the root that it checks, against the targets that `protocol::share` -reserves among others, with one aggregate per root, which it attaches through -its `mappings::Mapper` beneath the overlay, with NVX's container root's reserved -targets: a share's device mounted out of the root, then moved onto its target, -and a copy of each child's entry of the aggregate. - -`Root::mount_export`: the host export, the aggregate device of -`protocol::export`, one per root, mounted `nosuid,nodev` with the access of its -`ExportMode` at `hostfs/root` in the runtime directory, out of the root, below a -directory that only root may enter, whose root it returns as the `SourceRoot` of -the caller's mappings, which the caller's own mapper applies. A share, a child, -or the export that fails latches the root. - -`Root::mount_runtime`: the runtime's file systems, which an init mounts once -for its VM, before the root's first live share, after it makes every -mount of the root's tree `nosuid,nodev`: procfs at `/proc`, sysfs at `/sys`, -read-only, a tmpfs at `/dev` with an execution's device nodes and links, devpts -at `/dev/pts`, and tmpfs at `/dev/shm` and `/run`, with an execution's options, -then the time status's directory, when the caller names one, bound read-only at -`/run/nvx/time`. It refuses a mount point that leads through a symbolic link or -where something is mounted already, and, before it changes anything, a call -while the root holds the runtime's mounts, whose `/dev` and `/dev/pts` a second -restriction would make `nodev`, or a live share, since the `/run` -tmpfs would hide one beneath `/run` from the teardown. A call that fails -unmounts what it mounted. A later share beneath `/run` is on that -tmpfs, which the root's mapper owns, as a guest's mapper owns its `/tmp`. -Each attachment is checked at its named target before it is recorded. A runtime -mount whose attachment fails and which cannot be detached keeps its mount -descriptor, so rollback and teardown can retry cleanup without trusting its -original path. - -`Root::switch`: the switch into the root, which an init makes once for its VM: -it moves the root's mount onto `/`, then makes the root the caller's root -directory with `chroot`, and its working directory with `chdir`. Once the mount -is on `/`, the root has switched for good: a second switch and the teardown are -refused, and dropping the root unmounts nothing, while descriptors opened before -the switch, such as a `TargetRoot` and a `SourceRoot`, still map. A move that -fails leaves the root where it was. - -`Root::teardown`: the view's binds, then the shares in reverse order, a runtime mount whose -attachment failed and which could not be detached, then the mounts of an -aggregate, of the export, and of the runtime, the later first, the overlay, -the layers from the top down, and the upper, with every failed sync or unmount -reported. An -ext4 scratch's file system is synced before its unmount, since a mount namespace -that copied the root's mounts keeps them, unsynced, after this one unmounts -them, and a scratch whose sync fails stays mounted. - -Each failure is a typed `Error`, or a `SwitchError` for the switch. The -operations collect no child's status, change the caller's root only by the -switch and never its credentials, start no async runtime, and never power the VM -off. - -### `nvx::guest::view` - -Hidden, unstable internal reference code of the guest's space, like `root`. -`apply(&mut Root, &activation::View, dns)` sets the current UTS namespace's -hostname from the view's bytes. Image mode leaves `/etc/machine-id` untouched; -generated mode writes a new UUID at `MACHINE_ID_FILE` in the runtime directory -and binds it read-only over the image's ID. Optional DNS writes one server in -`RESOLVER_FILE` and binds it read-only over `/etc/resolv.conf`. -`nvx::guest::activation` re-exports both file constants. - -The root owns every bind, including an attachment left by a failed call. -Teardown before the switch removes them; after the switch teardown refuses -and drop unmounts nothing. A view is attempted once per root, before its -switch. Refusals return the crate's `Error` with kind `Unsupported`, and -system failures with kind `Io`; an error does not restore the old hostname -or remove generated files. The activation record's hostname limit and -machine-ID modes are unchanged. - -### `nvx::guest::gpt` - -Hidden, like `nvx::guest::root`, which reads its disks through it: NVX's -internal reference code of the guest's space, unstable, and crate-private once -nothing outside NVX uses it. - -`read_disk`: the partition table of a GPT disk, read and checked with -`nvx::gpt`'s `Disk::read` through the disk's block device, which the caller -opened, at the logical block size and size that `BLKSSZGET` and `BLKGETSIZE64` -report, with the caller's `Expected` values, such as the disk GUID that a boot -parameter names. It only reads the device, at explicit offsets, so it never -moves the file's position. It neither compares the partitions that the kernel -created with the table nor asks the kernel to reread the table. Each failure is -a typed `DeviceError`. - ## Egress policy files `nvx::host::network::policy` compiles the egress policy files that @@ -2544,18 +2413,19 @@ The `guest` feature adds the programs that run in NVX's Linux guests: the Linux images ship all ten at their established `/sbin/nvx-*` paths. The module's libraries are not programs: `terminal` opens the terminal on which an agent serves its session, and `mappings` describes host mappings and keeps the -account of a host's mapping table. NVX's own guest space is internal reference -code, hidden, unstable, and crate-private once nothing outside NVX uses it: -the mapper of `mappings` mounts host mappings beneath the descriptors of the -roots that its caller names, and `root` assembles a layered root, mounts live -shares in it through the mapper, and tears it down. +account of a host's mapping table. NVX's own guest space is nvx-init's internal +reference code, private to the crate, as [nvx-init's space](#nvx-inits-space) +tells: the mapper of `mappings` mounts host mappings beneath the descriptors of +the roots that its caller names, and `root` assembles a layered root, mounts +live shares in it through the mapper, and tears it down. `nvx-managed-agent` opens its control terminal and runs direct workloads through nvx-init's plain-process core: each child resets its priority, drops its credentials and capabilities, and enters its working directory before exec. Each execution ends its process group, including on normal completion, without cgroups, namespaces, limits, or a privilege-dropping helper. It applies its -host mapping table through `mappings`, from its mount of the host's export, -beneath `/` in direct mode, and any other guest init or agent can use them too. +host mapping table through the table of `mappings` and nvx-init's mapper, from +its mount of the host's export, beneath `/` in direct mode, and any other guest +init or agent can use the table and the mapping values too. Any guest init or agent can also run the boot steps of `nvx-time` and the steps of `nvx-reseed` in its own process, through the `nvx::guest::time` and `nvx::guest::reseed` calls, on which both programs run, @@ -2564,7 +2434,7 @@ and a template's guest captures before its root's assembly through and, in each clone, repairs its restore: it activates the restore's memory target, reseeds the kernel's random number generator, and finishes the restore, as `nvx-snapshot` does. -Nor is the hidden `gpt` module a program: it reads the partition table of a GPT +Nor is the private `gpt` module a program: it reads the partition table of a GPT disk's block device. | Binary | Program | @@ -2627,6 +2497,134 @@ which `GuestRustBuildConstants` in [`build_constants.py`](../scripts/nvx_tools/build_constants.py) then has to select. +### nvx-init's space + +nvx-init's space, the root that it assembles and what it mounts in it, is NVX's +internal reference code, private to the crate: `nvx::guest::root`, +`nvx::guest::view`, `nvx::guest::gpt`, the mapper of `nvx::guest::mappings`, and +the application of `nvx::guest::activation`, which nvx-init and NVX's managed +agent use. The +[guest-agent guidelines](../doc/guest-agent-guidelines.md#build-the-space) give +the rules that it keeps, which another init's space may adapt. + +#### `nvx::guest::root` + +The operations of a layered root, whether or not their caller is PID 1. + +`assemble`: the `Assembly`'s block devices, found by their MMIO windows, checked +against their roles' access modes and their expected sizes, and flushed, and the +`Root` that it mounts, at a runtime directory, a canonical path that no symbolic +link leads through, that it holds exclusively until its teardown, where nothing +is mounted already, once it has checked everything: the read-only layers of the +`Image` that the caller names, which it never probes: an EROFS image, whose +expected `nvx::uuid::Uuid` it checks in its superblock, or a GPT disk whose +partition table it reads and checks with `nvx::guest::gpt::read_disk`, against +the caller's `nvx::gpt::Expected` values, whose first partition it reads, up to +the caller's bound, as opaque bytes that it never mounts, and whose later +partitions, at least one, hold ext4 layers that it mounts `ro,nosuid,nodev` with +`errors=remount-ro`, so that an error that the kernel finds in a layer fails the +operation that found it, rather than panic the guest, as the layer's superblock +may ask, from the block devices that the kernel created when it found the disk, +once their superblocks show that no journal needs a recovery, which their +read-only device can't take, partition 2's at the overlay's base and the last +partition's at its top, under an overlay mounted `nosuid,nodev` with the one +option set of the caller's `OverlayMode`, `metacopy=on,xino=on` or, for lowers +that may carry untrusted overlay xattrs, `metacopy=off,redirect_dir=nofollow`, +with no fallback, whose upper and work directories are on the `Upper`: an ext4 +scratch mounted `nosuid,nodev`, whose superblock it checks for a journal that +the caller's `Journal` policy forbids, or a tmpfs mounted `nosuid,nodev` with +`size=N%` for the caller's cap of N percent of the guest's memory, which it +refuses outside 1 to 90 before anything changes, or else at the kernel's +default size. It refuses overlay options longer than +`MOUNT_OPTIONS_LIMIT`, the most that a mount passes, which, below `/run/nvx`, +fit up to 195 layers of a GPT disk, and an overlay that the kernel's mount table +shows read-only or without the mode's options in effect, since the kernel mounts +one so, rather than fail, when its upper can't hold its work directory or its +extended attributes. + +`Root::mount_share` and `Root::mount_aggregate`: virtio-fs live shares at +targets in the root that it checks, against the targets that `protocol::share` +reserves among others, with one aggregate per root, which it attaches through +its `mappings::Mapper` beneath the overlay, with NVX's container root's reserved +targets: a share's device mounted out of the root, then moved onto its target, +and a copy of each child's entry of the aggregate. + +`Root::mount_export`: the host export, the aggregate device of +`protocol::export`, one per root, mounted `nosuid,nodev` with the access of its +`ExportMode` at `hostfs/root` in the runtime directory, out of the root, below a +directory that only root may enter, whose root it returns as the `SourceRoot` of +the caller's mappings, which the caller's own mapper applies. A share, a child, +or the export that fails latches the root. + +`Root::mount_runtime`: the runtime's file systems, which an init mounts once +for its VM, before the root's first live share, after it makes every +mount of the root's tree `nosuid,nodev`: procfs at `/proc`, sysfs at `/sys`, +read-only, a tmpfs at `/dev` with an execution's device nodes and links, devpts +at `/dev/pts`, and tmpfs at `/dev/shm` and `/run`, with an execution's options, +then the time status's directory, when the caller names one, bound read-only at +`/run/nvx/time`. It refuses a mount point that leads through a symbolic link or +where something is mounted already, and, before it changes anything, a call +while the root holds the runtime's mounts, whose `/dev` and `/dev/pts` a second +restriction would make `nodev`, or a live share, since the `/run` +tmpfs would hide one beneath `/run` from the teardown. A call that fails +unmounts what it mounted. A later share beneath `/run` is on that +tmpfs, which the root's mapper owns, as a guest's mapper owns its `/tmp`. +Each attachment is checked at its named target before it is recorded. A runtime +mount whose attachment fails and which cannot be detached keeps its mount +descriptor, so rollback and teardown can retry cleanup without trusting its +original path. + +`Root::switch`: the switch into the root, which an init makes once for its VM: +it moves the root's mount onto `/`, then makes the root the caller's root +directory with `chroot`, and its working directory with `chdir`. Once the mount +is on `/`, the root has switched for good: a second switch and the teardown are +refused, and dropping the root unmounts nothing, while descriptors opened before +the switch, such as a `TargetRoot` and a `SourceRoot`, still map. A move that +fails leaves the root where it was. + +`Root::teardown`: the view's binds, then the shares in reverse order, a runtime mount whose +attachment failed and which could not be detached, then the mounts of an +aggregate, of the export, and of the runtime, the later first, the overlay, +the layers from the top down, and the upper, with every failed sync or unmount +reported. An +ext4 scratch's file system is synced before its unmount, since a mount namespace +that copied the root's mounts keeps them, unsynced, after this one unmounts +them, and a scratch whose sync fails stays mounted. + +Each failure is a typed `Error`, or a `SwitchError` for the switch. The +operations collect no child's status, change the caller's root only by the +switch and never its credentials, start no async runtime, and never power the VM +off. + +#### `nvx::guest::view` + +`apply(&mut Root, &activation::View, dns)` sets the current UTS namespace's +hostname from the view's bytes. Image mode leaves `/etc/machine-id` untouched; +generated mode writes a new UUID at `MACHINE_ID_FILE` in the runtime directory +and binds it read-only over the image's ID. Optional DNS writes one server in +`RESOLVER_FILE` and binds it read-only over `/etc/resolv.conf`. +`nvx::guest::activation` re-exports both file constants. + +The root owns every bind, including an attachment left by a failed call. +Teardown before the switch removes them; after the switch teardown refuses +and drop unmounts nothing. A view is attempted once per root, before its +switch. Refusals return the crate's `Error` with kind `Unsupported`, and +system failures with kind `Io`; an error does not restore the old hostname +or remove generated files. The activation record's hostname limit and +machine-ID modes are unchanged. + +#### `nvx::guest::gpt` + +`read_disk`, through which `nvx::guest::root` reads its disks: the partition +table of a GPT disk, read and checked with +`nvx::gpt`'s `Disk::read` through the disk's block device, which the caller +opened, at the logical block size and size that `BLKSSZGET` and `BLKGETSIZE64` +report, with the caller's `Expected` values, such as the disk GUID that a boot +parameter names. It only reads the device, at explicit offsets, so it never +moves the file's position. It neither compares the partitions that the kernel +created with the table nor asks the kernel to reread the table. Each failure is +a typed `DeviceError`. + ## Testing ```bash diff --git a/nvx-rs/public-api/linux.txt b/nvx-rs/public-api/linux.txt index d8685673f..cce9ed98a 100644 --- a/nvx-rs/public-api/linux.txt +++ b/nvx-rs/public-api/linux.txt @@ -378,29 +378,6 @@ variant nvx::guest::activation::ActivationState::Failed variant nvx::guest::activation::ActivationState::Ready variant nvx::guest::activation::ActivationState::Restricted variant nvx::guest::activation::ActivationState::Started -struct nvx::guest::activation::Applied (hidden) -method nvx::guest::activation::Applied::gateway (hidden) -method nvx::guest::activation::Applied::proxy (hidden) -method nvx::guest::activation::Applied::publication (hidden) -method nvx::guest::activation::Applied::root (hidden) -method nvx::guest::activation::Applied::root_mut (hidden) -method nvx::guest::activation::Applied::take_export (hidden) -method nvx::guest::activation::Applied::teardown (hidden) -struct nvx::guest::activation::AppliedTeardownError (hidden) -enum nvx::guest::activation::ApplyError (hidden) -variant nvx::guest::activation::ApplyError::Assembly (hidden) -field nvx::guest::activation::ApplyError::Assembly::0 (hidden) -variant nvx::guest::activation::ApplyError::Export (hidden) -field nvx::guest::activation::ApplyError::Export::error (hidden) -field nvx::guest::activation::ApplyError::Export::teardown (hidden) -variant nvx::guest::activation::ApplyError::Gpt (hidden) -field nvx::guest::activation::ApplyError::Gpt::0 (hidden) -variant nvx::guest::activation::ApplyError::Network (hidden) -field nvx::guest::activation::ApplyError::Network::error (hidden) -field nvx::guest::activation::ApplyError::Network::teardown (hidden) -variant nvx::guest::activation::ApplyError::View (hidden) -field nvx::guest::activation::ApplyError::View::error (hidden) -field nvx::guest::activation::ApplyError::View::teardown (hidden) enum nvx::guest::activation::CaptureCause variant nvx::guest::activation::CaptureCause::Io field nvx::guest::activation::CaptureCause::Io::0 @@ -426,12 +403,6 @@ const nvx::guest::activation::MOUNT_TABLE_LIMIT struct nvx::guest::activation::Mounted method nvx::guest::activation::Mounted::mount_point method nvx::guest::activation::Mounted::origin -enum nvx::guest::activation::NetworkError (hidden) -variant nvx::guest::activation::NetworkError::Bind (hidden) -field nvx::guest::activation::NetworkError::Bind::0 (hidden) -variant nvx::guest::activation::NetworkError::Gateway (hidden) -variant nvx::guest::activation::NetworkError::Write (hidden) -field nvx::guest::activation::NetworkError::Write::0 (hidden) enum nvx::guest::activation::Origin variant nvx::guest::activation::Origin::Distro variant nvx::guest::activation::Origin::Filesystem @@ -495,14 +466,6 @@ method nvx::guest::activation::Repaired::acknowledged_at method nvx::guest::activation::Repaired::cpus method nvx::guest::activation::Repaired::generation_id method nvx::guest::activation::Repaired::memory -struct nvx::guest::activation::RootTemplate (hidden) -field nvx::guest::activation::RootTemplate::distro (hidden) -field nvx::guest::activation::RootTemplate::filesystem_tag (hidden) -field nvx::guest::activation::RootTemplate::gateway (hidden) -field nvx::guest::activation::RootTemplate::runtime (hidden) -field nvx::guest::activation::RootTemplate::scratch (hidden) -field nvx::guest::activation::RootTemplate::timeout (hidden) -fn nvx::guest::activation::apply (hidden) mod nvx::guest::clone struct nvx::guest::clone::Activation method nvx::guest::clone::Activation::complete @@ -559,29 +522,6 @@ variant nvx::guest::clone::ActivationState::Failed variant nvx::guest::clone::ActivationState::Ready variant nvx::guest::clone::ActivationState::Restricted variant nvx::guest::clone::ActivationState::Started -struct nvx::guest::clone::Applied (hidden) -method nvx::guest::clone::Applied::gateway (hidden) -method nvx::guest::clone::Applied::proxy (hidden) -method nvx::guest::clone::Applied::publication (hidden) -method nvx::guest::clone::Applied::root (hidden) -method nvx::guest::clone::Applied::root_mut (hidden) -method nvx::guest::clone::Applied::take_export (hidden) -method nvx::guest::clone::Applied::teardown (hidden) -struct nvx::guest::clone::AppliedTeardownError (hidden) -enum nvx::guest::clone::ApplyError (hidden) -variant nvx::guest::clone::ApplyError::Assembly (hidden) -field nvx::guest::clone::ApplyError::Assembly::0 (hidden) -variant nvx::guest::clone::ApplyError::Export (hidden) -field nvx::guest::clone::ApplyError::Export::error (hidden) -field nvx::guest::clone::ApplyError::Export::teardown (hidden) -variant nvx::guest::clone::ApplyError::Gpt (hidden) -field nvx::guest::clone::ApplyError::Gpt::0 (hidden) -variant nvx::guest::clone::ApplyError::Network (hidden) -field nvx::guest::clone::ApplyError::Network::error (hidden) -field nvx::guest::clone::ApplyError::Network::teardown (hidden) -variant nvx::guest::clone::ApplyError::View (hidden) -field nvx::guest::clone::ApplyError::View::error (hidden) -field nvx::guest::clone::ApplyError::View::teardown (hidden) enum nvx::guest::clone::CaptureCause variant nvx::guest::clone::CaptureCause::Io field nvx::guest::clone::CaptureCause::Io::0 @@ -607,12 +547,6 @@ const nvx::guest::clone::MOUNT_TABLE_LIMIT struct nvx::guest::clone::Mounted method nvx::guest::clone::Mounted::mount_point method nvx::guest::clone::Mounted::origin -enum nvx::guest::clone::NetworkError (hidden) -variant nvx::guest::clone::NetworkError::Bind (hidden) -field nvx::guest::clone::NetworkError::Bind::0 (hidden) -variant nvx::guest::clone::NetworkError::Gateway (hidden) -variant nvx::guest::clone::NetworkError::Write (hidden) -field nvx::guest::clone::NetworkError::Write::0 (hidden) enum nvx::guest::clone::Origin variant nvx::guest::clone::Origin::Distro variant nvx::guest::clone::Origin::Filesystem @@ -676,29 +610,12 @@ method nvx::guest::clone::Repaired::acknowledged_at method nvx::guest::clone::Repaired::cpus method nvx::guest::clone::Repaired::generation_id method nvx::guest::clone::Repaired::memory -struct nvx::guest::clone::RootTemplate (hidden) -field nvx::guest::clone::RootTemplate::distro (hidden) -field nvx::guest::clone::RootTemplate::filesystem_tag (hidden) -field nvx::guest::clone::RootTemplate::gateway (hidden) -field nvx::guest::clone::RootTemplate::runtime (hidden) -field nvx::guest::clone::RootTemplate::scratch (hidden) -field nvx::guest::clone::RootTemplate::timeout (hidden) -fn nvx::guest::clone::apply (hidden) mod nvx::guest::console_pending (hidden) fn nvx::guest::console_pending::main (hidden) mod nvx::guest::device_io (hidden) fn nvx::guest::device_io::main (hidden) mod nvx::guest::exit (hidden) fn nvx::guest::exit::main (hidden) -mod nvx::guest::gpt (hidden) -enum nvx::guest::gpt::DeviceError (hidden) -variant nvx::guest::gpt::DeviceError::LogicalBlockSize (hidden) -field nvx::guest::gpt::DeviceError::LogicalBlockSize::0 (hidden) -variant nvx::guest::gpt::DeviceError::Size (hidden) -field nvx::guest::gpt::DeviceError::Size::0 (hidden) -variant nvx::guest::gpt::DeviceError::Table (hidden) -field nvx::guest::gpt::DeviceError::Table::0 (hidden) -fn nvx::guest::gpt::read_disk (hidden) mod nvx::guest::ingress (hidden) fn nvx::guest::ingress::main (hidden) mod nvx::guest::init (hidden) @@ -709,9 +626,6 @@ mod nvx::guest::mappings enum nvx::guest::mappings::Access variant nvx::guest::mappings::Access::ReadOnly variant nvx::guest::mappings::Access::ReadWrite -struct nvx::guest::mappings::ApplyError (hidden) -method nvx::guest::mappings::ApplyError::entry (hidden) -method nvx::guest::mappings::ApplyError::error (hidden) enum nvx::guest::mappings::InvalidMapping variant nvx::guest::mappings::InvalidMapping::Source variant nvx::guest::mappings::InvalidMapping::Target @@ -719,9 +633,6 @@ enum nvx::guest::mappings::Kind variant nvx::guest::mappings::Kind::Directory variant nvx::guest::mappings::Kind::File const nvx::guest::mappings::MAX_PATH_BYTES -struct nvx::guest::mappings::Mapper (hidden) -method nvx::guest::mappings::Mapper::new (hidden) -method nvx::guest::mappings::Mapper::targets (hidden) struct nvx::guest::mappings::Mapping method nvx::guest::mappings::Mapping::access method nvx::guest::mappings::Mapping::kind @@ -729,26 +640,6 @@ method nvx::guest::mappings::Mapping::new method nvx::guest::mappings::Mapping::source method nvx::guest::mappings::Mapping::target method nvx::guest::mappings::Mapping::with_kind -enum nvx::guest::mappings::MappingError (hidden) -variant nvx::guest::mappings::MappingError::Foreign (hidden) -field nvx::guest::mappings::MappingError::Foreign::path (hidden) -variant nvx::guest::mappings::MappingError::Latched (hidden) -variant nvx::guest::mappings::MappingError::Link (hidden) -field nvx::guest::mappings::MappingError::Link::path (hidden) -variant nvx::guest::mappings::MappingError::MountPoint (hidden) -variant nvx::guest::mappings::MappingError::Moved (hidden) -field nvx::guest::mappings::MappingError::Moved::mounted (hidden) -variant nvx::guest::mappings::MappingError::NotDirectory (hidden) -field nvx::guest::mappings::MappingError::NotDirectory::path (hidden) -variant nvx::guest::mappings::MappingError::Overlap (hidden) -field nvx::guest::mappings::MappingError::Overlap::mapped (hidden) -variant nvx::guest::mappings::MappingError::Reserved (hidden) -variant nvx::guest::mappings::MappingError::SourceKind (hidden) -variant nvx::guest::mappings::MappingError::System (hidden) -field nvx::guest::mappings::MappingError::System::error (hidden) -field nvx::guest::mappings::MappingError::System::step (hidden) -variant nvx::guest::mappings::MappingError::TargetKind (hidden) -method nvx::guest::mappings::MappingError::raw_os_error (hidden) struct nvx::guest::mappings::MappingTable method nvx::guest::mappings::MappingTable::announced method nvx::guest::mappings::MappingTable::applied @@ -774,21 +665,6 @@ field nvx::guest::mappings::Refusal::Mapping::error variant nvx::guest::mappings::Refusal::Order field nvx::guest::mappings::Refusal::Order::expected field nvx::guest::mappings::Refusal::Order::first -struct nvx::guest::mappings::Reserved (hidden) -method nvx::guest::mappings::Reserved::container_root (hidden) -method nvx::guest::mappings::Reserved::new (hidden) -struct nvx::guest::mappings::SourceRoot (hidden) -method nvx::guest::mappings::SourceRoot::open (hidden) -enum nvx::guest::mappings::Step (hidden) -variant nvx::guest::mappings::Step::Attach (hidden) -variant nvx::guest::mappings::Step::Clone (hidden) -variant nvx::guest::mappings::Step::Create (hidden) -variant nvx::guest::mappings::Step::Locate (hidden) -variant nvx::guest::mappings::Step::Restrict (hidden) -variant nvx::guest::mappings::Step::Source (hidden) -variant nvx::guest::mappings::Step::Status (hidden) -variant nvx::guest::mappings::Step::Unmount (hidden) -variant nvx::guest::mappings::Step::Walk (hidden) enum nvx::guest::mappings::TableError variant nvx::guest::mappings::TableError::Failed field nvx::guest::mappings::TableError::Failed::entry @@ -797,64 +673,6 @@ variant nvx::guest::mappings::TableError::Refused field nvx::guest::mappings::TableError::Refused::entry field nvx::guest::mappings::TableError::Refused::refusal method nvx::guest::mappings::TableError::entry -struct nvx::guest::mappings::TargetRoot (hidden) -method nvx::guest::mappings::TargetRoot::add_mount (hidden) -method nvx::guest::mappings::TargetRoot::open (hidden) -struct nvx::guest::mappings::TeardownError (hidden) -method nvx::guest::mappings::TeardownError::failures (hidden) -struct nvx::guest::mappings::UnmountFailure (hidden) -method nvx::guest::mappings::UnmountFailure::error (hidden) -method nvx::guest::mappings::UnmountFailure::target (hidden) -mod nvx::guest::mappings::mapper (hidden) -struct nvx::guest::mappings::mapper::ApplyError (hidden) -method nvx::guest::mappings::mapper::ApplyError::entry (hidden) -method nvx::guest::mappings::mapper::ApplyError::error (hidden) -struct nvx::guest::mappings::mapper::Mapper (hidden) -method nvx::guest::mappings::mapper::Mapper::new (hidden) -method nvx::guest::mappings::mapper::Mapper::targets (hidden) -enum nvx::guest::mappings::mapper::MappingError (hidden) -variant nvx::guest::mappings::mapper::MappingError::Foreign (hidden) -field nvx::guest::mappings::mapper::MappingError::Foreign::path (hidden) -variant nvx::guest::mappings::mapper::MappingError::Latched (hidden) -variant nvx::guest::mappings::mapper::MappingError::Link (hidden) -field nvx::guest::mappings::mapper::MappingError::Link::path (hidden) -variant nvx::guest::mappings::mapper::MappingError::MountPoint (hidden) -variant nvx::guest::mappings::mapper::MappingError::Moved (hidden) -field nvx::guest::mappings::mapper::MappingError::Moved::mounted (hidden) -variant nvx::guest::mappings::mapper::MappingError::NotDirectory (hidden) -field nvx::guest::mappings::mapper::MappingError::NotDirectory::path (hidden) -variant nvx::guest::mappings::mapper::MappingError::Overlap (hidden) -field nvx::guest::mappings::mapper::MappingError::Overlap::mapped (hidden) -variant nvx::guest::mappings::mapper::MappingError::Reserved (hidden) -variant nvx::guest::mappings::mapper::MappingError::SourceKind (hidden) -variant nvx::guest::mappings::mapper::MappingError::System (hidden) -field nvx::guest::mappings::mapper::MappingError::System::error (hidden) -field nvx::guest::mappings::mapper::MappingError::System::step (hidden) -variant nvx::guest::mappings::mapper::MappingError::TargetKind (hidden) -method nvx::guest::mappings::mapper::MappingError::raw_os_error (hidden) -struct nvx::guest::mappings::mapper::Reserved (hidden) -method nvx::guest::mappings::mapper::Reserved::container_root (hidden) -method nvx::guest::mappings::mapper::Reserved::new (hidden) -struct nvx::guest::mappings::mapper::SourceRoot (hidden) -method nvx::guest::mappings::mapper::SourceRoot::open (hidden) -enum nvx::guest::mappings::mapper::Step (hidden) -variant nvx::guest::mappings::mapper::Step::Attach (hidden) -variant nvx::guest::mappings::mapper::Step::Clone (hidden) -variant nvx::guest::mappings::mapper::Step::Create (hidden) -variant nvx::guest::mappings::mapper::Step::Locate (hidden) -variant nvx::guest::mappings::mapper::Step::Restrict (hidden) -variant nvx::guest::mappings::mapper::Step::Source (hidden) -variant nvx::guest::mappings::mapper::Step::Status (hidden) -variant nvx::guest::mappings::mapper::Step::Unmount (hidden) -variant nvx::guest::mappings::mapper::Step::Walk (hidden) -struct nvx::guest::mappings::mapper::TargetRoot (hidden) -method nvx::guest::mappings::mapper::TargetRoot::add_mount (hidden) -method nvx::guest::mappings::mapper::TargetRoot::open (hidden) -struct nvx::guest::mappings::mapper::TeardownError (hidden) -method nvx::guest::mappings::mapper::TeardownError::failures (hidden) -struct nvx::guest::mappings::mapper::UnmountFailure (hidden) -method nvx::guest::mappings::mapper::UnmountFailure::error (hidden) -method nvx::guest::mappings::mapper::UnmountFailure::target (hidden) mod nvx::guest::mmio_write (hidden) fn nvx::guest::mmio_write::main (hidden) mod nvx::guest::nvxs @@ -894,199 +712,6 @@ method nvx::guest::reseed::Seed::generation_id method nvx::guest::reseed::Seed::read fn nvx::guest::reseed::main (hidden) fn nvx::guest::reseed::reseed -mod nvx::guest::root (hidden) -struct nvx::guest::root::Assembly (hidden) -field nvx::guest::root::Assembly::distro (hidden) -field nvx::guest::root::Assembly::image (hidden) -field nvx::guest::root::Assembly::overlay (hidden) -field nvx::guest::root::Assembly::runtime (hidden) -field nvx::guest::root::Assembly::timeout (hidden) -field nvx::guest::root::Assembly::upper (hidden) -struct nvx::guest::root::AssemblyError (hidden) -struct nvx::guest::root::Device (hidden) -field nvx::guest::root::Device::mmio (hidden) -field nvx::guest::root::Device::sectors (hidden) -enum nvx::guest::root::DeviceStep (hidden) -variant nvx::guest::root::DeviceStep::Access (hidden) -variant nvx::guest::root::DeviceStep::Flush (hidden) -variant nvx::guest::root::DeviceStep::Open (hidden) -variant nvx::guest::root::DeviceStep::Reread (hidden) -variant nvx::guest::root::DeviceStep::Size (hidden) -variant nvx::guest::root::DeviceStep::Status (hidden) -variant nvx::guest::root::DeviceStep::Superblock (hidden) -enum nvx::guest::root::Error (hidden) -variant nvx::guest::root::Error::Access (hidden) -field nvx::guest::root::Error::Access::device (hidden) -field nvx::guest::root::Error::Access::read_only (hidden) -field nvx::guest::root::Error::Access::role (hidden) -variant nvx::guest::root::Error::Device (hidden) -field nvx::guest::root::Error::Device::device (hidden) -field nvx::guest::root::Error::Device::error (hidden) -field nvx::guest::root::Error::Device::role (hidden) -field nvx::guest::root::Error::Device::step (hidden) -variant nvx::guest::root::Error::Disk (hidden) -field nvx::guest::root::Error::Disk::device (hidden) -field nvx::guest::root::Error::Disk::error (hidden) -variant nvx::guest::root::Error::FirstPartition (hidden) -field nvx::guest::root::Error::FirstPartition::device (hidden) -field nvx::guest::root::Error::FirstPartition::error (hidden) -variant nvx::guest::root::Error::Journal (hidden) -field nvx::guest::root::Error::Journal::device (hidden) -variant nvx::guest::root::Error::Lookup (hidden) -field nvx::guest::root::Error::Lookup::error (hidden) -field nvx::guest::root::Error::Lookup::mmio (hidden) -field nvx::guest::root::Error::Lookup::role (hidden) -variant nvx::guest::root::Error::Mapping (hidden) -field nvx::guest::root::Error::Mapping::error (hidden) -field nvx::guest::root::Error::Mapping::mount (hidden) -variant nvx::guest::root::Error::Missing (hidden) -field nvx::guest::root::Error::Missing::mmio (hidden) -field nvx::guest::root::Error::Missing::role (hidden) -variant nvx::guest::root::Error::Mount (hidden) -field nvx::guest::root::Error::Mount::error (hidden) -field nvx::guest::root::Error::Mount::mount (hidden) -variant nvx::guest::root::Error::MountPoint (hidden) -field nvx::guest::root::Error::MountPoint::error (hidden) -field nvx::guest::root::Error::MountPoint::path (hidden) -variant nvx::guest::root::Error::NotErofs (hidden) -field nvx::guest::root::Error::NotErofs::device (hidden) -variant nvx::guest::root::Error::NotExt4 (hidden) -field nvx::guest::root::Error::NotExt4::device (hidden) -variant nvx::guest::root::Error::NotExt4Layer (hidden) -field nvx::guest::root::Error::NotExt4Layer::device (hidden) -field nvx::guest::root::Error::NotExt4Layer::number (hidden) -variant nvx::guest::root::Error::NotFresh (hidden) -field nvx::guest::root::Error::NotFresh::path (hidden) -variant nvx::guest::root::Error::Occupied (hidden) -field nvx::guest::root::Error::Occupied::path (hidden) -variant nvx::guest::root::Error::OverlayCheck (hidden) -field nvx::guest::root::Error::OverlayCheck::error (hidden) -field nvx::guest::root::Error::OverlayCheck::path (hidden) -variant nvx::guest::root::Error::OverlayFallback (hidden) -field nvx::guest::root::Error::OverlayFallback::effective (hidden) -field nvx::guest::root::Error::OverlayFallback::requested (hidden) -variant nvx::guest::root::Error::OverlayOptions (hidden) -field nvx::guest::root::Error::OverlayOptions::length (hidden) -variant nvx::guest::root::Error::Partition (hidden) -field nvx::guest::root::Error::Partition::node (hidden) -field nvx::guest::root::Error::Partition::number (hidden) -variant nvx::guest::root::Error::Recovery (hidden) -field nvx::guest::root::Error::Recovery::device (hidden) -field nvx::guest::root::Error::Recovery::number (hidden) -variant nvx::guest::root::Error::Reserved (hidden) -field nvx::guest::root::Error::Reserved::path (hidden) -variant nvx::guest::root::Error::Restrict (hidden) -field nvx::guest::root::Error::Restrict::error (hidden) -variant nvx::guest::root::Error::Runtime (hidden) -field nvx::guest::root::Error::Runtime::path (hidden) -variant nvx::guest::root::Error::Share (hidden) -field nvx::guest::root::Error::Share::0 (hidden) -variant nvx::guest::root::Error::Size (hidden) -field nvx::guest::root::Error::Size::actual (hidden) -field nvx::guest::root::Error::Size::device (hidden) -field nvx::guest::root::Error::Size::expected (hidden) -field nvx::guest::root::Error::Size::role (hidden) -variant nvx::guest::root::Error::TmpfsCap (hidden) -field nvx::guest::root::Error::TmpfsCap::percent (hidden) -variant nvx::guest::root::Error::Uuid (hidden) -field nvx::guest::root::Error::Uuid::actual (hidden) -field nvx::guest::root::Error::Uuid::expected (hidden) -enum nvx::guest::root::Image (hidden) -variant nvx::guest::root::Image::Erofs (hidden) -field nvx::guest::root::Image::Erofs::uuid (hidden) -variant nvx::guest::root::Image::Gpt (hidden) -field nvx::guest::root::Image::Gpt::expected (hidden) -field nvx::guest::root::Image::Gpt::first_partition_bound (hidden) -enum nvx::guest::root::Journal (hidden) -variant nvx::guest::root::Journal::Allowed (hidden) -variant nvx::guest::root::Journal::Forbidden (hidden) -struct nvx::guest::root::Lower (hidden) -const nvx::guest::root::MOUNT_OPTIONS_LIMIT (hidden) -enum nvx::guest::root::Mount (hidden) -variant nvx::guest::root::Mount::Aggregate (hidden) -field nvx::guest::root::Mount::Aggregate::tag (hidden) -variant nvx::guest::root::Mount::Child (hidden) -field nvx::guest::root::Mount::Child::name (hidden) -field nvx::guest::root::Mount::Child::target (hidden) -variant nvx::guest::root::Mount::Distro (hidden) -variant nvx::guest::root::Mount::Export (hidden) -field nvx::guest::root::Mount::Export::tag (hidden) -variant nvx::guest::root::Mount::Layer (hidden) -field nvx::guest::root::Mount::Layer::number (hidden) -variant nvx::guest::root::Mount::Mapping (hidden) -field nvx::guest::root::Mount::Mapping::target (hidden) -variant nvx::guest::root::Mount::Overlay (hidden) -variant nvx::guest::root::Mount::Runtime (hidden) -field nvx::guest::root::Mount::Runtime::0 (hidden) -variant nvx::guest::root::Mount::Scratch (hidden) -variant nvx::guest::root::Mount::Share (hidden) -field nvx::guest::root::Mount::Share::tag (hidden) -field nvx::guest::root::Mount::Share::target (hidden) -variant nvx::guest::root::Mount::Tmpfs (hidden) -enum nvx::guest::root::OverlayMode (hidden) -variant nvx::guest::root::OverlayMode::TrustedLowers (hidden) -variant nvx::guest::root::OverlayMode::UntrustedLowers (hidden) -method nvx::guest::root::OverlayMode::options (hidden) -enum nvx::guest::root::Role (hidden) -variant nvx::guest::root::Role::Distro (hidden) -variant nvx::guest::root::Role::Scratch (hidden) -struct nvx::guest::root::Root (hidden) -method nvx::guest::root::Root::mount_aggregate (hidden) -method nvx::guest::root::Root::mount_export (hidden) -method nvx::guest::root::Root::mount_share (hidden) -method nvx::guest::root::Root::path (hidden) -method nvx::guest::root::Root::runtime (hidden) -method nvx::guest::root::Root::teardown (hidden) -enum nvx::guest::root::RuntimeMounts (hidden) -variant nvx::guest::root::RuntimeMounts::Dev (hidden) -variant nvx::guest::root::RuntimeMounts::DevPts (hidden) -variant nvx::guest::root::RuntimeMounts::DevShm (hidden) -variant nvx::guest::root::RuntimeMounts::Proc (hidden) -variant nvx::guest::root::RuntimeMounts::Run (hidden) -variant nvx::guest::root::RuntimeMounts::Sys (hidden) -variant nvx::guest::root::RuntimeMounts::TimeStatus (hidden) -struct nvx::guest::root::ShareChild (hidden) -field nvx::guest::root::ShareChild::access (hidden) -field nvx::guest::root::ShareChild::kind (hidden) -field nvx::guest::root::ShareChild::name (hidden) -field nvx::guest::root::ShareChild::target (hidden) -enum nvx::guest::root::ShareKind (hidden) -variant nvx::guest::root::ShareKind::Directory (hidden) -variant nvx::guest::root::ShareKind::File (hidden) -enum nvx::guest::root::ShareRefusal (hidden) -variant nvx::guest::root::ShareRefusal::ChildName (hidden) -field nvx::guest::root::ShareRefusal::ChildName::0 (hidden) -variant nvx::guest::root::ShareRefusal::DuplicateChild (hidden) -field nvx::guest::root::ShareRefusal::DuplicateChild::0 (hidden) -variant nvx::guest::root::ShareRefusal::NoChildren (hidden) -variant nvx::guest::root::ShareRefusal::NotCanonical (hidden) -field nvx::guest::root::ShareRefusal::NotCanonical::0 (hidden) -variant nvx::guest::root::ShareRefusal::Overlap (hidden) -field nvx::guest::root::ShareRefusal::Overlap::claimed (hidden) -field nvx::guest::root::ShareRefusal::Overlap::target (hidden) -variant nvx::guest::root::ShareRefusal::Relative (hidden) -field nvx::guest::root::ShareRefusal::Relative::0 (hidden) -variant nvx::guest::root::ShareRefusal::Reserved (hidden) -field nvx::guest::root::ShareRefusal::Reserved::0 (hidden) -variant nvx::guest::root::ShareRefusal::ReservedCharacter (hidden) -field nvx::guest::root::ShareRefusal::ReservedCharacter::0 (hidden) -variant nvx::guest::root::ShareRefusal::SecondAggregate (hidden) -variant nvx::guest::root::ShareRefusal::SecondExport (hidden) -variant nvx::guest::root::ShareRefusal::Tag (hidden) -field nvx::guest::root::ShareRefusal::Tag::0 (hidden) -struct nvx::guest::root::TeardownError (hidden) -struct nvx::guest::root::Unmount (hidden) -enum nvx::guest::root::Upper (hidden) -variant nvx::guest::root::Upper::Scratch (hidden) -field nvx::guest::root::Upper::Scratch::device (hidden) -field nvx::guest::root::Upper::Scratch::journal (hidden) -variant nvx::guest::root::Upper::Tmpfs (hidden) -field nvx::guest::root::Upper::Tmpfs::max_memory_percent (hidden) -method nvx::guest::root::Upper::kind (hidden) -enum nvx::guest::root::UpperKind (hidden) -variant nvx::guest::root::UpperKind::Scratch (hidden) -variant nvx::guest::root::UpperKind::Tmpfs (hidden) -fn nvx::guest::root::assemble (hidden) mod nvx::guest::snapshot enum nvx::guest::snapshot::CaptureCause variant nvx::guest::snapshot::CaptureCause::Io @@ -1234,15 +859,6 @@ method nvx::guest::time::Violation::status fn nvx::guest::time::main (hidden) mod nvx::guest::time_probe (hidden) fn nvx::guest::time_probe::main (hidden) -mod nvx::guest::view (hidden) -const nvx::guest::view::MACHINE_ID_FILE (hidden) -enum nvx::guest::view::MachineId (hidden) -variant nvx::guest::view::MachineId::Generated (hidden) -field nvx::guest::view::MachineId::Generated::0 (hidden) -variant nvx::guest::view::MachineId::Image (hidden) -method nvx::guest::view::MachineId::generate (hidden) -const nvx::guest::view::RESOLVER_FILE (hidden) -fn nvx::guest::view::apply (hidden) fn nvx::guest_path mod nvx::host mod nvx::host::cli (hidden) diff --git a/nvx-rs/src/guest/activation.rs b/nvx-rs/src/guest/activation.rs index 763ce71c9..f9722c8c9 100644 --- a/nvx-rs/src/guest/activation.rs +++ b/nvx-rs/src/guest/activation.rs @@ -84,7 +84,7 @@ //! memory size that the repair reports, before anything has a side effect. An Activate whose //! record the adapter's framing refused unread, as longer than a record can be, fails as a //! record that long does; -//! 3. activating: the adapter applies the accepted record with `apply`, which +//! 3. activating: the adapter applies the accepted record in its own space, which //! activates the root and the workload with its settings, as below, passes the activation //! what the session received meanwhile, which came before Ready, then reports the result to //! [`Activation::complete`]; @@ -114,18 +114,15 @@ //! //! [guest-agent guidelines]: https://github.com/microsoft/nvx/blob/dev/doc/guest-agent-guidelines.md //! -//! The activation's application, `apply` and its types, is NVX's internal reference code of the -//! guest's space, hidden from this documentation and unstable: it becomes crate-private once -//! nothing outside NVX uses it. -//! -//! `apply` activates the clone as at a cold boot. It assembles the root as -//! `root::assemble` does, with the record's image, an EROFS image whose UUID it checks if the -//! record expects one, or a GPT disk whose GUID it checks if the record expects one, its upper, a -//! tmpfs or the scratch, with the record's journal policy, and its overlay mode, and with the -//! inputs that the guest takes from its own configuration, which its template captured, a -//! `RootTemplate`: the runtime directory, the block devices, the host filesystem device's +//! Each adapter applies the record in its own space. nvx-init applies it with its own, NVX's +//! internal reference code, private to the crate, which activates the clone as at a cold boot. It +//! assembles the root as a cold boot does, with the record's image, an EROFS image +//! whose UUID it checks if the record expects one, or a GPT disk whose GUID it checks if the +//! record expects one, its upper, a tmpfs or the scratch, with the record's journal policy, and +//! its overlay mode, and with the inputs that the guest takes from its own configuration, which +//! its template captured: the runtime directory, the block devices, the host filesystem device's //! virtio-fs tag, the gateway of the template's NIC, if it has one, and how long the assembly waits -//! for each block device, but never past the activation's deadline, which the adapter passes, so +//! for each block device, but never past the activation's deadline, so //! that the application ends by then. For a GPT disk, it first asks the kernel to reread the disk's //! partition table, once the distro device appeared, by the same deadline: the kernel created the //! block devices of partitions from the template's placeholder, when it found it, and creates those @@ -135,22 +132,19 @@ //! sandbox's scratch keeps its upper across boots; the check reads at most one entry of each, and //! no file. The record holds no execution identity: the adapter gives each execution the identity //! that the execution names, as for a sandbox without a workload identity at a cold boot. If the -//! record has an export, it mounts the host -//! export through the template's -//! filesystem device, with `Root::mount_export`, read-write, at -//! `hostfs/root` in the runtime directory, outside the root, where only the guest's root user -//! reaches it: the record names no mode, and OpenVMM limits each of the export's children to its -//! own access. It applies the record's VM-wide view through `view::apply`, with the record's -//! DNS server, if any. The root owns the machine-ID and resolver binds, including any made -//! before a later failure. The view sets the VM's hostname and generates an ID only when the -//! record requests one, after the repair's reseed. Application starts no process. It returns -//! the `Applied` root and workload, which the adapter keeps, with the export's root, from which -//! the adapter maps the host's mapping table once the clone is ready, the network's proxy -//! endpoint, which the adapter tells its workloads as at a cold boot, and the publication and -//! gateway with which the caller starts and supervises ingress. The `teardown` of the `Applied` -//! closes the export's root, then tears the root down with its view binds. An application that -//! fails returns an `ApplyError` instead, after which nothing that it mounted or bound stays -//! mounted, unless the error holds the unmounts that failed. +//! record has an export, it mounts the host export through the template's filesystem device, +//! read-write, at `hostfs/root` in the runtime directory, outside the root, where only the guest's +//! root user reaches it: the record names no mode, and OpenVMM limits each of the export's +//! children to its own access. It applies the record's VM-wide view, with the record's DNS server, +//! if any. The root owns the machine-ID and resolver binds, including any made before a later +//! failure. The view sets the VM's hostname and generates an ID only when the record requests +//! one, after the repair's reseed. The application starts no process. nvx-init keeps the root and +//! the workload that it activated, with the export's root, from which it maps the host's mapping +//! table once the clone is ready, the network's proxy endpoint, which it tells its workloads as at +//! a cold boot, and the publication and gateway with which it starts and supervises ingress. Their +//! teardown closes the export's root, then tears the root down with its view binds. An +//! application that fails leaves nothing that it mounted or bound mounted, unless its error holds +//! the unmounts that failed. //! //! # Example //! @@ -206,16 +200,16 @@ //! report-only finish without a running time daemon, which runs the deferred time work itself, //! in a child that it waits for. They never power the VM off, so a caller that is not PID 1 keeps //! its own reaping, transport, and shutdown. The activation needs no privilege, and -//! makes no system call and changes no admission fence. Its -//! application, `apply`, needs the guest's root user, in the guest's initial mount -//! namespace, as `root::assemble` does: for a GPT disk, it asks the kernel to reread the disk's -//! partition table, which needs `CAP_SYS_ADMIN`; it mounts the root, applies its VM-wide view, -//! and, if it fails, tears it down. It starts no process and generates at most one ID, a +//! makes no system call and changes no admission fence. nvx-init's +//! application of the record needs the guest's root user, in the guest's initial mount +//! namespace, as its assembly of a root does: for a GPT disk, it asks the kernel to reread the +//! disk's partition table, which needs `CAP_SYS_ADMIN`; it mounts the root, applies its VM-wide +//! view, and, if it fails, tears it down. It starts no process and generates at most one ID, a //! machine ID that the record asks for. It mounts the host export of a record that has one, and //! 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 state; +pub(crate) mod state; #[cfg(test)] mod tests; @@ -235,10 +229,7 @@ pub use self::state::{ Activation, ActivationAction, ActivationCause, ActivationFailure, ActivationInput, ActivationState, MACHINE_ID_FILE, RESOLVER_FILE, }; -#[doc(hidden)] -pub use self::state::{ - Applied, AppliedTeardownError, ApplyError, NetworkError, RootTemplate, apply, -}; +pub(crate) use self::state::{Applied, ApplyError, RootTemplate, apply}; pub use super::snapshot::{MOUNT_TABLE_LIMIT, Mounted, Origin}; pub use super::snapshot::{ RangeFault, RepairCause, RepairError, RepairProgress, RepairStage, Repaired, diff --git a/nvx-rs/src/guest/activation/state.rs b/nvx-rs/src/guest/activation/state.rs index 02ecb7e23..0241de1e0 100644 --- a/nvx-rs/src/guest/activation/state.rs +++ b/nvx-rs/src/guest/activation/state.rs @@ -3,7 +3,7 @@ //! the host in one Activate, and [`apply`](fn@apply), which activates the clone's root and workload //! with that configuration, as the [module](super#activation) describes. -mod apply; +pub(crate) mod apply; #[cfg(test)] mod tests; @@ -17,10 +17,8 @@ use crate::activation::{ ActivationError, ActivationRecord, ExpectedResources, ReadyActivation, TransactionId, }; -pub use self::apply::{ - Applied, AppliedTeardownError, ApplyError, MACHINE_ID_FILE, NetworkError, RESOLVER_FILE, - RootTemplate, apply, -}; +pub(crate) use self::apply::{Applied, ApplyError, RootTemplate, apply}; +pub use self::apply::{MACHINE_ID_FILE, RESOLVER_FILE}; /// The activation of a repaired clone: the state machine, without I/O and independent of any /// framing, of the clone's control session, which takes the clone's configuration from the host @@ -29,8 +27,8 @@ pub use self::apply::{ /// /// The adapter passes it what it receives on the session, each an [`ActivationInput`], through /// [`input`](Self::input), and does what the [`ActivationAction`] that it returns says. The -/// adapter applies the record of the Activate that the activation accepted, with -/// `apply`, whose waits end by the activation's [`deadline`](Self::deadline), passes +/// adapter applies the record of the Activate that the activation accepted, in its own space, +/// by the activation's [`deadline`](Self::deadline), passes /// the activation what the session received while the application ran, which came before Ready, /// then reports the result through [`complete`](Self::complete), whose Ready answers the Activate /// and each retry that [joined](ActivationAction::Join) it, and it reports the start of the @@ -54,13 +52,11 @@ pub use self::apply::{ /// # Example /// /// ```no_run +/// use std::error::Error; /// use std::time::Instant; /// -/// use nvx::activation::ReadyActivation; -/// use nvx::guest::activation::{ -/// self, Activation, ActivationAction, ActivationFailure, ActivationInput, -/// Applied, RootTemplate, -/// }; +/// use nvx::activation::{ActivationRecord, ReadyActivation}; +/// use nvx::guest::activation::{Activation, ActivationAction, ActivationFailure, ActivationInput}; /// /// /// What the agent's control session received from the host, in the agent's own framing. /// enum Received { @@ -87,12 +83,16 @@ pub use self::apply::{ /// /// Queues the acknowledgement of the session's reset, and abandons the earlier epoch's /// /// work. /// fn acknowledge(&mut self); -/// /// Keeps the clone's activated root and workload: the identity, if any, in which it runs -/// /// every execution, with the hostname of every execution, the root, with the runtime's -/// /// binds on it, which `Applied::teardown` removes before it tears the root down, and the -/// /// export's root, which `Applied::take_export` hands over, from which it maps the host's -/// /// mapping table. -/// fn keep(&mut self, applied: Applied); +/// /// Activates the clone's root and workload with the settings of `record` in the agent's +/// /// own space, whose waits for the devices end by `deadline`, and keeps them: the identity, +/// /// if any, in which it runs every execution, with the hostname of every execution, the +/// /// root, and the export's root, from which it maps the host's mapping table. nvx-init's +/// /// space, NVX's internal reference code, is private to `nvx`. +/// fn activate( +/// &mut self, +/// record: &ActivationRecord, +/// deadline: Option, +/// ) -> Result<(), Box>; /// /// Queues Ready, which reports `activation` beside the agent's own identities, such as its /// /// build. /// fn ready(&mut self, activation: ReadyActivation); @@ -101,13 +101,8 @@ pub use self::apply::{ /// fn serve(&mut self, message: Vec) -> bool; /// } /// -/// /// Serves the control session of the clone whose activation is `activation`, and whose -/// /// template captured `template`, the inputs of its root's assembly, until it fails. -/// fn serve( -/// agent: &mut impl Agent, -/// template: &RootTemplate, -/// mut activation: Activation, -/// ) -> Result<(), ActivationFailure> { +/// /// Serves the control session of the clone whose activation is `activation`, until it fails. +/// fn serve(agent: &mut impl Agent, mut activation: Activation) -> Result<(), ActivationFailure> { /// loop { /// let Some(received) = agent.receive(activation.deadline()) else { /// // The deadline passed, unless the clone is ready, which ends the activation. @@ -122,10 +117,10 @@ pub use self::apply::{ /// // effects start here. The /// // application waits for the devices until the activation's deadline at the /// // latest, so that it ends by then. -/// let applied = activation::apply(&record, template, activation.deadline()); +/// let applied = agent.activate(&record, activation.deadline()); /// // What the host sent meanwhile came before Ready: a retry of the Activate, /// // byte for byte, joins the application, and anything else, a reset included, -/// // fails the activation, which drops what the application assembled. +/// // fails the activation. /// let mut joined = 0; /// while let Some(received) = agent.receive(Some(Instant::now())) { /// let action = activation.input(Instant::now(), input(&received))?; @@ -133,14 +128,8 @@ pub use self::apply::{ /// joined += 1; /// } /// } -/// let ready = match applied { -/// Ok(applied) => { -/// agent.keep(applied); -/// activation.complete(Instant::now(), Ok(()))? -/// } -/// // A failed application fails the activation. -/// Err(error) => activation.complete(Instant::now(), Err(error.into()))?, -/// }; +/// // A failed application fails the activation. +/// let ready = activation.complete(Instant::now(), applied)?; /// // Ready answers the Activate, and each retry that joined its application. /// for _ in 0..=joined { /// agent.ready(ready); @@ -484,8 +473,8 @@ pub enum ActivationAction { /// The activation took the input, which needs nothing more of the adapter. Continue, /// The activation accepted the host's Activate, whose record decoded and validated, and - /// which nothing before had any side effect of: the adapter applies the record with - /// `apply`, which activates the root and the workload with its settings, passes + /// which nothing before had any side effect of: the adapter applies the record in its own + /// space, which activates the root and the workload with its settings, passes /// the activation what the session received meanwhile, which came before Ready, then reports /// the result to [`Activation::complete`]. Apply(Box), diff --git a/nvx-rs/src/guest/activation/state/apply.rs b/nvx-rs/src/guest/activation/state/apply.rs index 1a495d7c2..54e2694bf 100644 --- a/nvx-rs/src/guest/activation/state/apply.rs +++ b/nvx-rs/src/guest/activation/state/apply.rs @@ -1,7 +1,8 @@ //! Application of an accepted activation record, without starting a process. //! -//! This is unstable internal reference code of the guest's space. The root owns the VM-wide -//! view's binds; the caller owns ingress and the admission of its workloads. +//! This module is part of the guest's space, nvx-init's internal reference code, private to the +//! crate, as the [activation](crate::guest::activation#activation) describes. The root owns the +//! VM-wide view's binds; the caller owns ingress and the admission of its workloads. #[cfg(test)] mod tests; @@ -25,21 +26,20 @@ use crate::guest::view::{self, MachineId}; pub use crate::guest::view::{MACHINE_ID_FILE, RESOLVER_FILE}; /// The root inputs captured by a template, shared by its clones. -#[doc(hidden)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct RootTemplate { +pub(crate) struct RootTemplate { /// The runtime directory beneath which the root and its view files are assembled. - pub runtime: PathBuf, + pub(crate) runtime: PathBuf, /// The distro device, whose placeholder a clone replaces with its image. - pub distro: Device, + pub(crate) distro: Device, /// The scratch device, unused by a record with a RAM upper. - pub scratch: Device, + pub(crate) scratch: Device, /// How long assembly waits for each device, never past the activation's deadline. - pub timeout: Duration, + pub(crate) timeout: Duration, /// The virtio-fs tag of the host filesystem device, as [`Devices::filesystem_tag`] names it. - pub filesystem_tag: String, + pub(crate) filesystem_tag: String, /// The template NIC's gateway, returned for the caller's ingress setup. - pub gateway: Option, + pub(crate) gateway: Option, } /// The applied root and VM-wide view, with the settings the caller needs for ingress. @@ -48,9 +48,8 @@ pub struct RootTemplate { /// published listeners, starts and supervises ingress itself. The root owns every view bind, /// including one made before a later failure: its teardown removes them before its switch, /// and nothing unmounts them afterwards. -#[doc(hidden)] #[derive(Debug)] -pub struct Applied { +pub(crate) struct Applied { /// The export descriptor, dropped before the root unless the caller takes it. export: Option, /// The root, which also owns the view's binds. @@ -65,47 +64,46 @@ pub struct Applied { impl Applied { /// Returns the assembled root. - pub fn root(&self) -> &Root { + pub(crate) fn root(&self) -> &Root { &self.root } /// Returns the assembled root to mount runtime filesystems and shares in, and to switch into. - pub fn root_mut(&mut self) -> &mut Root { + pub(crate) fn root_mut(&mut self) -> &mut Root { &mut self.root } /// Returns the proxy endpoint for the caller to tell its workloads. - pub fn proxy(&self) -> Option<&str> { + pub(crate) fn proxy(&self) -> Option<&str> { self.proxy.as_deref() } /// Returns the record's publication, or unpublished when it has no network. - pub fn publication(&self) -> Publication { + pub(crate) fn publication(&self) -> Publication { self.publication } /// Returns the template NIC's gateway for the caller's ingress setup. - pub fn gateway(&self) -> Option { + pub(crate) fn gateway(&self) -> Option { self.gateway } /// Takes the export's root, whose descriptor must close before the root's teardown. - pub fn take_export(&mut self) -> Option { + pub(crate) fn take_export(&mut self) -> Option { self.export.take() } /// Closes the export descriptor, then tears the root down, including its view binds. /// /// As [`Root::teardown`] specifies, a switched root refuses teardown and unmounts nothing. - pub fn teardown(self) -> Result<(), AppliedTeardownError> { + pub(crate) fn teardown(self) -> Result<(), AppliedTeardownError> { teardown_with(&mut LiveSystem, self.export, self.root) } } /// Why an applied root's teardown failed: the root's teardown, which includes its view binds. -#[doc(hidden)] #[derive(Debug)] -pub struct AppliedTeardownError { +pub(crate) struct AppliedTeardownError { /// The root's teardown failure, including its view binds. root: Option, } @@ -131,8 +129,7 @@ impl std::error::Error for AppliedTeardownError {} /// The result reports the record's publication and the template's gateway. Published listeners /// require a gateway, but the caller, not this application, starts and supervises ingress. /// Each execution supplies its own identity; application reads no account database. -#[doc(hidden)] -pub fn apply( +pub(crate) fn apply( record: &ActivationRecord, template: &RootTemplate, deadline: Option, @@ -148,10 +145,9 @@ pub fn apply( } /// Why applying an activation record failed. -#[doc(hidden)] #[derive(Debug)] #[non_exhaustive] -pub enum ApplyError { +pub(crate) enum ApplyError { /// The distro device did not appear, or its GPT partition table could not be reread. Gpt(root::Error), /// Assembly failed, including a refusal of a non-fresh upper. @@ -223,10 +219,9 @@ impl std::error::Error for ApplyError { } /// Why an activation's network settings could not be applied. -#[doc(hidden)] #[derive(Debug)] #[non_exhaustive] -pub enum NetworkError { +pub(crate) enum NetworkError { /// The resolver file could not be written. Write(io::Error), /// The resolver bind retained by the root failed. diff --git a/nvx-rs/src/guest/gpt.rs b/nvx-rs/src/guest/gpt.rs index ddfa54362..8a550cc95 100644 --- a/nvx-rs/src/guest/gpt.rs +++ b/nvx-rs/src/guest/gpt.rs @@ -1,8 +1,7 @@ //! GPT disks that the guest's kernel attached as block devices. //! -//! This module is NVX's internal reference code of the guest's space, hidden from the -//! documentation and unstable: it becomes crate-private once nothing outside NVX uses it. -//! [`crate::gpt`], which reads and checks the table, stays public. +//! This module is part of the guest's space, nvx-init's internal reference code, private to the +//! crate. [`crate::gpt`], which reads and checks the table, stays public. //! //! [`read_disk`] reads the partition table of a disk through the disk's block device, such as //! `/dev/vda`, that the caller opened. It asks the device for its logical block size and its @@ -14,21 +13,6 @@ //! moves the file's position. The kernel created the disk's partitions, such as `/dev/vda1`, from //! the table that the disk held when the kernel found it; [`read_disk`] neither compares them //! with the table that it reads nor asks the kernel to reread the table. -//! -//! ```no_run -//! use std::fs::File; -//! -//! use nvx::gpt::Expected; -//! -//! # fn main() -> Result<(), Box> { -//! let device = File::open("/dev/vda")?; -//! let expected = Expected::new().disk_guid("8d2f3a4e-1c5b-4e7a-9f60-2b3c4d5e6f70".parse()?); -//! let disk = nvx::guest::gpt::read_disk(&device, &expected)?; -//! let configuration = disk.read_first_partition(&device, 64 * 1024)?; -//! println!("the first partition holds {} bytes of configuration", configuration.len()); -//! # Ok(()) -//! # } -//! ``` use std::fmt; use std::fs::File; @@ -41,7 +25,7 @@ use crate::gpt::{Disk, Expected, GptError}; /// Reads the partition table of the block device that `device` opened, at the device's logical /// block size and size, and checks it and the caller's `expected` values, as the module /// describes. -pub fn read_disk(device: &File, expected: &Expected) -> Result { +pub(crate) fn read_disk(device: &File, expected: &Expected) -> Result { let logical_block_size = sys::logical_block_size(device.as_fd()) .map_err(|error| DeviceError::LogicalBlockSize(error.into()))?; let size = @@ -52,7 +36,7 @@ pub fn read_disk(device: &File, expected: &Expected) -> Result Result> { + let device = File::open("/dev/vda")?; + let expected = Expected::new().disk_guid("8d2f3a4e-1c5b-4e7a-9f60-2b3c4d5e6f70".parse()?); + let disk = read_disk(&device, &expected)?; + let configuration = disk.read_first_partition(&device, 64 * 1024)?; + Ok(configuration.len()) + }; +} diff --git a/nvx-rs/src/guest/init/boot/tests.rs b/nvx-rs/src/guest/init/boot/tests.rs index 5fac93a07..1f4f40565 100644 --- a/nvx-rs/src/guest/init/boot/tests.rs +++ b/nvx-rs/src/guest/init/boot/tests.rs @@ -278,7 +278,7 @@ fn an_expired_activation_cannot_begin_or_advance_to_another_space_step() { #[test] fn a_failed_activation_ends_the_vm_with_the_status_of_its_class() { use crate::activation::{ActivationRecord, Configuration, RestoreSection, TransactionId}; - use crate::guest::activation::NetworkError; + use crate::guest::activation::state::apply::NetworkError; use crate::protocol::status::*; let now = Instant::now(); diff --git a/nvx-rs/src/guest/managed_agent/session/tests.rs b/nvx-rs/src/guest/managed_agent/session/tests.rs index 482e5340b..6363f81af 100644 --- a/nvx-rs/src/guest/managed_agent/session/tests.rs +++ b/nvx-rs/src/guest/managed_agent/session/tests.rs @@ -333,7 +333,7 @@ fn a_failed_restored_activation_gives_init_the_status_of_its_class() { peer.credit(); let failure = crate::Error::io("the network settings cannot be applied").with_source( crate::guest::activation::ApplyError::Network { - error: crate::guest::activation::NetworkError::Gateway, + error: crate::guest::activation::state::apply::NetworkError::Gateway, teardown: None, }, ); diff --git a/nvx-rs/src/guest/mappings.rs b/nvx-rs/src/guest/mappings.rs index d691320a6..fd5c5a979 100644 --- a/nvx-rs/src/guest/mappings.rs +++ b/nvx-rs/src/guest/mappings.rs @@ -8,8 +8,8 @@ //! caller's choice. //! //! NVX mounts mappings through its mapper, which binds each beneath the descriptor of its root. -//! The mapper, like the rest of the guest's space, is NVX's internal reference code: hidden from -//! this documentation, unstable, and crate-private once nothing outside NVX uses it. +//! The mapper, like the rest of the guest's space, is nvx-init's internal reference code, private +//! to this crate. //! //! # Host mapping table //! @@ -28,8 +28,7 @@ //! root, which lists the aggregate's children, no table maps: a table refuses an empty source. mod kernel; -#[doc(hidden)] -pub mod mapper; +pub(crate) mod mapper; mod table; use std::ffi::OsStr; @@ -40,12 +39,9 @@ use std::path::{Path, PathBuf}; use super::sys::{self, FileKind, MountAttributes, MountFlags}; pub(crate) use self::kernel::{Kernel, SystemKernel}; -#[doc(hidden)] -pub use self::mapper::{ - ApplyError, Mapper, MappingError, Reserved, SourceRoot, Step, TargetRoot, TeardownError, - UnmountFailure, +pub(crate) use self::mapper::{ + Mapped, Mapper, MappingError, Reserved, SourceRoot, Step, TargetRoot, unbind_with, }; -pub(crate) use self::mapper::{Mapped, unbind_with}; pub use self::table::{MappingTable, Refusal, TableError}; /// The longest source or target of a [`Mapping`], in bytes: Linux's `PATH_MAX` without its diff --git a/nvx-rs/src/guest/mappings/mapper.rs b/nvx-rs/src/guest/mappings/mapper.rs index 3e6cb3725..b85fe2b0c 100644 --- a/nvx-rs/src/guest/mappings/mapper.rs +++ b/nvx-rs/src/guest/mappings/mapper.rs @@ -1,9 +1,8 @@ //! Bind mounts of host mappings beneath the descriptors of their roots. //! -//! This module is NVX's internal reference code of the guest's space, hidden from the -//! documentation and unstable: it becomes crate-private once nothing outside NVX uses it. -//! [`guest::mappings`](super) re-exports its public items at their former paths, hidden too, -//! beside the mapping values and the host mapping table, which stay public. +//! This module is part of the guest's space, nvx-init's internal reference code, private to the +//! crate. [`guest::mappings`](super) re-exports the items that NVX's programs use, beside the +//! mapping values and the host mapping table, which stay public. //! //! A [`Mapper`] applies [`Mapping`]s beneath one [`TargetRoot`], in order, from a [`SourceRoot`], //! tracks them, and unmounts them, in reverse order, at the teardown of the layered root that @@ -141,7 +140,7 @@ const FILE_MODE: Mode = Mode::S_IRUSR /// /// The default reserves nothing more. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct Reserved { +pub(crate) struct Reserved { /// The reserved trees, relative to the target root. trees: Vec, /// The reserved paths, relative to the target root. @@ -151,7 +150,7 @@ pub struct Reserved { impl Reserved { /// Reserves `trees` and `paths`, absolute paths as seen from the target root, which must be /// canonical, as a [`Mapping`]'s target is: otherwise [`InvalidMapping::Target`]. - pub fn new(trees: T, paths: P) -> Result + pub(crate) fn new(trees: T, paths: P) -> Result where T: IntoIterator, T::Item: AsRef, @@ -179,7 +178,7 @@ impl Reserved { /// [`protocol::share`](crate::protocol::share) lists for the host and the guest: the trees /// `/proc`, `/sys`, `/dev`, `/.nvx-agent`, `/etc/machine-id`, `/etc/resolv.conf`, and /// `/run/nvx/time`, and the paths `/etc`, `/run`, and `/run/nvx`. - pub fn container_root() -> Self { + pub(crate) fn container_root() -> Self { Self::new(RESERVED_MOUNT_TARGETS, RESERVED_EXACT_MOUNT_TARGETS) .expect("the container runtime's reserved targets are canonical") } @@ -198,7 +197,7 @@ impl Reserved { /// system, and the root's own mounts, each of a whole file system too, on which the mapper may /// create what a target lacks. #[derive(Debug)] -pub struct TargetRoot { +pub(crate) struct TargetRoot { /// The root's directory, opened with `O_PATH`. directory: FileDescriptor, /// The unique IDs of the root's own mounts: the root's mount first. @@ -211,7 +210,7 @@ impl TargetRoot { /// the root's own: for example a layered root's overlay, or a guest's `/`. A final symbolic /// link at `path` is not followed. It needs Linux 6.8 or later, for the unique mount IDs and /// `statmount`. - pub fn open(path: &Path) -> io::Result { + pub(crate) fn open(path: &Path) -> io::Result { Self::open_with(&mut SystemKernel, path) } @@ -219,7 +218,7 @@ impl TargetRoot { /// the root's own mounts, on which the mapper may create what a target lacks, such as the /// scratch file system of a root without an overlay, and whose root a mapping may cover. A /// final symbolic link at `path` is not followed. - pub fn add_mount(&mut self, path: &Path) -> io::Result<()> { + pub(crate) fn add_mount(&mut self, path: &Path) -> io::Result<()> { self.add_mount_with(&mut SystemKernel, path) } @@ -263,14 +262,14 @@ impl TargetRoot { /// The root beneath which a [`Mapper`] or a runtime bind opens the sources of mappings, such as the /// guest's mount of the host's export: a descriptor of a directory. #[derive(Debug)] -pub struct SourceRoot { +pub(crate) struct SourceRoot { /// The root's directory, opened with `O_PATH`. directory: FileDescriptor, } impl SourceRoot { /// Opens the directory `path`, not through a final symbolic link. - pub fn open(path: &Path) -> io::Result { + pub(crate) fn open(path: &Path) -> io::Result { Ok(Self { directory: open_directory(path)?, }) @@ -280,7 +279,7 @@ impl SourceRoot { /// A step of applying or unmounting a mapping, at which a system call failed. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum Step { +pub(crate) enum Step { /// Opening the source beneath its root: a missing source fails with `ENOENT`, a symbolic or /// magic link on its path with `ELOOP`, and a path that crosses into another mount with /// `EXDEV`. @@ -323,7 +322,7 @@ impl fmt::Display for Step { /// Why a mapper refused a mapping, or failed to apply or unmount it. #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum MappingError { +pub(crate) enum MappingError { /// An earlier mapping of the mapper failed, so it refuses every later one. Latched, /// The runtime reserves the target, or a tree below it. @@ -382,7 +381,7 @@ impl MappingError { /// mapper, `EPERM` for a reserved target, `EBUSY` for an overlap or a mount point, `EINVAL` /// for a kind, `ELOOP` for a link, `ENOTDIR` for a component that is not a directory, `EXDEV` /// for a foreign mount, and `EAGAIN` for a moved mapping. - pub fn raw_os_error(&self) -> i32 { + pub(crate) fn raw_os_error(&self) -> i32 { let error = match self { Self::Latched => Errno::ECANCELED, Self::Reserved => Errno::EPERM, @@ -451,7 +450,7 @@ impl std::error::Error for MappingError {} /// Which mapping of a batch a [`Mapper`] refused or failed to apply, from 0, and why. #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct ApplyError { +pub(crate) struct ApplyError { /// The index of the mapping in its batch. entry: usize, /// Why it was refused or failed. @@ -460,12 +459,12 @@ pub struct ApplyError { impl ApplyError { /// Returns the index of the mapping in its batch, from 0. - pub fn entry(&self) -> usize { + pub(crate) fn entry(&self) -> usize { self.entry } /// Returns why the mapping was refused or failed. - pub fn error(&self) -> &MappingError { + pub(crate) fn error(&self) -> &MappingError { &self.error } } @@ -480,7 +479,7 @@ impl std::error::Error for ApplyError {} /// A mapping that a teardown failed to unmount, which stays mounted. #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct UnmountFailure { +pub(crate) struct UnmountFailure { /// The mapping's target, as an absolute path in the root. target: PathBuf, /// Why it was not unmounted. @@ -489,14 +488,14 @@ pub struct UnmountFailure { impl UnmountFailure { /// Returns the mapping's target, as an absolute path in the root. - pub fn target(&self) -> &Path { + pub(crate) fn target(&self) -> &Path { &self.target } /// Returns why the mapping was not unmounted: [`MappingError::Moved`] if another mount is at /// its target, and otherwise the failed [`Step::Locate`] or [`Step::Unmount`], which is also /// the step of a mapping that a rename moved and that the teardown failed to detach. - pub fn error(&self) -> &MappingError { + pub(crate) fn error(&self) -> &MappingError { &self.error } } @@ -514,14 +513,14 @@ impl fmt::Display for UnmountFailure { /// The mappings that a teardown failed to unmount, in the order in which it attempted them. #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct TeardownError { +pub(crate) struct TeardownError { /// The failures, which is never empty. failures: Vec, } impl TeardownError { /// Returns the failures, in the order in which the teardown attempted them. - pub fn failures(&self) -> &[UnmountFailure] { + pub(crate) fn failures(&self) -> &[UnmountFailure] { &self.failures } } @@ -564,7 +563,7 @@ struct Stray { /// The applier of a root's mappings, which checks, attaches, tracks, and unmounts them, as the /// [module](self) describes. #[derive(Debug)] -pub struct Mapper { +pub(crate) struct Mapper { /// The root beneath which it maps. root: TargetRoot, /// The targets that the root's profile reserves. @@ -580,7 +579,7 @@ pub struct Mapper { impl Mapper { /// Returns a mapper of no mappings yet beneath `root`, whose profile reserves `reserved`. - pub fn new(root: TargetRoot, reserved: Reserved) -> Self { + pub(crate) fn new(root: TargetRoot, reserved: Reserved) -> Self { Self { root, reserved, @@ -592,7 +591,7 @@ impl Mapper { /// Returns the targets of the mappings that the mapper tracks, in the order in which it /// applied them, relative to the root. - pub fn targets(&self) -> impl ExactSizeIterator { + pub(crate) fn targets(&self) -> impl ExactSizeIterator { self.mapped.iter().map(|mapped| mapped.target.as_path()) } diff --git a/nvx-rs/src/guest/mod.rs b/nvx-rs/src/guest/mod.rs index d8513e13f..3e788ca2e 100644 --- a/nvx-rs/src/guest/mod.rs +++ b/nvx-rs/src/guest/mod.rs @@ -16,14 +16,13 @@ //! [guest-agent guidelines]: https://github.com/microsoft/nvx/blob/dev/doc/guest-agent-guidelines.md //! //! The guest's space, the root that an init assembles and what it mounts in it, is each init's -//! own. NVX's own space is internal reference code, which NVX's guest programs use: it is hidden -//! from this documentation and unstable, and it becomes crate-private once nothing outside NVX -//! uses it. It is `root`, which assembles a layered root, mounts live shares in it, +//! own. NVX's own space is nvx-init's internal reference code, private to the crate, which NVX's +//! guest programs use. It is `root`, which assembles a layered root, mounts live shares in it, //! and tears it down; `view`, which sets the VM's hostname, //! machine ID, and resolver file, whose binds the root owns; `gpt`, which reads the partition //! table of a GPT disk that the kernel attached as a block device; the mapper of `mappings`, which //! mounts host mappings beneath the descriptors of the roots that it names; and the activation's -//! application, `activation::apply` and its types, with which a clone assembles its root and +//! application, with which a clone assembles its root and //! applies its VM-wide view without starting a process. The contracts that every space reads //! stay public: the mapping values and [`MappingTable`](mappings::MappingTable), and the tokens //! and records of [`protocol`](crate::protocol) and [`activation`](crate::activation). @@ -133,8 +132,7 @@ pub mod device_io; #[cfg(target_arch = "x86_64")] #[doc(hidden)] pub mod exit; -#[doc(hidden)] -pub mod gpt; +pub(crate) mod gpt; #[doc(hidden)] pub mod ingress; #[cfg(target_arch = "x86_64")] @@ -149,8 +147,7 @@ pub mod mappings; pub mod mmio_write; pub mod nvxs; pub mod reseed; -#[doc(hidden)] -pub mod root; +pub(crate) mod root; #[cfg(target_arch = "x86_64")] pub mod snapshot; pub mod terminal; @@ -159,5 +156,4 @@ pub mod time; #[cfg(target_arch = "x86_64")] #[doc(hidden)] pub mod time_probe; -#[doc(hidden)] -pub mod view; +pub(crate) mod view; diff --git a/nvx-rs/src/guest/root.rs b/nvx-rs/src/guest/root.rs index 2b2d928bf..53b7ae092 100644 --- a/nvx-rs/src/guest/root.rs +++ b/nvx-rs/src/guest/root.rs @@ -2,8 +2,8 @@ //! which a caller's mappings come, and tear it down, for a guest init or agent, whether or not it //! is PID 1. //! -//! This module is NVX's internal reference code of the guest's space, hidden from the -//! documentation and unstable: it becomes crate-private once nothing outside NVX uses it. +//! This module is part of the guest's space, nvx-init's internal reference code, private to the +//! crate. //! //! - [`assemble`] finds the block devices of an [`Assembly`] by their MMIO windows, checks and //! flushes them, and mounts the root: an overlay, mounted `nosuid,nodev` with the option set of @@ -149,7 +149,7 @@ use crate::uuid::Uuid; pub(crate) use self::block::window_directories; pub(crate) use self::kernel::Paths; -pub use self::share::{ShareChild, ShareKind, ShareRefusal}; +pub(crate) use self::share::{ShareChild, ShareKind, ShareRefusal}; /// Where the runtime binds its time status read-only in the root. pub(crate) const TIME_STATUS_TARGET: &str = "/run/nvx/time"; @@ -218,12 +218,12 @@ const RUNTIME_DIRECTORY_MODE: u32 = 0o755; /// The most bytes of options that `mount(2)` passes a file system whole: one 4 KiB page, the /// smallest into which Linux copies them, less the NUL that ends them. Linux cuts longer options /// short, so [`assemble`] refuses an overlay whose options are longer. -pub const MOUNT_OPTIONS_LIMIT: usize = 4095; +pub(crate) const MOUNT_OPTIONS_LIMIT: usize = 4095; /// The role of a block device in a layered root. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum Role { +pub(crate) enum Role { /// The read-only image: an EROFS file system, the overlay's lower layer, or a GPT disk, whose /// partitions after the first hold the overlay's lower layers. Distro, @@ -252,17 +252,17 @@ impl fmt::Display for Role { /// A block device that the VMM attaches for a role, which the base address of its MMIO window /// names, as the VMM declares it on the kernel command line. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Device { +pub(crate) struct Device { /// The base address of the device's MMIO window. - pub mmio: u64, + pub(crate) mmio: u64, /// The size that the device must have, in 512-byte sectors, if the caller expects one. - pub sectors: Option, + pub(crate) sectors: Option, } /// A step on a found block device, which an [`Error::Device`] names. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum DeviceStep { +pub(crate) enum DeviceStep { /// Reading its size. Size, /// Flushing its buffers. @@ -297,29 +297,29 @@ impl fmt::Display for DeviceStep { /// What [`assemble`] mounts, and where. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Assembly { +pub(crate) struct Assembly { /// The runtime directory: an absolute path in canonical form, not `/`, without an empty, `.`, /// or `..` component, or a trailing `/`, of at most /// [`MAX_PATH_BYTES`](super::mappings::MAX_PATH_BYTES) bytes, that no symbolic link leads /// through, and without `,`, `:`, `\`, `=`, or NUL, which the overlay's options reserve. - pub runtime: PathBuf, + pub(crate) runtime: PathBuf, /// The distro device, which holds the image. - pub distro: Device, + pub(crate) distro: Device, /// The image's format, and what the caller expects of it. - pub image: Image, + pub(crate) image: Image, /// The upper, which holds the overlay's upper and work directories. - pub upper: Upper, + pub(crate) upper: Upper, /// The overlay's mode, which selects its option set. - pub overlay: OverlayMode, + pub(crate) overlay: OverlayMode, /// How long to wait for each device to appear. - pub timeout: Duration, + pub(crate) timeout: Duration, } /// The image on the distro device, in the format that the caller names: an assembly never probes /// the device for another. #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] -pub enum Image { +pub(crate) enum Image { /// An EROFS file system: the overlay's one lower layer, which an assembly mounts read-only at /// `layers/distro`. Erofs { @@ -356,7 +356,7 @@ pub enum Image { /// directories on it. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum Upper { +pub(crate) enum Upper { /// A whole-device ext4 file system on the scratch device, which an assembly mounts /// `rw,nosuid,nodev` at `scratch`, and whose directories persist across boots. Scratch { @@ -376,7 +376,7 @@ pub enum Upper { impl Upper { /// Returns the upper's kind. - pub const fn kind(self) -> UpperKind { + pub(crate) const fn kind(self) -> UpperKind { match self { Self::Scratch { .. } => UpperKind::Scratch, Self::Tmpfs { .. } => UpperKind::Tmpfs, @@ -387,7 +387,7 @@ impl Upper { /// The kind of a root's upper. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum UpperKind { +pub(crate) enum UpperKind { /// An ext4 scratch, on the scratch device. Scratch, /// A tmpfs. @@ -397,7 +397,7 @@ pub enum UpperKind { /// Whether an ext4 scratch may have a journal, which [`assemble`] checks in the feature flags of /// its superblock before it mounts it. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Journal { +pub(crate) enum Journal { /// The scratch may have a journal or none, so the assembly reads nothing to check it. Allowed, /// The scratch may not have a journal: the assembly refuses a scratch whose ext4 superblock @@ -410,7 +410,7 @@ pub enum Journal { /// mounts without the set in effect. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum OverlayMode { +pub(crate) enum OverlayMode { /// `metacopy=on,xino=on`, the default, for lowers whose overlay xattrs the caller trusts: a /// change of a lower file's metadata copies up its metadata alone, renaming a lower directory /// records a redirect, and inode numbers stay unique across the layers. @@ -424,7 +424,7 @@ pub enum OverlayMode { impl OverlayMode { /// Returns the mode's option set, which follows the overlay's directories in its options. - pub const fn options(self) -> &'static str { + pub(crate) const fn options(self) -> &'static str { match self { Self::TrustedLowers => "metacopy=on,xino=on", Self::UntrustedLowers => "metacopy=off,redirect_dir=nofollow", @@ -475,7 +475,7 @@ impl OverlayMode { /// redirects when the upper can't hold the overlay's extended attributes. /// /// A failure unmounts what the assembly mounted, as [`Root::teardown`] does, and reports both. -pub fn assemble(assembly: &Assembly) -> Result { +pub(crate) fn assemble(assembly: &Assembly) -> Result { assemble_until(assembly, None) } @@ -616,7 +616,7 @@ fn assemble_with( /// A read-only layer of an assembled root, one of the overlay's lower directories. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Lower { +pub(crate) struct Lower { /// The layer's mount. mount: Mount, /// The node of the layer's block device. @@ -634,7 +634,7 @@ pub struct Lower { /// unmounts them the same way, and ignores failures, unless `Root::switch` has switched into the /// root: it then unmounts nothing. Until then, the root holds the lock of its runtime directory, /// where no other root assembles. -pub struct Root { +pub(crate) struct Root { /// The kernel that mounts and unmounts. kernel: Box, /// The runtime directory. @@ -684,12 +684,12 @@ pub struct Root { impl Root { /// Returns the root's original overlay mount point, `rootfs` in the runtime directory. /// This path is unchanged by `Root::switch`; paths then resolve inside the switched root. - pub fn path(&self) -> &Path { + pub(crate) fn path(&self) -> &Path { &self.path } /// Returns the runtime directory. - pub fn runtime(&self) -> &Path { + pub(crate) fn runtime(&self) -> &Path { &self.runtime } @@ -764,7 +764,7 @@ impl Root { /// /// Once `Root::switch` has switched into the root, the teardown refuses: it unmounts nothing, /// and its error's `TeardownError::is_switched` is true. - pub fn teardown(mut self) -> Result<(), TeardownError> { + pub(crate) fn teardown(mut self) -> Result<(), TeardownError> { self.unmount() } @@ -1798,7 +1798,7 @@ fn holds_entry(mut entries: impl Iterator>) -> io::Resul /// A mount of a layered root. #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum Mount { +pub(crate) enum Mount { /// The EROFS image, at `layers/distro` in the runtime directory. Distro, /// The layer in a partition of a GPT disk, at `layers/pN` in the runtime directory, for its @@ -1876,7 +1876,7 @@ impl fmt::Display for Mount { /// order of these variants. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum RuntimeMounts { +pub(crate) enum RuntimeMounts { /// procfs, at `/proc`. Proc, /// sysfs, at `/sys`. @@ -1911,7 +1911,7 @@ impl RuntimeMounts { /// Why a root operation failed. Its text names what failed, and the system's error, if any. #[derive(Debug)] #[non_exhaustive] -pub enum Error { +pub(crate) enum Error { /// The runtime directory's path is not absolute and canonical, holds a reserved character, /// or leads through a symbolic link. Runtime { @@ -2263,7 +2263,7 @@ impl std::error::Error for Error {} /// Why [`assemble`] failed, and how unmounting what it had mounted failed, if it did. #[derive(Debug)] -pub struct AssemblyError { +pub(crate) struct AssemblyError { /// The assembly's failure. error: Error, /// The failures to unmount what the assembly had mounted. @@ -2320,7 +2320,7 @@ impl std::error::Error for SwitchError {} /// The syncs and unmounts of a teardown that failed, in the order in which they were attempted, /// or the refusal of a root that has switched. #[derive(Debug)] -pub struct TeardownError { +pub(crate) struct TeardownError { /// The failures, which is empty only for the refusal of a root that has switched. failures: Vec, } @@ -2354,7 +2354,7 @@ impl std::error::Error for TeardownError {} /// An unmount that failed, which left its mount in place, or the sync of an ext4 scratch's file /// system that failed before its unmount, which the teardown then skips. #[derive(Debug)] -pub struct Unmount { +pub(crate) struct Unmount { /// The mount. mount: Mount, /// The step that failed. diff --git a/nvx-rs/src/guest/root/share.rs b/nvx-rs/src/guest/root/share.rs index 526ce122b..86a516290 100644 --- a/nvx-rs/src/guest/root/share.rs +++ b/nvx-rs/src/guest/root/share.rs @@ -27,7 +27,7 @@ const VIRTIOFS: &[u8] = b"virtiofs"; /// What a child of an aggregate share is: a host directory, or a host file. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -pub enum ShareKind { +pub(crate) enum ShareKind { /// A directory, bound at a directory of the root. #[default] Directory, @@ -47,22 +47,22 @@ impl ShareKind { /// A child of an aggregate share: an entry of the aggregate, bound at a target in the root. #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct ShareChild { +pub(crate) struct ShareChild { /// The name of the child's entry in the aggregate: letters, digits, `.`, `_`, and `-`, other /// than `.` and `..`. - pub name: String, + pub(crate) name: String, /// The absolute path in the root at which the child is bound. - pub target: PathBuf, + pub(crate) target: PathBuf, /// Whether the workload may write the child. - pub access: Access, + pub(crate) access: Access, /// What the child is. - pub kind: ShareKind, + pub(crate) kind: ShareKind, } /// Why a live share, or the host export, was refused before anything was mounted for it. #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] -pub enum ShareRefusal { +pub(crate) enum ShareRefusal { /// The virtio-fs tag is not a nonempty name of letters, digits, `_`, and `-`. Tag(String), /// An aggregate has no children. @@ -164,7 +164,12 @@ impl Root { /// /// A failure once the target passed its checks latches the root, and unmounts the device from /// `staging`, or leaves it there for the teardown if that unmount fails too. - pub fn mount_share(&mut self, tag: &str, target: &Path, access: Access) -> Result<(), Error> { + pub(crate) fn mount_share( + &mut self, + tag: &str, + target: &Path, + access: Access, + ) -> Result<(), Error> { let mount = Mount::Share { tag: tag.to_owned(), target: target.to_owned(), @@ -199,7 +204,7 @@ impl Root { /// /// A root has one aggregate at most, since every aggregate is mounted at the same place: once /// the root has mounted one, whether or not its children were all bound, another is refused. - pub fn mount_aggregate( + pub(crate) fn mount_aggregate( &mut self, tag: &str, access: Access, @@ -272,7 +277,7 @@ impl Root { /// A root has one export at most, since every export is mounted at the same place: once the /// root has mounted one, another is refused. A failure once the tag passed its check latches /// the root; an export that is mounted by then stays for the teardown. - pub fn mount_export(&mut self, tag: &str, access: Access) -> Result { + pub(crate) fn mount_export(&mut self, tag: &str, access: Access) -> Result { let export = Mount::Export { tag: tag.to_owned(), }; diff --git a/nvx-rs/src/guest/root/tests/view.rs b/nvx-rs/src/guest/root/tests/view.rs index eeed24152..081ca9d2d 100644 --- a/nvx-rs/src/guest/root/tests/view.rs +++ b/nvx-rs/src/guest/root/tests/view.rs @@ -154,7 +154,8 @@ fn a_later_bind_failure_stays_owned_and_its_teardown_removes_the_earlier_bind() #[cfg(target_arch = "x86_64")] #[test] fn actual_resolver_failures_keep_the_network_class_and_root_owned_bind() { - use crate::guest::activation::{ApplyError, NetworkError}; + use crate::guest::activation::ApplyError; + use crate::guest::activation::state::apply::NetworkError; for write_failure in [false, true] { let (guest, mut root, _, resolver) = prepared(); diff --git a/nvx-rs/src/guest/view.rs b/nvx-rs/src/guest/view.rs index 0caa42fa4..a95119cf6 100644 --- a/nvx-rs/src/guest/view.rs +++ b/nvx-rs/src/guest/view.rs @@ -1,8 +1,8 @@ //! The VM-wide hostname, machine ID, and resolver file of an assembled root. //! -//! This is unstable internal reference code of the guest's space, hidden from documentation; -//! it becomes crate-private once nothing outside NVX uses it. The root owns the view's binds: -//! its teardown removes them before its switch, and nothing unmounts them afterwards. +//! This module is part of the guest's space, nvx-init's internal reference code, private to the +//! crate. The root owns the view's binds: its teardown removes them before its switch, and +//! nothing unmounts them afterwards. use std::fs; use std::io; @@ -32,7 +32,7 @@ pub(crate) const RESOLVER_TARGET: &str = "/etc/resolv.conf"; /// The machine ID visible at `/etc/machine-id`. #[derive(Clone, Debug, Default, PartialEq, Eq)] #[non_exhaustive] -pub enum MachineId { +pub(crate) enum MachineId { /// The image's own `/etc/machine-id`, left untouched. #[default] Image, @@ -46,7 +46,7 @@ impl MachineId { /// whatever the umask: 32 lowercase hexadecimal digits and a newline. /// /// An existing file or symbolic link fails with `AlreadyExists` and is left alone. - pub fn generate(path: impl Into) -> io::Result { + pub(crate) fn generate(path: impl Into) -> io::Result { let path = path.into(); let mut id = [0u8; 16]; getrandom::fill(&mut id).map_err(|error| { @@ -84,7 +84,7 @@ impl MachineId { /// to set the hostname and mount, and refuses an invalid hostname, a switched or failed root, /// or a root whose view has already been attempted. System failures have kind `Io`; refusals /// have kind `Unsupported`. An error does not restore the previous hostname or remove files. -pub fn apply(root: &mut Root, view: &View, dns: Option) -> Result { +pub(crate) fn apply(root: &mut Root, view: &View, dns: Option) -> Result { apply_staged(root, view, dns).map_err(ApplyFailure::into_error) } diff --git a/nvx-rs/src/lib.rs b/nvx-rs/src/lib.rs index f7d333324..9c91c39fd 100644 --- a/nvx-rs/src/lib.rs +++ b/nvx-rs/src/lib.rs @@ -41,8 +41,8 @@ //! and the capture of a template's guest, which repairs its clones (`guest::snapshot`), and //! their activation (`guest::activation`). The crate's `nvx-*` guest binaries run the programs. //! NVX's own guest space, which assembles a layered root, reads GPT disks, mounts host mappings, -//! and applies a clone's activation, is internal reference code: hidden, unstable, and -//! crate-private once nothing outside NVX uses it. +//! and applies a clone's activation, is nvx-init's internal reference code, private to the +//! crate. //! //! # Example //!