1#![warn(missing_docs)]
7
8use anyhow::Context;
9use anyhow::bail;
10use mesh::MeshPayload;
11use std::collections::BTreeMap;
12use std::ffi::OsStr;
13use std::ffi::OsString;
14use std::path::PathBuf;
15
16#[derive(Clone, Debug, MeshPayload)]
17pub enum TestScenarioConfig {
18 SaveFail,
19 RestoreStuck,
20 SaveStuck,
21}
22
23impl std::str::FromStr for TestScenarioConfig {
24 type Err = anyhow::Error;
25
26 fn from_str(s: &str) -> Result<TestScenarioConfig, anyhow::Error> {
27 match s {
28 "SERVICING_SAVE_FAIL" => Ok(TestScenarioConfig::SaveFail),
29 "SERVICING_RESTORE_STUCK" => Ok(TestScenarioConfig::RestoreStuck),
30 "SERVICING_SAVE_STUCK" => Ok(TestScenarioConfig::SaveStuck),
31 _ => Err(anyhow::anyhow!("Invalid test config: {}", s)),
32 }
33 }
34}
35
36pub struct Options {
40 pub wait_for_start: bool,
43
44 pub signal_vtl0_started: bool,
49
50 pub reformat_vmgs: bool,
53
54 pub pid: Option<PathBuf>,
57
58 pub vmbus_max_version: Option<u32>,
61
62 pub vmbus_enable_mnf: Option<bool>,
65
66 pub vmbus_force_confidential_external_memory: bool,
72
73 pub cmdline_append: Option<String>,
76
77 pub vnc_port: u32,
80
81 pub gdbstub: bool,
84
85 pub gdbstub_port: u32,
88
89 pub vtl0_starts_paused: bool,
92
93 pub framebuffer_gpa_base: Option<u64>,
98
99 pub serial_wait_for_rts: bool,
103
104 pub force_load_vtl0_image: Option<String>,
110
111 pub nvme_vfio: bool,
114
115 pub mcr: bool, pub enable_shared_visibility_pool: bool,
123
124 pub hide_isolation: bool,
127
128 pub halt_on_guest_halt: bool,
133
134 pub no_sidecar_hotplug: bool,
137
138 pub nvme_keep_alive: bool,
140
141 pub test_configuration: Option<TestScenarioConfig>,
145
146 pub disable_uefi_frontpage: bool,
150}
151
152impl Options {
153 pub(crate) fn parse(
154 extra_args: Vec<String>,
155 extra_env: Vec<(String, Option<String>)>,
156 ) -> anyhow::Result<Self> {
157 let mut env: BTreeMap<OsString, OsString> = std::env::vars_os().collect();
159 for (key, value) in extra_env {
160 match value {
161 Some(value) => env.insert(key.into(), value.into()),
162 None => env.remove::<OsStr>(key.as_ref()),
163 };
164 }
165
166 let legacy_openhcl_env = |name: &str| -> Option<&OsString> {
169 env.get::<OsStr>(name.as_ref()).or_else(|| {
170 env.get::<OsStr>(
171 format!(
172 "UNDERHILL_{}",
173 name.strip_prefix("OPENHCL_").unwrap_or(name)
174 )
175 .as_ref(),
176 )
177 })
178 };
179
180 let parse_env_string =
182 |name: &str| -> Option<&OsString> { env.get::<OsStr>(name.as_ref()) };
183
184 fn parse_bool(value: Option<&OsString>) -> bool {
185 value
186 .map(|v| v.eq_ignore_ascii_case("true") || v == "1")
187 .unwrap_or_default()
188 }
189
190 let parse_legacy_env_bool = |name| parse_bool(legacy_openhcl_env(name));
191 let parse_env_bool = |name: &str| parse_bool(env.get::<OsStr>(name.as_ref()));
192
193 let parse_legacy_env_number = |name| {
194 legacy_openhcl_env(name)
195 .map(|v| {
196 v.to_string_lossy().parse().context(format!(
197 "Error parsing numeric environment variable {} {:?}",
198 name, v
199 ))
200 })
201 .transpose()
202 };
203
204 let mut wait_for_start = parse_legacy_env_bool("OPENHCL_WAIT_FOR_START");
205 let mut reformat_vmgs = parse_legacy_env_bool("OPENHCL_REFORMAT_VMGS");
206 let mut pid = legacy_openhcl_env("OPENHCL_PID_FILE_PATH")
207 .map(|x| x.to_string_lossy().into_owned().into());
208 let vmbus_max_version = legacy_openhcl_env("OPENHCL_VMBUS_MAX_VERSION")
209 .map(|x| {
210 vmbus_core::parse_vmbus_version(&(x.to_string_lossy()))
211 .map_err(|x| anyhow::anyhow!("Error parsing vmbus max version: {}", x))
212 })
213 .transpose()?;
214 let vmbus_enable_mnf =
215 legacy_openhcl_env("OPENHCL_VMBUS_ENABLE_MNF").map(|v| parse_bool(Some(v)));
216 let vmbus_force_confidential_external_memory =
217 parse_env_bool("OPENHCL_VMBUS_FORCE_CONFIDENTIAL_EXTERNAL_MEMORY");
218 let cmdline_append =
219 legacy_openhcl_env("OPENHCL_CMDLINE_APPEND").map(|x| x.to_string_lossy().into_owned());
220 let force_load_vtl0_image = legacy_openhcl_env("OPENHCL_FORCE_LOAD_VTL0_IMAGE")
221 .map(|x| x.to_string_lossy().into_owned());
222 let mut vnc_port = parse_legacy_env_number("OPENHCL_VNC_PORT")?.map(|x| x as u32);
223 let framebuffer_gpa_base = parse_legacy_env_number("OPENHCL_FRAMEBUFFER_GPA_BASE")?;
224 let vtl0_starts_paused = parse_legacy_env_bool("OPENHCL_VTL0_STARTS_PAUSED");
225 let serial_wait_for_rts = parse_legacy_env_bool("OPENHCL_SERIAL_WAIT_FOR_RTS");
226 let nvme_vfio = parse_legacy_env_bool("OPENHCL_NVME_VFIO");
227 let mcr = parse_legacy_env_bool("OPENHCL_MCR_DEVICE");
228 let enable_shared_visibility_pool =
229 parse_legacy_env_bool("OPENHCL_ENABLE_SHARED_VISIBILITY_POOL");
230 let hide_isolation = parse_env_bool("OPENHCL_HIDE_ISOLATION");
231 let halt_on_guest_halt = parse_legacy_env_bool("OPENHCL_HALT_ON_GUEST_HALT");
232 let no_sidecar_hotplug = parse_legacy_env_bool("OPENHCL_NO_SIDECAR_HOTPLUG");
233 let gdbstub = parse_legacy_env_bool("OPENHCL_GDBSTUB");
234 let gdbstub_port = parse_legacy_env_number("OPENHCL_GDBSTUB_PORT")?.map(|x| x as u32);
235 let nvme_keep_alive = parse_env_bool("OPENHCL_NVME_KEEP_ALIVE");
236 let test_configuration = parse_env_string("OPENHCL_TEST_CONFIG").and_then(|x| {
237 x.to_string_lossy()
238 .parse::<TestScenarioConfig>()
239 .map_err(|e| {
240 tracing::warn!(
241 "failed to parse OPENHCL_TEST_CONFIG: {}. No test will be simulated.",
242 e
243 )
244 })
245 .ok()
246 });
247 let disable_uefi_frontpage = parse_env_bool("OPENHCL_DISABLE_UEFI_FRONTPAGE");
248 let signal_vtl0_started = parse_env_bool("OPENHCL_SIGNAL_VTL0_STARTED");
249
250 let mut args = std::env::args().chain(extra_args);
251 args.next();
253
254 while let Some(next) = args.next() {
255 let arg = next;
256
257 match &*arg {
258 "--wait-for-start" => wait_for_start = true,
259 "--reformat-vmgs" => reformat_vmgs = true,
260
261 x if x.starts_with("--") && x.len() > 2 => {
262 if let Some(eq) = arg.find('=') {
263 let (name, value) = arg.split_at(eq);
264 let value = &value[1..];
266 Self::parse_value_arg(name, value, &mut pid, &mut vnc_port)?;
267 } else {
268 if let Some(value) = args.next() {
269 Self::parse_value_arg(&arg, &value, &mut pid, &mut vnc_port)?;
270 } else {
271 bail!("Expected a value after argument {}", arg);
272 }
273 }
274 }
275 x => bail!("Unrecognized argument {}", x),
276 }
277 }
278
279 Ok(Self {
280 wait_for_start,
281 signal_vtl0_started,
282 reformat_vmgs,
283 pid,
284 vmbus_max_version,
285 vmbus_enable_mnf,
286 vmbus_force_confidential_external_memory,
287 cmdline_append,
288 vnc_port: vnc_port.unwrap_or(3),
289 framebuffer_gpa_base,
290 gdbstub,
291 gdbstub_port: gdbstub_port.unwrap_or(4),
292 vtl0_starts_paused,
293 serial_wait_for_rts,
294 force_load_vtl0_image,
295 nvme_vfio,
296 mcr,
297 enable_shared_visibility_pool,
298 hide_isolation,
299 halt_on_guest_halt,
300 no_sidecar_hotplug,
301 nvme_keep_alive,
302 test_configuration,
303 disable_uefi_frontpage,
304 })
305 }
306
307 fn parse_value_arg(
308 name: &str,
309 value: &str,
310 pid: &mut Option<PathBuf>,
311 vnc_port: &mut Option<u32>,
312 ) -> anyhow::Result<()> {
313 match name {
314 "--pid" => *pid = Some(value.into()),
315 "--vnc-port" => {
316 *vnc_port = Some(
317 value
318 .parse()
319 .context(format!("Error parsing VNC port {}", value))?,
320 )
321 }
322 x => bail!("Unrecognized argument {}", x),
323 }
324
325 Ok(())
326 }
327}