diff --git a/nvx-rs/README.md b/nvx-rs/README.md
index 3019c6ebc..f9e4f884a 100644
--- a/nvx-rs/README.md
+++ b/nvx-rs/README.md
@@ -970,13 +970,15 @@ compatibility wrapper.
Verified process references. A `ProcessRef` names one process by its ID, its
start time, and the `BootId` of the host boot during which it started.
-`Process::open` checks a reference and holds its process through a pidfd on
-Linux or a process handle on Windows, whether or not the caller started it, so
-that waits sleep until the process exits and termination never reaches a process
-that reuses its ID. A reference from an earlier boot names an exited process,
-and a process whose identity this user cannot check, such as another user's,
-makes `Process::open` fail rather than report an exit. Linux hosts without
-pidfds poll the reference instead, and cannot terminate the process.
+`Process::open` returns `Opened::Running(Process)` only after checking the
+recorded boot, ID, and start time. It holds the process through a pidfd on
+Linux or a process handle on Windows, whether or not the caller started it,
+so that waits and termination never reach a process that reuses its ID.
+`Opened::Exited(Exited)` proves that the recorded boot ended or no running
+process matches both its ID and start time. A process whose identity this
+user cannot check makes `Process::open` fail rather than report an exit.
+Linux hosts without pidfds poll the reference instead, and cannot terminate
+the process.
On Linux and Windows, `Process::exit_handle()` returns
`Result>`: an independently owned duplicate of the pidfd
@@ -988,8 +990,8 @@ Duplication failures have kind `Io` and retain their original I/O source.
No watcher thread, future or callback is created by the library.
`Process::wait(deadline)` returns `Some(Exited)` only after verified exit, or
-`None` when the deadline passes without confirmation. `Exited` cannot be made
-by a caller.
+`None` when the deadline passes without confirmation. `Exited` proves exit,
+not an exit status, and cannot be made by a caller.
`Process::bytes_read` reports how many bytes the process has read through the
operating system's read calls, whether the host's cache served them or not:
diff --git a/nvx-rs/public-api/linux.txt b/nvx-rs/public-api/linux.txt
index 2d90a5759..edc5c8d46 100644
--- a/nvx-rs/public-api/linux.txt
+++ b/nvx-rs/public-api/linux.txt
@@ -2448,6 +2448,11 @@ method nvx::host::process::BootId::current
method nvx::host::process::BootId::from_bytes
type nvx::host::process::ExitHandle
struct nvx::host::process::Exited
+enum nvx::host::process::Opened
+variant nvx::host::process::Opened::Exited
+field nvx::host::process::Opened::Exited::0
+variant nvx::host::process::Opened::Running
+field nvx::host::process::Opened::Running::0
struct nvx::host::process::Process
method nvx::host::process::Process::bytes_read
method nvx::host::process::Process::exit_handle
diff --git a/nvx-rs/public-api/windows.txt b/nvx-rs/public-api/windows.txt
index c1e2bc8cf..e45f6cacf 100644
--- a/nvx-rs/public-api/windows.txt
+++ b/nvx-rs/public-api/windows.txt
@@ -1034,6 +1034,11 @@ method nvx::host::process::BootId::current
method nvx::host::process::BootId::from_bytes
type nvx::host::process::ExitHandle
struct nvx::host::process::Exited
+enum nvx::host::process::Opened
+variant nvx::host::process::Opened::Exited
+field nvx::host::process::Opened::Exited::0
+variant nvx::host::process::Opened::Running
+field nvx::host::process::Opened::Running::0
struct nvx::host::process::Process
method nvx::host::process::Process::bytes_read
method nvx::host::process::Process::exit_handle
diff --git a/nvx-rs/src/host/process.rs b/nvx-rs/src/host/process.rs
index 100abe93d..a24ed8776 100644
--- a/nvx-rs/src/host/process.rs
+++ b/nvx-rs/src/host/process.rs
@@ -2,10 +2,11 @@
//!
//! A [`ProcessRef`] names one process by its ID, its start time, and the [`BootId`] of the host
//! boot during which it started, which together never name another process. [`Process::open`]
-//! checks a reference against the host and holds the process that it names through a handle of
-//! the operating system: a pidfd on Linux, and a process handle on Windows. Waits and termination
-//! go through that handle, so they reach the referenced process, whether or not the caller
-//! started it, and never another process that reuses its ID. A process that does not match its
+//! checks a reference against the host and returns [`Opened::Running`] with the process held
+//! through a handle of the operating system, or [`Opened::Exited`] with proof that the recorded
+//! process exited. The handle is a pidfd on Linux, and a process handle on Windows. Waits and
+//! termination go through that handle, so they reach the referenced process, whether or not the
+//! caller started it, and never another process that reuses its ID. A process that does not match its
//! reference is never signaled, and neither is one that a reference from an earlier boot names:
//! every process of that boot has exited.
//!
@@ -188,14 +189,23 @@ pub struct Process {
handle: platform::ProcessHandle,
}
-/// Proof that a verified process exited, produced only by [`Process::wait`].
+/// The result of checking a recorded process reference against this host.
+#[derive(Debug)]
+pub enum Opened {
+ /// The process matched the recorded boot, ID, and start time; the caller owns its `Process`.
+ Running(Process),
+ /// The recorded process exited: its boot ended, or no running process matches its identity.
+ Exited(Exited),
+}
+
+/// Proof that a verified process exited, produced only by [`Process::wait`] or [`Process::open`].
///
/// It proves exit, not the process's exit status, and cannot be constructed by a caller.
#[derive(Debug)]
pub struct Exited(());
impl Process {
- /// Finds the process that `reference` names, and returns `None` if it has exited.
+ /// Finds the process that `reference` names, or returns proof that it has exited.
///
/// The process has exited when no process has its ID, when the process that has it started
/// at another time, and when the reference comes from another boot.
@@ -207,12 +217,15 @@ impl Process {
/// other users. Fails with [`io::ErrorKind::InvalidInput`] for a process ID that the host
/// never assigns, such as 0, and with [`io::ErrorKind::Unsupported`] on hosts other than
/// Linux and Windows.
- pub fn open(reference: &ProcessRef) -> io::Result > {
+ pub fn open(reference: &ProcessRef) -> io::Result {
let handle = open_reference(reference, BootId::current, platform::open_process)?;
- Ok(handle.map(|handle| Self {
- reference: reference.clone(),
- handle,
- }))
+ Ok(match handle {
+ Some(handle) => Opened::Running(Self {
+ reference: reference.clone(),
+ handle,
+ }),
+ None => Opened::Exited(Exited(())),
+ })
}
/// Returns the process that `handle` holds, whose identity the caller verified to be
diff --git a/nvx-rs/src/host/process/tests.rs b/nvx-rs/src/host/process/tests.rs
index a90c83102..85f4b54d8 100644
--- a/nvx-rs/src/host/process/tests.rs
+++ b/nvx-rs/src/host/process/tests.rs
@@ -182,6 +182,21 @@ fn sleeper() -> std::process::Command {
#[cfg(any(target_os = "linux", windows))]
struct ExitChild(std::process::Child);
+#[cfg(any(target_os = "linux", windows))]
+impl ExitChild {
+ fn spawn() -> Self {
+ Self(crate::host::tests::spawn_locked(&mut sleeper()).unwrap())
+ }
+
+ fn reference(&self) -> ProcessRef {
+ ProcessRef {
+ pid: self.0.id(),
+ start_time: platform::process_start_time(self.0.id()).unwrap().unwrap(),
+ boot: BootId::current().unwrap(),
+ }
+ }
+}
+
#[cfg(any(target_os = "linux", windows))]
impl Drop for ExitChild {
fn drop(&mut self) {
@@ -224,7 +239,9 @@ fn exit_handles_are_independently_owned_and_exist_only_for_exit_events() {
start_time: platform::process_start_time(child.0.id()).unwrap().unwrap(),
boot: BootId::current().unwrap(),
};
- let process = Process::open(&reference).unwrap().unwrap();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the child runs");
+ };
let handle = process.exit_handle().unwrap();
assert_eq!(handle.is_some(), process.wait_method() == WaitMethod::Event);
if handle.is_some() {
@@ -239,6 +256,47 @@ fn exit_handles_are_independently_owned_and_exist_only_for_exit_events() {
}
}
+#[cfg(any(target_os = "linux", windows))]
+#[test]
+fn opening_a_recorded_child_returns_running_only_for_its_identity() {
+ let mut child = ExitChild::spawn();
+ let reference = child.reference();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the recorded child runs");
+ };
+ assert_eq!(process.reference(), &reference);
+ assert!(!process.has_exited().unwrap());
+ let mut other_boot = *reference.boot.as_bytes();
+ other_boot[0] ^= 1;
+ for exited in [
+ ProcessRef {
+ boot: BootId::from_bytes(other_boot),
+ ..reference.clone()
+ },
+ ProcessRef {
+ start_time: reference.start_time + 1,
+ ..reference.clone()
+ },
+ ] {
+ assert!(matches!(Process::open(&exited).unwrap(), Opened::Exited(_)));
+ }
+ assert!(child.0.try_wait().unwrap().is_none());
+ assert!(!process.has_exited().unwrap());
+}
+
+#[cfg(any(target_os = "linux", windows))]
+#[test]
+fn opening_a_reaped_recorded_child_proves_exit() {
+ let mut child = ExitChild::spawn();
+ let reference = child.reference();
+ child.0.kill().unwrap();
+ child.0.wait().unwrap();
+ assert!(matches!(
+ Process::open(&reference).unwrap(),
+ Opened::Exited(_)
+ ));
+}
+
#[cfg(any(target_os = "linux", windows))]
#[test]
fn polling_follows_a_real_process_until_it_exits() {
@@ -347,7 +405,9 @@ fn processes_count_the_bytes_that_their_read_calls_read() {
start_time: platform::process_start_time(child.id()).unwrap().unwrap(),
boot: BootId::current().unwrap(),
};
- let process = Process::open(&reference).unwrap().unwrap();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the reader runs");
+ };
let counted = process.bytes_read();
let mut stdin = child.stdin.take().unwrap();
stdin.write_all(b"x\n").unwrap();
diff --git a/nvx-rs/src/host/process/tests/linux.rs b/nvx-rs/src/host/process/tests/linux.rs
index a2a0cf72f..50bb942b0 100644
--- a/nvx-rs/src/host/process/tests/linux.rs
+++ b/nvx-rs/src/host/process/tests/linux.rs
@@ -144,13 +144,14 @@ fn a_process_runs_until_its_last_thread_exits() {
thread::sleep(POLL_INTERVAL);
}
assert_eq!(platform::process_start_time(pid).unwrap(), Some(start_time));
- let process = Process::open(&ProcessRef {
+ let Opened::Running(process) = Process::open(&ProcessRef {
pid,
start_time,
boot: BootId::current().unwrap(),
})
- .unwrap()
- .expect("a process whose main thread exited still runs");
+ .unwrap() else {
+ panic!("a process whose main thread exited still runs");
+ };
assert!(
process
.wait(Instant::now() + Duration::from_millis(100))
@@ -197,13 +198,16 @@ fn a_process_whose_command_name_is_not_utf8_can_be_examined() {
start_time: platform::process_start_time(pid).unwrap().unwrap(),
boot: BootId::current().unwrap(),
};
- assert!(Process::open(&reference).unwrap().is_some());
+ assert!(matches!(
+ Process::open(&reference).unwrap(),
+ Opened::Running(_)
+ ));
// A reference to a process that exited and whose ID this one reused names no process.
let stale = ProcessRef {
start_time: reference.start_time + 1,
..reference
};
- assert!(Process::open(&stale).unwrap().is_none());
+ assert!(matches!(Process::open(&stale).unwrap(), Opened::Exited(_)));
child.0.kill().unwrap();
child.0.wait().unwrap();
}
diff --git a/nvx-rs/src/host/restore/tests.rs b/nvx-rs/src/host/restore/tests.rs
index b302590b0..f163d92da 100644
--- a/nvx-rs/src/host/restore/tests.rs
+++ b/nvx-rs/src/host/restore/tests.rs
@@ -12,7 +12,7 @@ use crate::activation::{
OverlayMode, ReadyActivation, RestoreSection, TransactionId, Upper, View,
};
use crate::host::platform::inherit::spawn_command;
-use crate::host::process::BootId;
+use crate::host::process::{BootId, Opened};
use crate::host::vm::create_private_dir;
use crate::protocol::restore_ready::parse_ready_event;
@@ -25,13 +25,15 @@ fn vm_directory() -> (tempfile::TempDir, VmDir) {
}
fn held_process(pid: u32) -> Process {
- Process::open(&ProcessRef {
+ let Opened::Running(process) = Process::open(&ProcessRef {
pid,
start_time: platform::process_start_time(pid).unwrap().unwrap(),
boot: BootId::current().unwrap(),
})
- .unwrap()
- .unwrap()
+ .unwrap() else {
+ panic!("the fixture process runs");
+ };
+ process
}
/// A separate process, held before it may connect, and alive until the test ends it.
diff --git a/nvx-rs/src/host/store/locked.rs b/nvx-rs/src/host/store/locked.rs
index 91d1dd322..67e6d269a 100644
--- a/nvx-rs/src/host/store/locked.rs
+++ b/nvx-rs/src/host/store/locked.rs
@@ -20,7 +20,7 @@ use super::{CLAIM_RELEASE_WAIT, LOCK_RETRY_INTERVAL, SandboxStore, record, store
use crate::error::{Error, ErrorCode, Result};
use crate::host::filesystem::FileIdentity;
use crate::host::platform;
-use crate::host::process::{BootId, Process, ProcessRef};
+use crate::host::process::{BootId, Opened, Process, ProcessRef};
use crate::protocol::CAPABILITY_LEN;
/// The store of a sandbox whose lock this holds, through which its lifecycle transitions change
@@ -357,7 +357,12 @@ impl LockedStore {
/// Ends the launch that `journal` records, and removes its files once nothing of it runs.
pub(super) fn end_launch(&self, journal: &LaunchJournal) -> Result {
let recovery = match journal.process() {
- Some(process) => self.end_identified(journal, process, Process::open),
+ Some(process) => self.end_identified(journal, process, |reference| {
+ Ok(match Process::open(reference)? {
+ Opened::Running(process) => Some(process),
+ Opened::Exited(_) => None,
+ })
+ }),
None => self.end_unidentified(journal),
};
if !matches!(recovery, Recovery::Indeterminate(_)) {
diff --git a/nvx-rs/src/host/store/vmm.rs b/nvx-rs/src/host/store/vmm.rs
index 4453d2fa6..f4a66918c 100644
--- a/nvx-rs/src/host/store/vmm.rs
+++ b/nvx-rs/src/host/store/vmm.rs
@@ -5,7 +5,7 @@ use std::io;
use std::time::Instant;
use crate::host::platform;
-use crate::host::process::{BootId, KILL_TIMEOUT, Process, ProcessRef};
+use crate::host::process::{BootId, KILL_TIMEOUT, Opened, Process, ProcessRef};
/// Finds the live process `pid` of the current boot and holds it, or returns `None` if it has
/// exited.
@@ -16,10 +16,14 @@ pub(super) fn find(pid: u32) -> io::Result> {
let Some(start_time) = platform::process_start_time(pid)? else {
return Ok(None);
};
- Process::open(&ProcessRef {
+ let opened = Process::open(&ProcessRef {
pid,
start_time,
boot: BootId::current()?,
+ })?;
+ Ok(match opened {
+ Opened::Running(process) => Some(process),
+ Opened::Exited(_) => None,
})
}
diff --git a/nvx-rs/src/host/supervisor/reattach.rs b/nvx-rs/src/host/supervisor/reattach.rs
index 5563c4269..c04037bf9 100644
--- a/nvx-rs/src/host/supervisor/reattach.rs
+++ b/nvx-rs/src/host/supervisor/reattach.rs
@@ -5,7 +5,7 @@ use std::io;
use super::vmm::Vmm;
use crate::error::Result;
-use crate::host::process::{Process, ProcessRef};
+use crate::host::process::{Opened, Process, ProcessRef};
use crate::host::store::{
IdentityCheck, IdentityPart, LockedStore, Recovery, RuntimeIdentity, RuntimeRecord,
SandboxStore,
@@ -65,7 +65,7 @@ pub enum Reattached {
/// can. Fails with [`ErrorKind::Unsupported`](crate::ErrorKind::Unsupported) for an invalid
/// `runtime`, and as [`LockedStore::recover`] does for inconsistent records.
pub fn reattach(locked: &mut LockedStore, runtime: &RuntimeIdentity) -> Result {
- reattach_with(locked, runtime, Process::open, |_| Ok(()))
+ reattach_reporting(locked, runtime, |_| Ok(()))
}
/// Reattaches as [`reattach`] does, and calls `exited` with the runtime record of a VMM that it
@@ -77,7 +77,17 @@ pub(crate) fn reattach_reporting(
runtime: &RuntimeIdentity,
exited: impl FnOnce(&RuntimeRecord) -> Result<()>,
) -> Result {
- reattach_with(locked, runtime, Process::open, exited)
+ reattach_with(
+ locked,
+ runtime,
+ |reference| {
+ Ok(match Process::open(reference)? {
+ Opened::Running(process) => Some(process),
+ Opened::Exited(_) => None,
+ })
+ },
+ exited,
+ )
}
/// Reattaches as [`reattach_reporting`] does, holding the recorded VMM with `open`, which is
diff --git a/nvx-rs/src/host/template/clone/tests/launch.rs b/nvx-rs/src/host/template/clone/tests/launch.rs
index 7ec87eec6..ccd088aa8 100644
--- a/nvx-rs/src/host/template/clone/tests/launch.rs
+++ b/nvx-rs/src/host/template/clone/tests/launch.rs
@@ -18,7 +18,7 @@ use super::*;
use crate::activation::{ReadyActivation, StoreGeneration, TransactionId};
use crate::host::artifacts;
use crate::host::platform::Transport;
-use crate::host::process::{BootId, Process, ProcessRef};
+use crate::host::process::{BootId, Opened, Process, ProcessRef};
use crate::host::snapshot::tests::artifact;
use crate::host::store::files::{sync_dir, write_private};
use crate::host::store::{
@@ -361,9 +361,9 @@ impl VmmDir {
/// before it asks, since a process that has exited no longer opens.
pub(super) fn end_and_wait(&self) {
let reference = self.reference.as_ref().expect("the test knows the VMM");
- let process = Process::open(reference)
- .unwrap()
- .expect("the VMM runs until the test ends it");
+ let Opened::Running(process) = Process::open(reference).unwrap() else {
+ panic!("the VMM runs until the test ends it");
+ };
self.end();
assert!(process.wait(Instant::now() + TIMEOUT).unwrap().is_some());
}
@@ -371,7 +371,7 @@ impl VmmDir {
/// Waits until the VMM, which exits on its own, as its [plan](Self::plan) asks, has exited.
pub(super) fn wait_exit(&self) {
let reference = self.reference.as_ref().expect("the test knows the VMM");
- if let Some(process) = Process::open(reference).unwrap() {
+ if let Opened::Running(process) = Process::open(reference).unwrap() {
assert!(process.wait(Instant::now() + TIMEOUT).unwrap().is_some());
}
}
@@ -412,7 +412,7 @@ impl Drop for VmmDir {
fn drop(&mut self) {
let _ = fs::write(self.path().join(EXIT_MARKER), b"");
if let Some(reference) = &self.reference
- && let Ok(Some(process)) = Process::open(reference)
+ && let Ok(Opened::Running(process)) = Process::open(reference)
&& !process
.wait(Instant::now() + TIMEOUT)
.is_ok_and(|exited| exited.is_some())
@@ -2301,7 +2301,9 @@ fn a_reattach_takes_the_report_of_a_vmm_whose_launch_a_recovery_of_the_sandbox_e
fn a_launch_whose_caller_crashes_once_its_vmm_started_ends_the_vmm_as_reattach_recovers_it() {
let crashed = Crashed::at("launched");
let reference = crashed.vmm.reference.clone().unwrap();
- let process = Process::open(&reference).unwrap().unwrap();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the crashed caller's VMM runs");
+ };
// The VMM listens beside the snapshot on Linux, as it has once it tried to send its event.
crashed.vmm.started();
assert_eq!(exists(&crashed.socket()), cfg!(target_os = "linux"));
@@ -2338,7 +2340,9 @@ fn a_restore_whose_caller_crashes_before_its_commit_ends_its_vmm_as_reattach_rec
for step in ["activating", "activated", "starting", "started"] {
let crashed = Crashed::at(step);
let reference = crashed.vmm.reference.clone().unwrap();
- let process = Process::open(&reference).unwrap().unwrap();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the crashed caller's VMM runs");
+ };
// The caller read the VMM's restore-ready event, whose flush waited for that on Windows.
assert!(crashed.vmm.started().ends_with(" true"), "{step}");
assert!(crashed.vmm.flushed(), "{step}");
@@ -3663,7 +3667,10 @@ fn a_reattach_that_cannot_record_a_failure_ends_and_forgets_nothing_of_the_clone
.journal()
.unwrap()
.expect("the reattach kept the journal");
- assert!(Process::open(journal.process().unwrap()).unwrap().is_some());
+ assert!(matches!(
+ Process::open(journal.process().unwrap()).unwrap(),
+ Opened::Running(_)
+ ));
crashed.refuses_release(&mut locked);
// Once the record can be written, the reattach ends the launch, and records its failure.
fs::remove_dir(&path).unwrap();
@@ -3719,7 +3726,7 @@ fn a_reattach_that_cannot_record_a_failure_ends_and_forgets_nothing_of_the_clone
.unwrap()
.expect("the reattach kept the record");
assert!(
- Process::open(record.process()).unwrap().is_some(),
+ matches!(Process::open(record.process()).unwrap(), Opened::Running(_)),
"{evidence:?}"
);
crashed.refuses_release(&mut locked);
@@ -3775,7 +3782,10 @@ fn a_reattach_makes_the_record_of_an_earlier_failure_durable_before_it_ends_the_
.journal()
.unwrap()
.expect("the reattach kept the journal");
- assert!(Process::open(journal.process().unwrap()).unwrap().is_some());
+ assert!(matches!(
+ Process::open(journal.process().unwrap()).unwrap(),
+ Opened::Running(_)
+ ));
crashed.refuses_release(&mut locked);
// One that can syncs the sandbox's directory before it ends the launch.
let synced = RefCell::new(Vec::new());
diff --git a/nvx-rs/src/host/template/clone/tests/restore.rs b/nvx-rs/src/host/template/clone/tests/restore.rs
index 61282c561..54d8f45eb 100644
--- a/nvx-rs/src/host/template/clone/tests/restore.rs
+++ b/nvx-rs/src/host/template/clone/tests/restore.rs
@@ -20,7 +20,7 @@ use super::launch::{
use super::*;
use crate::activation::{ReadyActivation, RestoreSection, TransactionId};
use crate::host::platform::Transport;
-use crate::host::process::{BootId, Process};
+use crate::host::process::{BootId, Opened, Process};
use crate::host::store::{CLONE_ACTIVATION_NAME, Recovery};
use crate::host::supervisor::{Starting, StopOutcome};
use crate::host::template::clone::launch::Hooks;
@@ -421,7 +421,7 @@ fn vmm_with(plan: &str) -> VmmDir {
/// Checks that the VMM of `vmm` has exited.
fn assert_vmm_ended(vmm: &VmmDir) {
let reference = vmm.reference.as_ref().unwrap();
- if let Some(process) = Process::open(reference).unwrap() {
+ if let Opened::Running(process) = Process::open(reference).unwrap() {
assert!(process.wait(Instant::now() + TIMEOUT).unwrap().is_some());
}
}
@@ -1193,7 +1193,10 @@ fn a_launch_that_cannot_be_ended_keeps_its_records_and_its_lease_until_a_reattac
assert_eq!(refusal.code(), ErrorCode::SandboxRunning, "{refusal}");
let records = CloneRecords::read(locked).unwrap().unwrap();
assert_eq!(records.journal.unwrap().process(), Some(&reference));
- assert!(Process::open(&reference).unwrap().is_some());
+ assert!(matches!(
+ Process::open(&reference).unwrap(),
+ Opened::Running(_)
+ ));
// A reattach ends the launch through its VMM's verified identity: the record keeps the
// failure's stage, evidence, and original outcome, with the teardown that resolved it.
let mut control = FakeControl::default();
diff --git a/nvx-rs/src/host/template/store/leases/tests.rs b/nvx-rs/src/host/template/store/leases/tests.rs
index eaeed151c..2a8b6898d 100644
--- a/nvx-rs/src/host/template/store/leases/tests.rs
+++ b/nvx-rs/src/host/template/store/leases/tests.rs
@@ -15,7 +15,7 @@ use serde_json::{Value, json};
use super::*;
use crate::activation::TemplateReference;
-use crate::host::process::{Process, ProcessRef};
+use crate::host::process::{Opened, Process, ProcessRef};
use crate::host::snapshot::tests::{ManifestSpec, artifact, network_class};
use crate::host::store::files::write_private;
use crate::host::store::{
@@ -2459,7 +2459,7 @@ impl Drop for VmmDir {
fn drop(&mut self) {
let _ = fs::write(self.path().join(EXIT_MARKER), b"");
if let Some(reference) = &self.reference
- && let Ok(Some(process)) = Process::open(reference)
+ && let Ok(Opened::Running(process)) = Process::open(reference)
&& !process
.wait(Instant::now() + TIMEOUT)
.is_ok_and(|exited| exited.is_some())
@@ -2625,7 +2625,9 @@ fn a_clone_that_crashes_before_its_spawn_keeps_its_lease_until_its_launch_ends()
fn a_clone_that_crashes_after_its_spawn_keeps_its_lease_until_its_unrecorded_vmm_exits() {
let crashed = Crashed::after("after-spawn");
let reference = crashed.vmm.reference.clone().unwrap();
- let process = Process::open(&reference).unwrap().unwrap();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the unrecorded VMM runs");
+ };
let mut locked = crashed.lock();
assert_eq!(
locked.journal().unwrap().unwrap().state(),
@@ -2656,7 +2658,9 @@ fn a_clone_that_crashes_after_its_spawn_keeps_its_lease_until_its_unrecorded_vmm
fn a_clone_that_crashes_after_recording_its_vmm_keeps_its_lease_until_reattach_ends_it() {
let crashed = Crashed::after("after-identity");
let reference = crashed.vmm.reference.clone().unwrap();
- let process = Process::open(&reference).unwrap().unwrap();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the recorded VMM runs");
+ };
let mut locked = crashed.lock();
let journal = locked.journal().unwrap().unwrap();
assert_eq!(journal.launch(), crashed.lease.launch());
diff --git a/nvx-rs/src/host/transport.rs b/nvx-rs/src/host/transport.rs
index 5ab40b105..bd171ded9 100644
--- a/nvx-rs/src/host/transport.rs
+++ b/nvx-rs/src/host/transport.rs
@@ -47,7 +47,7 @@ use std::thread;
use std::time::{Duration, Instant};
use super::platform;
-use super::process::{Process, ProcessRef};
+use super::process::{Opened, Process, ProcessRef};
use crate::protocol::CAPABILITY_LEN;
use crate::protocol::nvxs::{
ErrorCode, HostEvent, HostSession, Identity, RecordType, SessionError,
@@ -76,9 +76,12 @@ pub fn connect_verified(
reference: &ProcessRef,
deadline: Instant,
) -> Result> {
- let process = Process::open(reference)
+ let process = match Process::open(reference)
.map_err(|error| Error::io("cannot verify the endpoint's VM process").with_source(error))?
- .ok_or_else(exited)?;
+ {
+ Opened::Running(process) => process,
+ Opened::Exited(_) => return Err(exited()),
+ };
let mut unavailable = None;
loop {
check_running(&process)?;
diff --git a/nvx-rs/src/host/transport/tests.rs b/nvx-rs/src/host/transport/tests.rs
index 2136f8fbb..eb449723e 100644
--- a/nvx-rs/src/host/transport/tests.rs
+++ b/nvx-rs/src/host/transport/tests.rs
@@ -166,13 +166,10 @@ fn another_process_serving_the_endpoint_is_refused() {
assert!(source.to_string().contains(&std::process::id().to_string()));
assert!(source.to_string().contains(&process.pid.to_string()));
}
- assert!(
- !Process::open(&process)
- .unwrap()
- .unwrap()
- .has_exited()
- .unwrap()
- );
+ let Opened::Running(held) = Process::open(&process).unwrap() else {
+ panic!("the unrelated VM process still runs");
+ };
+ assert!(!held.has_exited().unwrap());
}
}
diff --git a/nvx-rs/src/host/vm.rs b/nvx-rs/src/host/vm.rs
index 72e724aa6..142ca7c0c 100644
--- a/nvx-rs/src/host/vm.rs
+++ b/nvx-rs/src/host/vm.rs
@@ -31,7 +31,10 @@ use crate::host::runtime::{Runtime, RuntimeIdentity};
use crate::host::snapshot::Snapshot;
use crate::host::spawn::{self, VmmCommand};
use crate::host::transport::{self, HostConnection};
-use crate::host::{platform::inherit::Inherited, process::Process, process::ProcessRef};
+use crate::host::{
+ platform::inherit::Inherited,
+ process::{Opened, Process, ProcessRef},
+};
use crate::protocol::CAPABILITY_LEN;
use crate::{Error, Result};
@@ -252,12 +255,16 @@ pub fn verify(saved: &SavedVm, runtime: &RuntimeIdentity) -> Result {
),
));
}
- Process::open(&saved.process)
+ match Process::open(&saved.process)
.map_err(|error| Error::io("cannot verify the saved VM process").with_source(error))?
- .ok_or_else(|| {
+ {
+ Opened::Running(process) => Ok(process),
+ Opened::Exited(_) => {
let message = "the saved VM process exited";
- Error::exited(message).with_source(io::Error::new(io::ErrorKind::NotFound, message))
- })
+ Err(Error::exited(message)
+ .with_source(io::Error::new(io::ErrorKind::NotFound, message)))
+ }
+ }
}
/// A verified held VM process and its authenticated control connection.
diff --git a/nvx-rs/src/host/vm/tests.rs b/nvx-rs/src/host/vm/tests.rs
index b637a7532..8903967cb 100644
--- a/nvx-rs/src/host/vm/tests.rs
+++ b/nvx-rs/src/host/vm/tests.rs
@@ -11,6 +11,8 @@ use crate::host::config::{
Boot, LayeredRoot, PROCESS_COMMAND_LINE_LIMIT, RootConfig, UpperConfig,
process_command_line_units,
};
+#[cfg(any(target_os = "linux", windows))]
+use crate::host::process::Opened;
use crate::host::runtime::{ArtifactIdentity, LaunchOptions, RuntimeIdentity};
use crate::hypervisor::Hypervisor;
use crate::protocol::root::RootImage;
@@ -901,7 +903,9 @@ fn spawn_returns_the_same_outputs_and_capability_with_a_verified_process() {
assert_eq!(launched.0.capability.as_bytes(), &capability);
let reference = launched.0.process.reference();
assert_ne!(reference.pid, std::process::id());
- let reopened = Process::open(reference).unwrap().unwrap();
+ let Opened::Running(reopened) = Process::open(reference).unwrap() else {
+ panic!("the recorded VMM runs");
+ };
assert_eq!(reopened.reference(), reference);
assert!(!reopened.has_exited().unwrap());
assert!(!format!("{:?}", launched.0).contains(&format!("{capability:?}")));
@@ -1073,7 +1077,9 @@ fn a_library_vm_outlives_the_caller_that_spawned_it_without_retaining_its_output
);
let reference =
serde_json::from_slice(&fs::read(vm.join("caller-process.json")).unwrap()).unwrap();
- let process = Process::open(&reference).unwrap().unwrap();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the caller's recorded VMM runs");
+ };
let launched = RunningVm(Launched {
process,
outputs: VmDir::open(&vm).unwrap().outputs(false).unwrap(),
@@ -1233,7 +1239,9 @@ fn current_cold_spawn_boots_a_guest_accepts_shell_input_and_exits_cleanly() {
let launched = RunningVm(spawn(prepared).unwrap());
assert_eq!(launched.0.capability.as_bytes(), &capability);
let reference = launched.0.process.reference();
- let reopened = Process::open(reference).unwrap().unwrap();
+ let Opened::Running(reopened) = Process::open(reference).unwrap() else {
+ panic!("the recorded VMM runs");
+ };
assert_eq!(reopened.reference(), reference);
let deadline = Instant::now() + Duration::from_secs(60);
let mut console = loop {
@@ -1334,7 +1342,9 @@ impl StopChild {
start_time: platform::process_start_time(child.id()).unwrap().unwrap(),
boot: crate::host::process::BootId::current().unwrap(),
};
- let process = Process::open(&reference).unwrap().unwrap();
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the owned child runs");
+ };
Self { child, process }
}
}
diff --git a/nvx-rs/tests/cli.rs b/nvx-rs/tests/cli.rs
index 01191d000..0c32142f2 100644
--- a/nvx-rs/tests/cli.rs
+++ b/nvx-rs/tests/cli.rs
@@ -51,7 +51,7 @@ use std::process::{Command, Output, Stdio};
use std::thread;
use std::time::{Duration, Instant};
-use nvx::host::process::{Process, ProcessRef};
+use nvx::host::process::{Opened, Process, ProcessRef};
use serde_json::{Value, json};
use self::harness::{FAKE_OPENVMM, NVX_RS, Root, canonical};
@@ -800,7 +800,10 @@ fn runtime_record(root: &Root, state: &str) -> Option {
fn recorded_process(root: &Root, state: &str) -> Option {
let record = runtime_record(root, state)?;
let reference: ProcessRef = serde_json::from_value(record["process"].clone()).unwrap();
- Process::open(&reference).unwrap()
+ match Process::open(&reference).unwrap() {
+ Opened::Running(process) => Some(process),
+ Opened::Exited(_) => None,
+ }
}
/// Returns the names in the state directory `state` below the root, sorted.
@@ -1057,9 +1060,9 @@ fn a_start_that_was_interrupted_is_ended_under_another_identity_too() {
// The CLI dies, as a crash would, and leaves its OpenVMM running.
start.kill().unwrap();
start.wait().unwrap();
- let process = Process::open(&reference)
- .unwrap()
- .expect("the OpenVMM of the start runs");
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the OpenVMM of the start runs");
+ };
// Under another runtime identity, the next transition ends the start, whose OpenVMM it
// verifies through the journal.
@@ -2059,9 +2062,9 @@ fn a_start_that_ends_before_openvmm_opens_the_images_leaves_it_the_checked_ones(
// The CLI dies, as a crash would, before OpenVMM has opened its images, and leaves it running.
start.kill().unwrap();
start.wait().unwrap();
- let process = Process::open(&reference)
- .unwrap()
- .expect("the OpenVMM of the start runs");
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the OpenVMM of the start runs");
+ };
let blocks = root
.path
.join("build")
diff --git a/nvx-rs/tests/cli/lifecycle.rs b/nvx-rs/tests/cli/lifecycle.rs
index d82a0f548..ce2ac6b58 100644
--- a/nvx-rs/tests/cli/lifecycle.rs
+++ b/nvx-rs/tests/cli/lifecycle.rs
@@ -11,7 +11,7 @@ use std::time::{Duration, Instant};
use nvx::contract::{
BOOT_CONTRACT_VERSION, CONTROL_CONTRACT_REVISION, CONTROL_SESSION_PROTOCOL_VERSION,
};
-use nvx::host::process::{Process, ProcessRef};
+use nvx::host::process::{Opened, Process, ProcessRef};
use nvx::host::sandbox::distro::DistroBinding;
use nvx::host::sandbox::scratch::ScratchBinding;
use nvx::host::sandbox::{LayerRole, SandboxLayer};
@@ -153,9 +153,9 @@ fn assert_runtime_record(root: &Root, state: &str, record: &Value) -> (String, P
"{record}"
);
let reference: ProcessRef = serde_json::from_value(record["process"].clone()).unwrap();
- let process = Process::open(&reference)
- .unwrap()
- .unwrap_or_else(|| panic!("the OpenVMM of the record does not run: {record}"));
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the OpenVMM of the record does not run: {record}");
+ };
let endpoint = record["endpoint"].as_str().unwrap().to_owned();
assert_endpoint(root, state, &endpoint);
let digest = |index: usize| format!("sha256:{}", PROVENANCE[index].2);
@@ -1432,7 +1432,10 @@ fn recycled_process_id(root: &Root) {
let path = root.below("state/runtime.json");
let mut record = json(&path);
let reference: ProcessRef = serde_json::from_value(record["process"].clone()).unwrap();
- let openvmm = Unrecorded(Process::open(&reference).unwrap().expect("OpenVMM runs"));
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("OpenVMM runs");
+ };
+ let openvmm = Unrecorded(process);
let start_time = record["process"]["startTime"].as_u64().unwrap();
record["process"]["startTime"] = json!(start_time + 1);
fs::write(&path, serde_json::to_vec_pretty(&record).unwrap()).unwrap();
diff --git a/nvx-rs/tests/fake_openvmm.rs b/nvx-rs/tests/fake_openvmm.rs
index b112f951b..3444797cd 100644
--- a/nvx-rs/tests/fake_openvmm.rs
+++ b/nvx-rs/tests/fake_openvmm.rs
@@ -20,7 +20,7 @@ use std::time::{Duration, Instant};
use nvx::host::instance::{Found, Instance, Running, Started, Stopped, WorkloadOutcome};
use nvx::host::launch::{LaunchSpec, PROCESS_COMMAND_LINE_LIMIT};
-use nvx::host::process::{Process, ProcessRef};
+use nvx::host::process::{Opened, Process, ProcessRef};
use nvx::host::store::{ArtifactIdentity, CallerFiles, IdentityPart, RuntimeIdentity};
use nvx::host::supervisor::spawn_command;
use nvx::protocol::{Workload, WorkloadEnvironment};
@@ -425,9 +425,10 @@ fn a_restarted_orchestrator_reattaches_to_its_vm() {
let Found::Mismatched(mismatched) = locked.find(|| Ok(runtime_with('9'))).unwrap() else {
panic!("a VM of another runtime identity was not reported");
};
- let process = Process::open(mismatched.vmm().record().process())
- .unwrap()
- .expect("the VM runs");
+ let Opened::Running(process) = Process::open(mismatched.vmm().record().process()).unwrap()
+ else {
+ panic!("the VM runs");
+ };
for timeout in [Duration::ZERO, Duration::MAX] {
let Found::Mismatched(mismatched) = locked.find(|| Ok(runtime_with('9'))).unwrap() else {
panic!("a VM of another runtime identity was not reported");
@@ -467,9 +468,10 @@ fn outcome_reports_of_another_runtime_are_never_read() {
let Found::Mismatched(mismatched) = locked.find(|| Ok(runtime_with('9'))).unwrap() else {
panic!("a VM of another runtime identity was not reported");
};
- let process = Process::open(mismatched.vmm().record().process())
- .unwrap()
- .expect("the VM runs");
+ let Opened::Running(process) = Process::open(mismatched.vmm().record().process()).unwrap()
+ else {
+ panic!("the VM runs");
+ };
fs::write(
instance.report_path(),
r#"{"schema_version":1,"outcome":{"operation":"managed","category":"success",
@@ -517,7 +519,9 @@ fn records_that_name_another_process_never_end_it() {
let mut record: serde_json::Value =
serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
let reference: ProcessRef = serde_json::from_value(record["process"].clone()).unwrap();
- let process = Process::open(&reference).unwrap().expect("the VM runs");
+ let Opened::Running(process) = Process::open(&reference).unwrap() else {
+ panic!("the VM runs");
+ };
tamper(&mut record);
fs::write(&path, serde_json::to_vec(&record).unwrap()).unwrap();
// Under another runtime identity too, the VM is found stopped, and its record is cleared,
diff --git a/nvx-rs/tests/store.rs b/nvx-rs/tests/store.rs
index 9602cce68..ea22e24ed 100644
--- a/nvx-rs/tests/store.rs
+++ b/nvx-rs/tests/store.rs
@@ -20,7 +20,7 @@ use std::time::{Duration, Instant};
use nvx::ErrorKind;
use nvx::host::platform;
-use nvx::host::process::{BootId, Process, ProcessRef};
+use nvx::host::process::{BootId, Opened, Process, ProcessRef};
use nvx::host::store::{
ArtifactIdentity, IdentityCheck, IdentityPart, JOURNAL_NAME, Launch, LaunchId, LaunchState,
LockedStore, RUNTIME_NAME, Recovery, RuntimeIdentity, SandboxStore,
@@ -179,7 +179,7 @@ fn kill(pid: u32, start_time: u64) -> io::Result {
start_time,
boot: BootId::current()?,
};
- let Some(process) = Process::open(&reference)? else {
+ let Opened::Running(process) = Process::open(&reference)? else {
return Ok(true);
};
process.kill()?;
@@ -530,9 +530,9 @@ fn a_committed_launch_names_its_vm_for_another_build() {
IdentityCheck::Mismatched(vec![IdentityPart::Initramfs])
);
// The record's verified reference holds the VMM, whichever process started it.
- let reattached = Process::open(found.process())
- .unwrap()
- .expect("the recorded VMM runs");
+ let Opened::Running(reattached) = Process::open(found.process()).unwrap() else {
+ panic!("the recorded VMM runs");
+ };
assert!(!reattached.has_exited().unwrap());
drop(vmm);
assert!(
diff --git a/nvx-rs/tests/supervisor.rs b/nvx-rs/tests/supervisor.rs
index 45b2058ab..f5a29252a 100644
--- a/nvx-rs/tests/supervisor.rs
+++ b/nvx-rs/tests/supervisor.rs
@@ -30,7 +30,7 @@ use std::time::{Duration, Instant};
use nvx::ErrorKind;
use nvx::host::launch::MAX_TIMEOUT;
-use nvx::host::process::{BootId, Process, ProcessRef, WaitMethod};
+use nvx::host::process::{BootId, Opened, Process, ProcessRef, WaitMethod};
#[cfg(windows)]
use nvx::host::store::FORMAT;
use nvx::host::store::{
@@ -449,7 +449,7 @@ impl Drop for VmmDirectory {
fn drop(&mut self) {
let _ = File::create(self.path().join(EXIT_MARKER));
if let Some(reference) = &self.reference
- && let Ok(Some(process)) = Process::open(reference)
+ && let Ok(Opened::Running(process)) = Process::open(reference)
&& !process
.wait(Instant::now() + TIMEOUT)
.is_ok_and(|exited| exited.is_some())
@@ -572,16 +572,20 @@ impl Drop for Launcher {
/// Opens the process that `reference` names, which must still run.
fn open_running(reference: &ProcessRef) -> Process {
- Process::open(reference)
- .unwrap()
- .unwrap_or_else(|| panic!("{reference:?} does not run"))
+ let Opened::Running(process) = Process::open(reference).unwrap() else {
+ panic!("{reference:?} does not run");
+ };
+ process
}
/// Checks that the process that `process` holds has exited, and that no process matches its
/// reference.
fn assert_ended(process: &Process) {
assert!(process.has_exited().unwrap());
- assert!(Process::open(process.reference()).unwrap().is_none());
+ assert!(matches!(
+ Process::open(process.reference()).unwrap(),
+ Opened::Exited(_)
+ ));
}
#[test]
@@ -782,7 +786,7 @@ fn a_reused_process_id_is_never_signaled() {
},
] {
assert!(
- Process::open(&impostor).unwrap().is_none(),
+ matches!(Process::open(&impostor).unwrap(), Opened::Exited(_)),
"{impostor:?} names a running process"
);
}
@@ -824,7 +828,10 @@ fn references_that_name_no_process_are_never_opened() {
vmm.end();
assert!(process.wait(Instant::now() + TIMEOUT).unwrap().is_some());
assert!(launched.kill_and_wait());
- assert!(Process::open(process.reference()).unwrap().is_none());
+ assert!(matches!(
+ Process::open(process.reference()).unwrap(),
+ Opened::Exited(_)
+ ));
assert!(process.has_exited().unwrap());
}
diff --git a/nvx-rs/tests/template_capture.rs b/nvx-rs/tests/template_capture.rs
index 0036b1c10..d03074966 100644
--- a/nvx-rs/tests/template_capture.rs
+++ b/nvx-rs/tests/template_capture.rs
@@ -23,7 +23,7 @@ use std::thread;
use std::time::{Duration, Instant};
use nvx::host::platform;
-use nvx::host::process::{BootId, Process, ProcessRef};
+use nvx::host::process::{BootId, Opened, Process, ProcessRef};
use nvx::host::sandbox::distro::{DISTRO_POLICY_REVISION, DistroBinding, PolicyVerdict};
use nvx::host::sandbox::{LayerRole, SandboxDistro, SandboxLayer, SandboxPlan, SandboxUpper};
#[cfg(target_os = "linux")]
@@ -528,7 +528,7 @@ impl Orphan {
impl Drop for Orphan {
fn drop(&mut self) {
- if let Ok(Some(process)) = Process::open(&self.0) {
+ if let Ok(Opened::Running(process)) = Process::open(&self.0) {
let _ = process.kill();
let _ = process.wait(Instant::now() + TIMEOUT);
}
diff --git a/tests/runtime-adapter/tests/common/mod.rs b/tests/runtime-adapter/tests/common/mod.rs
index dc5e0f914..fef951c26 100644
--- a/tests/runtime-adapter/tests/common/mod.rs
+++ b/tests/runtime-adapter/tests/common/mod.rs
@@ -30,7 +30,7 @@ use std::time::{Duration, Instant};
use nvx::Hypervisor;
use nvx::host::artifacts::{ArtifactDir, Artifacts, Guest};
-use nvx::host::process::Process;
+use nvx::host::process::{Opened, Process};
use nvx::host::store::{LOG_NAME, RuntimeRecord, SandboxStore};
use nvx::host::supervisor::{self, Reattached, StopOutcome};
use nvx::host::template::{CAPTURES_NAME, Store, TRANSACTION_NAME};
@@ -407,7 +407,10 @@ impl Sandbox {
/// Returns the VMM of the sandbox's runtime record, if the record names one that runs.
pub(crate) fn vmm(&self) -> Option {
- Process::open(self.record()?.process()).unwrap()
+ match Process::open(self.record()?.process()).unwrap() {
+ Opened::Running(process) => Some(process),
+ Opened::Exited(_) => None,
+ }
}
/// Ends the VM that the sandbox's store records, whatever its runtime identity: reattach ends
diff --git a/tests/runtime-adapter/tests/vm_clone.rs b/tests/runtime-adapter/tests/vm_clone.rs
index cbc20e665..46f447124 100644
--- a/tests/runtime-adapter/tests/vm_clone.rs
+++ b/tests/runtime-adapter/tests/vm_clone.rs
@@ -228,7 +228,7 @@ use nvx::exec::{Key, Limits, Timers, Window};
use nvx::host::artifacts::Artifacts;
use nvx::host::filesystem::GUEST_EXPORT;
use nvx::host::platform::Transport;
-use nvx::host::process::{BootId, Process};
+use nvx::host::process::{BootId, Opened, Process};
use nvx::host::sandbox::SandboxNetwork;
use nvx::host::store::{CallerFiles, LOG_NAME, SandboxStore};
use nvx::host::template::{
@@ -2379,9 +2379,10 @@ fn refuses_early(template: &Template, early: Message) {
let restoring = planned
.launch(&mut acquired, &runtime.artifacts.openvmm, &runtime.identity)
.unwrap();
- let vmm = Process::open(restoring.starting().process().reference())
- .unwrap()
- .expect("the clone's VMM runs");
+ let Opened::Running(vmm) = Process::open(restoring.starting().process().reference()).unwrap()
+ else {
+ panic!("the clone's VMM runs");
+ };
let mut control = Early {
early: early.clone(),
vmm,
diff --git a/tests/runtime-adapter/tests/vm_steps.rs b/tests/runtime-adapter/tests/vm_steps.rs
index bc4821d7d..9ff0fea3c 100644
--- a/tests/runtime-adapter/tests/vm_steps.rs
+++ b/tests/runtime-adapter/tests/vm_steps.rs
@@ -20,7 +20,7 @@ use std::path::Path;
use std::time::Instant;
use nvx::ErrorCode;
-use nvx::host::process::Process;
+use nvx::host::process::{Opened, Process};
use nvx::host::store::{LaunchState, SandboxStore};
use runtime_adapter::hook::{Action, HOOK_EXIT, STEP_HOOK, Step, StepHook};
use runtime_adapter::staged::Staged;
@@ -205,9 +205,9 @@ fn the_hook_pauses_a_launch_at_each_of_its_steps() {
.unwrap()
.expect("the launch has its journal");
assert_eq!(journal.state(), LaunchState::Spawned, "at {step}");
- let vmm = Process::open(journal.process().unwrap())
- .unwrap()
- .expect("the VMM runs");
+ let Opened::Running(vmm) = Process::open(journal.process().unwrap()).unwrap() else {
+ panic!("the VMM runs");
+ };
assert!(!vmm.has_exited().unwrap(), "at {step}");
assert_eq!(store.runtime().unwrap(), None, "at {step}");
}