diff --git a/bin/propolis-standalone/README.md b/bin/propolis-standalone/README.md index fa6048bde..f7ca8c522 100644 --- a/bin/propolis-standalone/README.md +++ b/bin/propolis-standalone/README.md @@ -17,6 +17,12 @@ cpus = 4 bootrom = "/path/to/bootrom/OVMF_CODE.fd" memory = 1024 +# Exit propolis-standalone process with if instance halts (default: 0) +# exit_on_halt = + +# Exit propolis-standalone process with if instance reboots (default: unset) +# exit_on_reboot = + [block_dev.alpine_iso] type = "file" path = "/path/to/alpine-extended-3.12.0-x86_64.iso" diff --git a/bin/propolis-standalone/src/main.rs b/bin/propolis-standalone/src/main.rs index 0716665d5..36f00b9ac 100644 --- a/bin/propolis-standalone/src/main.rs +++ b/bin/propolis-standalone/src/main.rs @@ -6,6 +6,7 @@ use std::collections::VecDeque; use std::fs::File; use std::io::{Error, ErrorKind, Result}; use std::path::Path; +use std::process::ExitCode; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -193,6 +194,7 @@ struct InstState { instance: Option, state: State, vcpu_tasks: Vec, + exit_code: Option, } struct InstInner { @@ -216,6 +218,7 @@ impl Instance { instance: Some(pinst), state: State::Initialize, vcpu_tasks: Vec::new(), + exit_code: None, }), boot_gen: AtomicUsize::new(0), eq: EventQueue::new(), @@ -404,8 +407,22 @@ impl Instance { for mut vcpu_ctrl in guard.vcpu_tasks.drain(..) { vcpu_ctrl.exit(); } + if guard.exit_code.is_none() { + guard.exit_code = Some(inner.config.main.exit_on_halt); + } } State::Reset => { + if let (None, Some(code)) = + (guard.exit_code, inner.config.main.exit_on_reboot) + { + // Emit the configured exit-on-reboot code if one is + // configured an no existing code would already + // supersede it. + guard.exit_code = Some(code); + guard.state = State::Halt; + cur_ev = Some(InstEvent::ReqHalt); + continue; + } let inst = guard.instance.as_ref().unwrap().lock(); Self::device_state_transition(State::Reset, &inst, false); inst.machine().reinitialize().unwrap(); @@ -432,13 +449,14 @@ impl Instance { } } - fn wait_destroyed(&self) { + fn wait_destroyed(&self) -> ExitCode { let guard = self.0.state.lock().unwrap(); - let _guard = self + let mut guard = self .0 .cv .wait_while(guard, |g| !matches!(g.state, State::Destroy)) .unwrap(); + ExitCode::from(guard.exit_code.take().unwrap_or(0)) } fn vcpu_loop( @@ -997,7 +1015,7 @@ struct Args { restore: bool, } -fn main() -> anyhow::Result<()> { +fn main() -> anyhow::Result { let Args { target, snapshot, restore } = Args::parse(); // Ensure proper setup of USDT probes @@ -1058,6 +1076,5 @@ fn main() -> anyhow::Result<()> { ); // wait for instance to be destroyed - inst.wait_destroyed(); - Ok(()) + Ok(inst.wait_destroyed()) } diff --git a/crates/propolis-standalone-config/src/lib.rs b/crates/propolis-standalone-config/src/lib.rs index ded94903a..8e4d96d08 100644 --- a/crates/propolis-standalone-config/src/lib.rs +++ b/crates/propolis-standalone-config/src/lib.rs @@ -51,6 +51,16 @@ pub struct Main { pub memory: usize, pub use_reservoir: Option, pub cpuid_profile: Option, + /// Process exitcode to emit if/when instance halts + /// + /// Default: 0 + #[serde(default)] + pub exit_on_halt: u8, + /// Process exitcode to emit if/when instance reboots + /// + /// Default: None, does not exit on reboot + #[serde(default)] + pub exit_on_reboot: Option, } /// A hard-coded device, either enabled by default or accessible locally