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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions doc/openvmm-upstream-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
`61994293a9e5e3bcc1d685d708cf462920eed030`; see [Upstream status](#upstream-status).
- Mapped fork tip: `18f4039db9511a3db59a847e95fb1f1f06f2f235`, position 131. The graph, PR
summary, and commit mapping below cover positions 001-131 and the PR20 fixup at position 148.
- NVX's pinned OpenVMM revision: `bced18bec5c29c2ef43376ad443b52e3d0cddafb`, position 219, the
head of the `feature/microvm/release/v5.1` branch of microsoft/openvmm. The v5.1 rebase moved the
219 commits of `feature/microvm/dev` at the NVX pin before it, `db42fde04`, onto `ba105dd7f`
- NVX's pinned OpenVMM revision: `3cf58a5234ed824435825c9f87187f5129219724`, position 220.
Position 220, which microsoft/openvmm#4981 proposes for `feature/microvm/dev`, is one commit on
position 219, `bced18bec`, the head of `feature/microvm/dev` and of the
`feature/microvm/release/v5.1` branch of microsoft/openvmm. The v5.1 rebase moved the 219
commits of `feature/microvm/dev` at the NVX pin before that rebase, `db42fde04`, onto `ba105dd7f`
without dropping any: the 212 of `feature/microvm/release/v5.0` and the seven that
microsoft/openvmm#4953, microsoft/openvmm#4954, microsoft/openvmm#4955, microsoft/openvmm#4970,
microsoft/openvmm#4968, and microsoft/openvmm#4969 landed after it. Every commit keeps its
position, and only position 183, which conflicted with upstream, changed its patch; see
[Conflicts of the v5.1 rebase](#conflicts-of-the-v51-rebase). The pin is 88 commits past the
mapped tip on the same first-parent chain. Positions 132-219, except the PR20 fixup at position
[Conflicts of the v5.1 rebase](#conflicts-of-the-v51-rebase). The pin is 89 commits past the
mapped tip on the same first-parent chain. Positions 132-220, except the PR20 fixup at position
148, are not yet assigned to PRs; see
[Commits after the mapped tip](#commits-after-the-mapped-tip).
- Upstream main: `ba105dd7f00499acc1862456c179c0930cb2c8f7` (2026-11-06), the fork base itself;
Expand Down Expand Up @@ -879,8 +881,8 @@ ba105dd7f
-> long Windows paths [210] -> state change wait logging [211]
-> port forward bind code [212] -> virtio queue starts after restore [213-215]
-> storage generation block identity [216-217]
-> device start confirmation [218-219]
-> bced18bec (NVX pin)
-> device start confirmation [218-219] -> long Windows report paths [220]
-> 3cf58a523 (NVX pin)
```

For upstream review, PR01-PR06 and PR14 can be rebased independently onto current upstream main,
Expand All @@ -896,15 +898,16 @@ see [The v3.0 reorganization](#the-v30-reorganization).

## Commits after the mapped tip

The NVX pin extends the chain past position 131 without merges. Of the 88 later commits, position
The NVX pin extends the chain past position 131 without merges. Of the 89 later commits, position
148 is a PR20 fixup, and the others are not yet classified or assigned to PRs. Each of positions
132-187 combines the v2.4 commits that the table lists, as v3.0 positions 145-200 did, which
developed one concept; "(part)" marks a v2.4 fixup that split across commits. Positions 188-192
followed the v3.0 reorganization, positions 193-206 are the ABI restart of the v4.0
reorganization, positions 207-212 followed it, and positions 213-219 followed the v5.0
reorganization, so they combine none. Each commit has the position that it had in v5.0, since the
v5.1 rebase dropped none. A v4.0 position counts in `feature/microvm/dev` at `e1ca353eb`, whose
first 210 commits are those of `feature/microvm/release/v4.0`.
reorganization, positions 207-212 followed it, positions 213-219 followed the v5.0
reorganization, and position 220 followed the v5.1 rebase, so they combine none. Each commit
through position 219 has the position that it had in v5.0, since the v5.1 rebase dropped none. A
v4.0 position counts in `feature/microvm/dev` at `e1ca353eb`, whose first 210 commits are those of
`feature/microvm/release/v4.0`.

| Position | Commit | v4.0 position | v3.0 position | v2.4 positions | Topic |
| ---: | --- | ---: | ---: | --- | --- |
Expand Down Expand Up @@ -996,6 +999,7 @@ first 210 commits are those of `feature/microvm/release/v4.0`.
| 217 | `13f78d81a` | - | - | - | microVM: check the storage generation of a restore's sandbox blocks (microsoft/openvmm#4970) |
| 218 | `dbec9b624` | - | - | - | virtio: keep the progress of queues that a failed start stops (microsoft/openvmm#4968) |
| 219 | `bced18bec` | - | - | - | vmcore: confirm device starts before the VM runs (microsoft/openvmm#4969) |
| 220 | `3cf58a523` | - | - | - | microVM: publish the outcome report below long Windows paths (microsoft/openvmm#4981) |

The remarks that say whether commits apply to upstream main record what the v3.0 reorganization
found on its base, `b2676222b`, at the positions that the commits have now.
Expand Down Expand Up @@ -1049,8 +1053,11 @@ PR09's position 015, whose start now confirms each unit's start before it starts
dependents, and PR13's position 039, whose transports now leave a start's answer to that
confirmation instead of taking it in `poll_device`, and replaces one of position 039's tests; it
adds its confirmation next to the input quiesce of PR09's position 018 in the files that position
018 changes, and rewrites position 215's documentation. Assigning the later commits therefore means
revisiting those PRs, not only appending new ones.
018 changes, and rewrites position 215's documentation. Position 220 changes the outcome report
publication of PR24's position 122, which on Windows now publishes the report through the
extended-length form of its path, and moves the extended-length encoding that position 210 gives
`pal`'s secure creation into `pal`'s file system module, which both now use. Assigning the later
commits therefore means revisiting those PRs, not only appending new ones.

Position 178, by contrast, changes only `disk_blob`, which upstream main and the fork base
have alike and no other commit of the chain changes, so it can go upstream on its own. Position
Expand Down Expand Up @@ -1149,6 +1156,12 @@ and position 218's tests. Only `state_unit`'s requests, the virtio test transpor
device unit, and the device worker's request loop apply there. So both follow PR09 and PR13
upstream.

Position 220 builds on the chain too. Replaying it on upstream main conflicts in two of its three
files: the outcome report module, which PR24's position 122 adds and upstream main lacks, and
`pal`'s security module, whose encoding position 210 adds. Only its addition to `pal`'s file system
module applies there. So its report change follows PR24 upstream, and its `pal` change follows
position 210's.

## PR summary

Each count is of v5.1 commits, which include the fixups that the v3.0, v4.0, and v5.0
Expand Down
1 change: 1 addition & 0 deletions nvx-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ windows-sys = { version = "0.61", optional = true, features = [
"Win32_System_LibraryLoader",
"Win32_System_Pipes",
"Win32_System_Registry",
"Win32_System_SystemInformation",
"Win32_System_Threading",
"Win32_System_WindowsProgramming",
] }
Expand Down
41 changes: 24 additions & 17 deletions nvx-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,9 @@ when needed, so storage paths may exceed the legacy 248-character directory and
260-character file limits without enabling a machine-wide long-path setting.
The filesystem's component limits and Windows' extended-length path limit
(about 32,767 UTF-16 code units, including the resolved root and internal file
names) still apply. VM launches have a separate
[working-directory limit](#windows-working-directory-limit).
names) still apply. VM launches start their VMMs outside the store, so Windows'
[working-directory limit](#windows-working-directory-limit) does not bound
storage paths.

### `nvx::host::instance`

Expand All @@ -541,10 +542,11 @@ instance, with the caller's files and its directory, through the store.
`write_json` and `read_json` keep the configuration records that callers store
beside an instance.

An instance's directory is also its VMM's working directory. `check_launch` and
`LockedInstance::start` apply the
[Windows working-directory limit](#windows-working-directory-limit) before
instance creation or launch records, respectively.
`LockedInstance::start` starts OpenVMM in the working directory that
`supervisor::vmm_working_dir` chooses for the instance's directory: on Windows,
the system directory, so that the
[Windows working-directory limit](#windows-working-directory-limit) does not
bound the instance's directory.

### `nvx::host::nvxs`

Expand Down Expand Up @@ -649,10 +651,18 @@ reattach has verified the VM's exit.

Windows' process-creation API limits `VmmCommand::working_dir` to 258 UTF-16
code units in its absolute path, including any verbatim prefix and trailing
separator. It may name a shorter directory than the store. A raw `spawn`
refuses an overlong working directory with `InvalidInput`; high-level instance,
capture, and clone preflight return `Unsupported` with the limit in the
top-level message. Neither silently changes the requested directory.
separator, whatever the prefix and the host's long-path setting. A raw `spawn`
refuses an overlong working directory with `InvalidInput`, and never changes the
requested directory.

`vmm_working_dir` chooses the working directory of the VMM of a sandbox store:
on Windows, the system directory, such as `C:\Windows\System32`, which every
host has, whose path is short, and which only administrators may change, and on
other hosts, the store itself. OpenVMM resolves only the relative paths of its
arguments against its working directory, and every launch gives it absolute
ones. Instances, template captures, and clones start their VMMs there, so their
stores' paths may exceed the limit, and an orchestrator that launches a VMM
itself, as the supervisor's example does, starts it there too.

### `nvx::host::template`

Expand Down Expand Up @@ -991,13 +1001,10 @@ report, the snapshot's manifest and artifacts, and the key, and publishes the
transaction in two commits, a rename into `generations/` that replaces nothing,
then the index.

Capture preflight applies the
[Windows working-directory limit](#windows-working-directory-limit) to
`<template-store>\captures\<16-hex-slot>\transaction` before creating the slot
or scratch. Without a trailing separator this leaves 220 units for the absolute
template-store path, or 210 for a state root whose store is `<state>\templates`.
A clone's sandbox directory is also its VMM's working directory, which
`ClonePlan::launch` checks before writing launch records.
The VMMs of captures and clones start in the working directory that
`vmm_working_dir` chooses, the system directory on Windows, so the
[Windows working-directory limit](#windows-working-directory-limit) bounds
neither the template store's paths nor a clone's sandbox directory.

A capture that fails publishes nothing, and `Store::recover_captures` ends the
captures that a crash interrupted, publishing those that were checked, and
Expand Down
1 change: 1 addition & 0 deletions nvx-rs/public-api/linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2644,6 +2644,7 @@ fn nvx::host::supervisor::lock_spawns
fn nvx::host::supervisor::reattach
fn nvx::host::supervisor::spawn
fn nvx::host::supervisor::spawn_command
fn nvx::host::supervisor::vmm_working_dir
mod nvx::host::template
struct nvx::host::template::Acquired
field nvx::host::template::Acquired::lease
Expand Down
1 change: 1 addition & 0 deletions nvx-rs/public-api/windows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,7 @@ fn nvx::host::supervisor::lock_spawns
fn nvx::host::supervisor::reattach
fn nvx::host::supervisor::spawn
fn nvx::host::supervisor::spawn_command
fn nvx::host::supervisor::vmm_working_dir
mod nvx::host::template
struct nvx::host::template::Acquired
field nvx::host::template::Acquired::lease
Expand Down
17 changes: 12 additions & 5 deletions nvx-rs/src/host/instance/locked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use super::{
use crate::error::{Error, ErrorCode, Result};
use crate::host::launch::{LaunchSpec, timeout_problem};
use crate::host::process::KILL_TIMEOUT;
use crate::host::report;
use crate::host::session::{self, ControlSession, SessionError};
use crate::host::store::{LaunchId, LockedStore, RemovalError, RuntimeIdentity};
use crate::host::supervisor::{self, Reattached, StopOutcome, VmmCommand};
use crate::host::{platform, report};
use crate::protocol::GuestFeatures;

/// An instance whose lock this holds, through which its lifecycle transitions change its
Expand Down Expand Up @@ -84,6 +84,8 @@ impl LockedInstance {
/// objects they named when they were planned. It launches OpenVMM with
/// [`supervisor::launch`], whose journal records the launch before OpenVMM is spawned and its
/// process identity right after, so that [`find`](Self::find) ends a start whose caller died.
/// OpenVMM starts in the working directory that [`supervisor::vmm_working_dir`] chooses for
/// the instance's directory, which may then be as long as its store's paths may be.
/// The capability travels through OpenVMM's standard-input pipe, which is closed before
/// OpenVMM starts, and never appears in arguments, the environment, or the log. Once the guest
/// is ready, has the required features, and has mounted the mapped host paths, start commits
Expand All @@ -94,8 +96,7 @@ impl LockedInstance {
/// [`crate::ErrorKind::Unsupported`] when `timeout` is zero or
/// longer than [`MAX_TIMEOUT`](crate::host::launch::MAX_TIMEOUT), when the export plan maps a
/// read-only path that the host cannot keep read-only, when the specification fails
/// [`LaunchSpec::check`] or exceeds a limit, when the instance's directory exceeds Windows'
/// working-directory limit, or when OpenVMM's command line would not fit
/// [`LaunchSpec::check`] or exceeds a limit, or when OpenVMM's command line would not fit
/// [`PROCESS_COMMAND_LINE_LIMIT`](crate::host::launch::PROCESS_COMMAND_LINE_LIMIT), which holds on
/// every host; such a launch records nothing. Unlike [`crate::host::instance::check_launch`], start keeps no reserve.
/// A launch that does not become ready is terminated through its verified process identity;
Expand All @@ -107,7 +108,6 @@ impl LockedInstance {
timeout: Duration,
) -> Result<Started> {
check_timeout("start timeout", timeout, true)?;
platform::check_working_dir(self.instance.dir())?;
if let Some(plan) = &spec.filesystem {
plan.verify()?;
}
Expand All @@ -117,6 +117,13 @@ impl LockedInstance {
// fit is refused before the store records it.
let endpoint = control_endpoint(&instance.store.socket_path())?;
spec.fit_process_command_line(&spec.openvmm_arguments(&endpoint, &report)?, 0)?;
let working_dir = supervisor::vmm_working_dir(instance.dir()).map_err(|error| {
Error::io(format!(
"cannot find the working directory of the VMM of sandbox {}",
instance.name
))
.with_source(error)
})?;
let capability = generate_capability()?;
let launch = LaunchId::generate()?;
let started = Instant::now();
Expand All @@ -128,7 +135,7 @@ impl LockedInstance {
|endpoint| {
let arguments = spec.openvmm_arguments(endpoint, &report)?;
spec.fit_process_command_line(&arguments, 0)?;
let mut command = VmmCommand::new(&spec.openvmm, arguments, instance.dir());
let mut command = VmmCommand::new(&spec.openvmm, arguments, &working_dir);
command.breakaway_from_job = spec.breakaway_from_job;
Ok(command)
},
Expand Down
7 changes: 3 additions & 4 deletions nvx-rs/src/host/instance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ fn control_endpoint(socket: &Path) -> Result<String> {
.map_err(|error| Error::io("cannot choose a control endpoint").with_source(error))
}

/// Fails with [`ErrorKind::Unsupported`] if the working directory `dir` exceeds Windows' limit,
/// or if OpenVMM's command line for `spec`, launched for an instance in `dir`, would not fit the
/// limit of a Windows process command line; see [`LaunchSpec::check_process_command_line`].
/// Fails with [`ErrorKind::Unsupported`] if OpenVMM's command line for `spec`, launched for an
/// instance in `dir`, would not fit the limit of a Windows process command line; see
/// [`LaunchSpec::check_process_command_line`].
///
/// The directory need not exist yet, so that a caller can check a launch before it creates the
/// instance. Every control endpoint of an instance has the same length.
Expand All @@ -110,7 +110,6 @@ pub fn check_launch(dir: &Path, spec: &LaunchSpec) -> Result<()> {
"cannot resolve the instance directory {}",
dir.display()
)))?;
platform::check_working_dir(&dir)?;
let endpoint = control_endpoint(&dir.join(SOCKET_NAME))?;
spec.check_process_command_line(&endpoint, &dir.join(OUTCOME_NAME))
}
Expand Down
31 changes: 12 additions & 19 deletions nvx-rs/src/host/instance/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,35 +215,28 @@ mod store {

#[cfg(windows)]
#[test]
fn working_directory_limits_are_checked_before_instance_launches() {
fn launches_of_instances_beyond_the_working_directory_limit_are_checked_alike() {
use std::os::windows::ffi::OsStrExt;

let directory = tempfile::tempdir().unwrap();
let mut parent = directory.path().to_path_buf();
let mut parent = directory.path().join("long-state-root-component");
while parent.as_os_str().encode_wide().count() < 300 {
parent.push("long-state-root-component");
}
fs::create_dir_all(&parent).unwrap();
let dir = parent.join("vm");
let spec = LaunchSpec::new(
let mut spec = LaunchSpec::new(
Artifacts::new("openvmm", "vmlinux", "initramfs.cpio.gz"),
crate::Hypervisor::Whp,
);
// The VMM starts in the system directory, so the instance's directory may exceed the 258
// UTF-16 code units of a process's working directory, and only the command line's limit
// holds.
check_launch(&dir, &spec).unwrap();
spec.openvmm = directory
.path()
.join("o".repeat(PROCESS_COMMAND_LINE_LIMIT));
let error = check_launch(&dir, &spec).unwrap_err();
assert_eq!(error.kind(), ErrorKind::Unsupported);
assert!(error.message().contains("working directory"));
assert!(error.message().contains("258"));
assert!(!dir.exists());

let instance = Instance::create(&dir, "test", CallerFiles::new(CONFIG).unwrap()).unwrap();
let mut locked = instance.lock(None).unwrap();
let error = locked
.start(&spec, &runtime(), Duration::from_secs(1))
.unwrap_err();
assert_eq!(error.kind(), ErrorKind::Unsupported);
assert!(error.message().contains("working directory"));
assert!(error.message().contains("258"));
assert!(instance.store.journal().unwrap().is_none());
assert!(!dir.join(LOG_NAME).exists());
assert_eq!(error.kind(), ErrorKind::Unsupported, "{error}");
assert!(!parent.exists());
Comment thread
ppenna marked this conversation as resolved.
Resolved
}
}
7 changes: 4 additions & 3 deletions nvx-rs/src/host/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@ fn host_identity(wanted: bool) -> Option<(u32, u32)> {

/// Everything that OpenVMM's command line for one VM depends on.
///
/// Paths are used as given, and OpenVMM runs in the instance's directory, so make them absolute,
/// for example with [`absolute`](super::artifacts::absolute). The other settings must pass
/// [`check`](Self::check), which every method that builds a command line applies.
/// Paths are used as given, and OpenVMM does not run in the caller's directory, but in the one
/// that [`vmm_working_dir`](super::supervisor::vmm_working_dir) chooses for the instance, so make
/// them absolute, for example with [`absolute`](super::artifacts::absolute). The other settings
/// must pass [`check`](Self::check), which every method that builds a command line applies.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LaunchSpec {
/// The `openvmm` executable.
Expand Down
Loading
Loading