From 0ba89625b283cb3fab52dd3503f19945174b433d Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 22 May 2024 14:36:22 -0700 Subject: [PATCH 01/36] initial commit of vm_runner --- src/vm_runner/Cargo.lock | 366 +++++++++++++++++++++ src/vm_runner/Cargo.toml | 18 + src/vm_runner/src/bin/opensut_vm_runner.rs | 7 + src/vm_runner/src/config.rs | 44 +++ src/vm_runner/src/lib.rs | 190 +++++++++++ src/vm_runner/tests/manage_exit_error.toml | 10 + src/vm_runner/tests/manage_exit_ok.toml | 10 + 7 files changed, 645 insertions(+) create mode 100644 src/vm_runner/Cargo.lock create mode 100644 src/vm_runner/Cargo.toml create mode 100644 src/vm_runner/src/bin/opensut_vm_runner.rs create mode 100644 src/vm_runner/src/config.rs create mode 100644 src/vm_runner/src/lib.rs create mode 100644 src/vm_runner/tests/manage_exit_error.toml create mode 100644 src/vm_runner/tests/manage_exit_ok.toml diff --git a/src/vm_runner/Cargo.lock b/src/vm_runner/Cargo.lock new file mode 100644 index 00000000..17f9dd59 --- /dev/null +++ b/src/vm_runner/Cargo.lock @@ -0,0 +1,366 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "opensut_vm_runner" +version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "nix", + "serde", + "toml", +] + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "serde" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "2.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "toml" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +dependencies = [ + "memchr", +] diff --git a/src/vm_runner/Cargo.toml b/src/vm_runner/Cargo.toml new file mode 100644 index 00000000..7f8b9f77 --- /dev/null +++ b/src/vm_runner/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "opensut_vm_runner" +version = "0.1.0" +authors = ["Stuart Pernsteiner "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Latest `nix` version with MSRV <= 1.63. Rust 1.63 is what's currently +# provided in Debian Stable. +nix = "0.26.4" + +serde = { version = "1", features = ["derive"] } +toml = "0.7.3" + +log = "0.4" +env_logger = "0.10.2" diff --git a/src/vm_runner/src/bin/opensut_vm_runner.rs b/src/vm_runner/src/bin/opensut_vm_runner.rs new file mode 100644 index 00000000..472b49d0 --- /dev/null +++ b/src/vm_runner/src/bin/opensut_vm_runner.rs @@ -0,0 +1,7 @@ +use env_logger; +use opensut_vm_runner; + +fn main() { + env_logger::init(); + opensut_vm_runner::main(); +} diff --git a/src/vm_runner/src/config.rs b/src/vm_runner/src/config.rs new file mode 100644 index 00000000..f5176372 --- /dev/null +++ b/src/vm_runner/src/config.rs @@ -0,0 +1,44 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub mode: Mode, + #[serde(default)] + pub processes: Vec, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Mode { + /// Manage multiple processes running concurrently. Any number of processes is permitted. All + /// processes will be started, and the runner will wait for all of them to exit. If a process + /// exits unsuccessfully, all other processes will be terminated. + Manage, + /// `exec` a single command. There must be exactly one process in the config file. + Exec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum Process { + Shell(ShellProcess), + Vm(VmProcess), +} + +/// Run a shell command. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ShellProcess { + pub command: String, +} + +/// Spawn a VM. +/// +/// This could instead be done using a `ShellProcess` that invokes QEMU, but using `type = "vm"` +/// handles the most common device options automatically. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct VmProcess { + // TODO +} diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs new file mode 100644 index 00000000..3339fd3f --- /dev/null +++ b/src/vm_runner/src/lib.rs @@ -0,0 +1,190 @@ +use std::collections::HashMap; +use std::convert::TryFrom; +use std::env; +use std::fs; +use std::io; +use std::mem; +use std::os::unix::process::CommandExt; +use std::process::{self, Command, Child}; +use log::trace; +use nix; +use nix::unistd::Pid; +use nix::sys::wait::{WaitStatus, WaitPidFlag}; +use toml; +use crate::config::{Config, Mode, Process}; + +pub mod config; + + +/// Helper for cleaning up child processes on drop. The caller is responsible for adding each +/// child as soon as it's spawned, and for removing children after `wait` indicates that they've +/// terminated. If an error occurs, the `ManagedProcesses` object will be dropped, and any child +/// processes currently registered with it will be killed. +struct ManagedProcesses { + children: HashMap, +} + +impl ManagedProcesses { + pub fn new() -> ManagedProcesses { + ManagedProcesses { + children: HashMap::new(), + } + } + + pub fn len(&self) -> usize { + self.children.len() + } + + pub fn add(&mut self, child: Child) { + let pid = child.id(); + self.children.insert(pid, child); + } + + pub fn remove(&mut self, pid: u32) -> Option { + self.children.remove(&pid) + } +} + +impl Drop for ManagedProcesses { + fn drop(&mut self) { + for (&pid, child) in &mut self.children { + let result = child.kill(); + match result { + Ok(()) => {}, + Err(e) => { + eprintln!("failed to kill child {}: {}", pid, e); + // Continue trying to kill remaining children. + }, + } + } + + for (pid, mut child) in mem::take(&mut self.children) { + let result = child.wait(); + match result { + Ok(_) => {}, + Err(e) => { + eprintln!("failed to wait for child {}: {}", pid, e); + // Continue waiting on remaining children. + }, + } + } + } +} + + +pub fn run_manage(cfg: &Config) -> io::Result<()> { + let mut children = ManagedProcesses::new(); + + for process in &cfg.processes { + let shell_process = match *process { + Process::Shell(ref x) => x, + _ => panic!("unimplemented: non-shell processes in `mode = 'exec'`"), + }; + trace!("spawn: {:?}", shell_process); + let child = Command::new("/bin/sh") + .args(&["-c", &shell_process.command]) + .spawn()?; + trace!("spawned pid = {}", child.id()); + children.add(child); + } + + // Await all children. If any child returns nonzero, kill all other children. + while children.len() > 0 { + trace!("waitpid..."); + let status = nix::sys::wait::waitid(nix::sys::wait::Id::All, WaitPidFlag::WEXITED)?; + trace!("waitpid returned {:?}", status); + let mut remove_child = |pid: Pid| { + let pid = u32::try_from(pid.as_raw()).unwrap(); + children.remove(pid); + }; + match status { + WaitStatus::Exited(pid, exit_code) => { + if exit_code == 0 { + // Normal exit. Just remove the child. + remove_child(pid); + } else { + // Abnormal exit. + remove_child(pid); + panic!("process {} exited unexpectedly with code {}", pid, exit_code); + } + }, + WaitStatus::Signaled(pid, signal, _) => { + // Killed by a signal. + remove_child(pid); + panic!("process {} was killed unexpectedly by signal {:?}", pid, signal); + }, + WaitStatus::Stopped(pid, signal) => { + // Process suspended by SIGSTOP or similar. We should never receive this event, + // since we don't include `WUNTRACED`/`WSTOPPED` in the `waidpid` call above. + // + // The child is still alive, so don't remove it from `children`. + panic!("impossible: waitpid reported that {} stopped due to signal {:?}, \ + but we did not request such events", pid, signal); + }, + WaitStatus::Continued(pid) => { + // Process continued due to SIGCONT or similar. We should never receive this + // event, since we don't include `WCONTINUED` in the `waidpid` call above. + // + // The child is still alive, so don't remove it from `children`. + panic!("impossible: waitpid reported that {} continued due to a signal, \ + but we did not request such events", pid); + }, + WaitStatus::PtraceEvent(pid, _, event) => { + // Stopped by ptrace. This can happen when the child invokes `PTRACE_TRACEME`. + // + // The child is still alive, so don't remove it from `children`. + panic!("process {} unexpectedly stopped with ptrace event {}", pid, event); + }, + WaitStatus::PtraceSyscall(pid) => { + // Stopped by ptrace due to a syscall. We should never receive this event, since + // we don't enable syscall tracing on any children. + // + // The child is still alive, so don't remove it from `children`. + panic!("impossible: waitpid reported that {} stopped due to a ptrace syscall, \ + but we did not enable syscall tracing", pid); + }, + WaitStatus::StillAlive => { + // No state change; process is still alive. We should never receive this event, + // since we don't include `WNOHANG` in the `waidpid` call above. + panic!("impossible: waitpid reported no changes, \ + but we expected it to block until a change occurred"); + }, + } + } + + Ok(()) +} + +pub fn run_exec(cfg: &Config) -> io::Result<()> { + assert!(cfg.processes.len() == 1, + "config error: `mode = 'exec'` requires exactly one entry in `processes`"); + let process = &cfg.processes[0]; + + let shell_process = match *process { + Process::Shell(ref x) => x, + _ => panic!("unimplemented: non-shell processes in `mode = 'exec'`"), + }; + + let err = Command::new("/bin/sh") + .args(&["-c", &shell_process.command]) + .exec(); + Err(err) +} + + +pub fn main() { + let args = env::args_os().collect::>(); + if args.len() != 2 { + let cmd_name = env::args().nth(0).unwrap_or_else(|| "vm_runner".to_string()); + eprintln!("usage: {} config.toml", cmd_name); + process::exit(1); + } + + let config_str = fs::read_to_string(&args[1]).unwrap(); + let cfg: Config = toml::from_str(&config_str).unwrap(); + + match cfg.mode { + Mode::Manage => run_manage(&cfg).unwrap(), + Mode::Exec => run_exec(&cfg).unwrap(), + } +} diff --git a/src/vm_runner/tests/manage_exit_error.toml b/src/vm_runner/tests/manage_exit_error.toml new file mode 100644 index 00000000..8b47826a --- /dev/null +++ b/src/vm_runner/tests/manage_exit_error.toml @@ -0,0 +1,10 @@ +mode = "manage" + +[[processes]] +type = "shell" +command = "sleep 10" + +[[processes]] +type = "shell" +command = "false" + diff --git a/src/vm_runner/tests/manage_exit_ok.toml b/src/vm_runner/tests/manage_exit_ok.toml new file mode 100644 index 00000000..62c98c49 --- /dev/null +++ b/src/vm_runner/tests/manage_exit_ok.toml @@ -0,0 +1,10 @@ +mode = "manage" + +[[processes]] +type = "shell" +command = "sleep 2" + +[[processes]] +type = "shell" +command = "sleep 1" + From 43f2c09214c8d86c9a2cfc3bc2c5160016d29dbc Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 22 May 2024 15:06:59 -0700 Subject: [PATCH 02/36] vm_runner: factor out config::Process -> Command builder --- src/pkvm_setup/vhost-device | 2 +- src/vm_runner/src/lib.rs | 37 ++++++++++++++++++++----------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/pkvm_setup/vhost-device b/src/pkvm_setup/vhost-device index 61fd77cb..47810026 160000 --- a/src/pkvm_setup/vhost-device +++ b/src/pkvm_setup/vhost-device @@ -1 +1 @@ -Subproject commit 61fd77cb2db0612b4bfa0bb027c704cd3e59e626 +Subproject commit 4781002645470efcb523e758be124a7622b27998 diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index 3339fd3f..4b536f8c 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -11,7 +11,7 @@ use nix; use nix::unistd::Pid; use nix::sys::wait::{WaitStatus, WaitPidFlag}; use toml; -use crate::config::{Config, Mode, Process}; +use crate::config::{Config, Mode}; pub mod config; @@ -72,18 +72,25 @@ impl Drop for ManagedProcesses { } +fn build_command(process: &config::Process) -> Command { + let shell_process = match *process { + config::Process::Shell(ref x) => x, + _ => panic!("unimplemented: non-shell processes"), + }; + + let mut cmd = Command::new("/bin/sh"); + cmd.args(&["-c", &shell_process.command]); + cmd +} + + pub fn run_manage(cfg: &Config) -> io::Result<()> { let mut children = ManagedProcesses::new(); for process in &cfg.processes { - let shell_process = match *process { - Process::Shell(ref x) => x, - _ => panic!("unimplemented: non-shell processes in `mode = 'exec'`"), - }; - trace!("spawn: {:?}", shell_process); - let child = Command::new("/bin/sh") - .args(&["-c", &shell_process.command]) - .spawn()?; + let mut cmd = build_command(process); + trace!("spawn: {:?}", cmd); + let child = cmd.spawn()?; trace!("spawned pid = {}", child.id()); children.add(child); } @@ -160,14 +167,10 @@ pub fn run_exec(cfg: &Config) -> io::Result<()> { "config error: `mode = 'exec'` requires exactly one entry in `processes`"); let process = &cfg.processes[0]; - let shell_process = match *process { - Process::Shell(ref x) => x, - _ => panic!("unimplemented: non-shell processes in `mode = 'exec'`"), - }; - - let err = Command::new("/bin/sh") - .args(&["-c", &shell_process.command]) - .exec(); + let mut cmd = build_command(process); + trace!("exec: {:?}", cmd); + let err = cmd.exec(); + trace!("exec error: {}", err); Err(err) } From b5c11b74e608c13e18fd3f5798be9f4082e62874 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 22 May 2024 16:34:02 -0700 Subject: [PATCH 03/36] vm_runner: rename [[processes]] -> [[process]] --- src/vm_runner/src/config.rs | 2 +- src/vm_runner/src/lib.rs | 6 +++--- src/vm_runner/tests/manage_exit_error.toml | 4 ++-- src/vm_runner/tests/manage_exit_ok.toml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vm_runner/src/config.rs b/src/vm_runner/src/config.rs index f5176372..3c29eefd 100644 --- a/src/vm_runner/src/config.rs +++ b/src/vm_runner/src/config.rs @@ -5,7 +5,7 @@ use serde::{Serialize, Deserialize}; pub struct Config { pub mode: Mode, #[serde(default)] - pub processes: Vec, + pub process: Vec, } #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)] diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index 4b536f8c..5b412599 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -87,7 +87,7 @@ fn build_command(process: &config::Process) -> Command { pub fn run_manage(cfg: &Config) -> io::Result<()> { let mut children = ManagedProcesses::new(); - for process in &cfg.processes { + for process in &cfg.process { let mut cmd = build_command(process); trace!("spawn: {:?}", cmd); let child = cmd.spawn()?; @@ -163,9 +163,9 @@ pub fn run_manage(cfg: &Config) -> io::Result<()> { } pub fn run_exec(cfg: &Config) -> io::Result<()> { - assert!(cfg.processes.len() == 1, + assert!(cfg.process.len() == 1, "config error: `mode = 'exec'` requires exactly one entry in `processes`"); - let process = &cfg.processes[0]; + let process = &cfg.process[0]; let mut cmd = build_command(process); trace!("exec: {:?}", cmd); diff --git a/src/vm_runner/tests/manage_exit_error.toml b/src/vm_runner/tests/manage_exit_error.toml index 8b47826a..27e163c9 100644 --- a/src/vm_runner/tests/manage_exit_error.toml +++ b/src/vm_runner/tests/manage_exit_error.toml @@ -1,10 +1,10 @@ mode = "manage" -[[processes]] +[[process]] type = "shell" command = "sleep 10" -[[processes]] +[[process]] type = "shell" command = "false" diff --git a/src/vm_runner/tests/manage_exit_ok.toml b/src/vm_runner/tests/manage_exit_ok.toml index 62c98c49..958a1622 100644 --- a/src/vm_runner/tests/manage_exit_ok.toml +++ b/src/vm_runner/tests/manage_exit_ok.toml @@ -1,10 +1,10 @@ mode = "manage" -[[processes]] +[[process]] type = "shell" command = "sleep 2" -[[processes]] +[[process]] type = "shell" command = "sleep 1" From dffc171685fd8458517e9e546ff431782c87e516 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 22 May 2024 17:13:35 -0700 Subject: [PATCH 04/36] vm_runner: add support for running vms --- src/vm_runner/Cargo.lock | 28 ++++++- src/vm_runner/Cargo.toml | 2 + src/vm_runner/src/config.rs | 86 ++++++++++++++++++++- src/vm_runner/src/lib.rs | 150 +++++++++++++++++++++++++++++++++--- 4 files changed, 251 insertions(+), 15 deletions(-) diff --git a/src/vm_runner/Cargo.lock b/src/vm_runner/Cargo.lock index 17f9dd59..761684b7 100644 --- a/src/vm_runner/Cargo.lock +++ b/src/vm_runner/Cargo.lock @@ -42,12 +42,24 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -67,7 +79,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", + "serde", ] [[package]] @@ -126,6 +149,7 @@ name = "opensut_vm_runner" version = "0.1.0" dependencies = [ "env_logger", + "indexmap 2.2.6", "log", "nix", "serde", @@ -261,7 +285,7 @@ version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ - "indexmap", + "indexmap 1.9.3", "serde", "serde_spanned", "toml_datetime", diff --git a/src/vm_runner/Cargo.toml b/src/vm_runner/Cargo.toml index 7f8b9f77..335bd254 100644 --- a/src/vm_runner/Cargo.toml +++ b/src/vm_runner/Cargo.toml @@ -16,3 +16,5 @@ toml = "0.7.3" log = "0.4" env_logger = "0.10.2" + +indexmap = { version = "2.2.6", features = ["serde"] } diff --git a/src/vm_runner/src/config.rs b/src/vm_runner/src/config.rs index 3c29eefd..024a709a 100644 --- a/src/vm_runner/src/config.rs +++ b/src/vm_runner/src/config.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; +use indexmap::IndexMap; use serde::{Serialize, Deserialize}; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -40,5 +42,87 @@ pub struct ShellProcess { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct VmProcess { - // TODO + pub kernel: String, + pub initrd: Option, + #[serde(default)] + pub append: String, + + #[serde(default = "const_u32::<1024>")] + pub ram_mb: u32, + + /// If set, use KVM. Otherwise, run emulation with no hardware support. + /// + /// Using KVM requires access to the `/dev/kvm` device. + #[serde(default = "const_true")] + pub kvm: bool, + + /// Disk definitions. Devices must be named sequentially as `vda`, `vdb`, and so on. They + /// will be presented to the guest in name order, so `vda` will appear as `/dev/vda`, `vdb` as + /// `/dev/vdb`, and so on. + /// + /// The disks we present to the guest will be sequentially named starting with `/dev/vda`. + /// Requiring the user to also name their config file entries sequentially means lets us ensure + /// that there's a correspondence between the names in the config file and the device names + /// within the guest. + #[serde(default)] + pub disk: HashMap, + #[serde(default)] + pub net: VmNet, + /// 9p filesystem definitions. The key will be used as the "mount tag", which must be passed + /// to `mount` in the guest to mount the filesystem. + #[serde(default, rename = "9p")] + pub fs_9p: HashMap, + /// GPIO device definitions. Devices are added in order; the first one listed in the config + /// file will be `/dev/gpiochip1`, the second will be `/dev/gpiochip2`, and so on. (Note that + /// the guest will also have a `gpiochip0` provided automatically by QEMU.) + #[serde(default)] + pub gpio: IndexMap, +} + +fn const_true() -> bool { + true +} + +fn const_u32() -> u32 { + N +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct VmDisk { + pub format: String, + pub path: String, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct VmNet { + #[serde(default)] + pub port_forward: HashMap, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PortForward { + pub outer_port: u16, + pub inner_port: u16, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Vm9P { + pub path: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "mode", rename_all = "snake_case")] +pub enum VmGpio { + External, + Passthrough(PassthroughGpio), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PassthroughGpio { + pub device: String, } diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index 5b412599..19be8954 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -1,11 +1,14 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::env; +use std::fmt::Write as _; use std::fs; use std::io; use std::mem; use std::os::unix::process::CommandExt; use std::process::{self, Command, Child}; +use std::thread; +use std::time::Duration; use log::trace; use nix; use nix::unistd::Pid; @@ -72,23 +75,139 @@ impl Drop for ManagedProcesses { } -fn build_command(process: &config::Process) -> Command { - let shell_process = match *process { - config::Process::Shell(ref x) => x, - _ => panic!("unimplemented: non-shell processes"), - }; +#[derive(Debug, Default)] +struct Commands { + /// Start these processes early, before the ones in `commands`. + early_commands: Vec, + commands: Vec, +} + +fn build_commands(processes: &[config::Process]) -> Commands { + let mut cmds = Commands::default(); + for process in processes { + match *process { + config::Process::Shell(ref shell) => { + let mut cmd = Command::new("/bin/sh"); + cmd.args(&["-c", &shell.command]); + cmds.commands.push(cmd); + }, + config::Process::Vm(ref vm) => { + build_vm_command(vm, &mut cmds); + }, + } + } + cmds +} + +fn needs_escaping_for_qemu(arg: &str) -> bool { + arg.contains(&[',', '=', ':']) +} + +fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { + let config::VmProcess { + ref kernel, ref initrd, ref append, + ram_mb, kvm, + ref disk, ref net, ref fs_9p, ref gpio, + } = *vm; + + let mut vm_cmd = Command::new("qemu-system-aarch64"); + + // Basic machine configuration + vm_cmd.args(&["-M", "virt"]); + vm_cmd.args(&["-smp", "4"]); + vm_cmd.args(&["-m", &format!("{}", ram_mb)]); + vm_cmd.args(&["-nographic"]); + + // KVM + if kvm { + vm_cmd.args(&["-cpu", "host"]); + vm_cmd.args(&["-enable-kvm"]); + } else { + vm_cmd.args(&["-cpu", "cortex-a72"]); + vm_cmd.args(&["-machine", "virtualization=true"]); + vm_cmd.args(&["-machine", "virt,gic-version=3"]); + } + + // Non-configurable devices + vm_cmd.args(&["-device", "virtio-scsi-pci,id=scsi0"]); + vm_cmd.args(&["-object", "rng-random,filename=/dev/urandom,id=rng0"]); + vm_cmd.args(&["-device", "virtio-rng-pci,rng=rng0"]); + + // Kernel and related flags + vm_cmd.args(&["-kernel", &kernel]); + if let Some(ref initrd) = initrd { + vm_cmd.args(&["-initrd", &initrd]); + } + if append.len() > 0 { + vm_cmd.args(&["-append", &append]); + } + + for i in 0 .. disk.len() { + let i = u8::try_from(i).unwrap(); + let letter = (b'a' + i) as char; + let name = format!("vd{}", letter); + let d = disk.get(&name) + .unwrap_or_else(|| panic!("non-contiguous disk definitions: missing {}", name)); + // Forbid characters that require escaping in QEMU device arguments. + assert!(!needs_escaping_for_qemu(&d.path), + "unsupported character in disk {} path: {:?}", name, d.path); + assert!(["qcow2", "raw"].contains(&(&d.format as &str)), + "unsupported format for disk {}: {:?}", name, d.format); + vm_cmd.args(&["-drive", &format!("if=virtio,format={},file={}", d.format, d.path)]); + } + + let config::VmNet { ref port_forward } = *net; + let mut netdev_str = format!("user,id=net0"); + for pf in port_forward.values() { + write!(netdev_str, ",hostfwd=tcp:127.0.0.1:{}-:{}", pf.outer_port, pf.inner_port) + .unwrap(); + } + vm_cmd.args(&["-device", "virtio-net-pci,netdev=net0"]); + vm_cmd.args(&["-netdev", &netdev_str]); - let mut cmd = Command::new("/bin/sh"); - cmd.args(&["-c", &shell_process.command]); - cmd + for (name, fs) in fs_9p { + assert!(!needs_escaping_for_qemu(name), + "unsupported character in 9p name {:?}", name); + assert!(!needs_escaping_for_qemu(&fs.path), + "unsupported character in 9p {} path: {:?}", name, fs.path); + vm_cmd.args(&["-fsdev", + &format!("local,id=fs_9p__{},path={},security_model=mapped-xattr", name, fs.path)]); + vm_cmd.args(&["-device", + &format!("virtio-9p-pci,fsdev=fs_9p__{},mount_tag={}", name, name)]); + } + + if gpio.len() > 0 { + vm_cmd.args(&["-object", + &format!("memory-backend-file,id=mem,size={}M,mem_path=/dev/shm,share=on", ram_mb)]); + vm_cmd.args(&["-numa", "node,memdev=mem"]); + } + for (name, g) in gpio { + todo!(); + } + + cmds.commands.push(vm_cmd); } pub fn run_manage(cfg: &Config) -> io::Result<()> { let mut children = ManagedProcesses::new(); - for process in &cfg.process { - let mut cmd = build_command(process); + let cmds = build_commands(&cfg.process); + + if cmds.early_commands.len() > 0 { + for mut cmd in cmds.early_commands { + trace!("spawn (early): {:?}", cmd); + let child = cmd.spawn()?; + trace!("spawned pid = {}", child.id()); + children.add(child); + } + + // Give daemons time to start up and open their sockets. + // TODO: Use a systemd-notify like protocol to wait for daemon startup. + thread::sleep(Duration::from_millis(200)); + } + + for mut cmd in cmds.commands { trace!("spawn: {:?}", cmd); let child = cmd.spawn()?; trace!("spawned pid = {}", child.id()); @@ -165,9 +284,14 @@ pub fn run_manage(cfg: &Config) -> io::Result<()> { pub fn run_exec(cfg: &Config) -> io::Result<()> { assert!(cfg.process.len() == 1, "config error: `mode = 'exec'` requires exactly one entry in `processes`"); - let process = &cfg.process[0]; - let mut cmd = build_command(process); + let mut cmds = build_commands(&cfg.process); + assert!(cmds.commands.len() == 1, + "impossible: one `Process` produced multiple main `Command`s"); + assert!(cmds.early_commands.len() == 0, + "process requires running helpers, which `mode = 'exec'` does not support"); + + let mut cmd = cmds.commands.pop().unwrap(); trace!("exec: {:?}", cmd); let err = cmd.exec(); trace!("exec error: {}", err); @@ -186,6 +310,8 @@ pub fn main() { let config_str = fs::read_to_string(&args[1]).unwrap(); let cfg: Config = toml::from_str(&config_str).unwrap(); + trace!("parsed config = {:?}", cfg); + match cfg.mode { Mode::Manage => run_manage(&cfg).unwrap(), Mode::Exec => run_exec(&cfg).unwrap(), From 48cf97fbae278845840c32344335cfd37444785a Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 23 May 2024 10:13:34 -0700 Subject: [PATCH 05/36] vm_runner: refactor: make library `main` take config path as argument --- src/vm_runner/src/bin/opensut_vm_runner.rs | 13 ++++++++++++- src/vm_runner/src/lib.rs | 15 ++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/vm_runner/src/bin/opensut_vm_runner.rs b/src/vm_runner/src/bin/opensut_vm_runner.rs index 472b49d0..056a6f35 100644 --- a/src/vm_runner/src/bin/opensut_vm_runner.rs +++ b/src/vm_runner/src/bin/opensut_vm_runner.rs @@ -1,7 +1,18 @@ +use std::env; +use std::process; + use env_logger; use opensut_vm_runner; fn main() { env_logger::init(); - opensut_vm_runner::main(); + + let args = env::args_os().collect::>(); + if args.len() != 2 { + let cmd_name = env::args().nth(0).unwrap_or_else(|| "vm_runner".to_string()); + eprintln!("usage: {} config.toml", cmd_name); + process::exit(1); + } + + opensut_vm_runner::runner_main(&args[1]); } diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index 19be8954..c216a858 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; use std::convert::TryFrom; -use std::env; use std::fmt::Write as _; use std::fs; use std::io; use std::mem; use std::os::unix::process::CommandExt; -use std::process::{self, Command, Child}; +use std::path::Path; +use std::process::{Command, Child}; use std::thread; use std::time::Duration; use log::trace; @@ -299,15 +299,8 @@ pub fn run_exec(cfg: &Config) -> io::Result<()> { } -pub fn main() { - let args = env::args_os().collect::>(); - if args.len() != 2 { - let cmd_name = env::args().nth(0).unwrap_or_else(|| "vm_runner".to_string()); - eprintln!("usage: {} config.toml", cmd_name); - process::exit(1); - } - - let config_str = fs::read_to_string(&args[1]).unwrap(); +pub fn runner_main(config_path: impl AsRef) { + let config_str = fs::read_to_string(config_path).unwrap(); let cfg: Config = toml::from_str(&config_str).unwrap(); trace!("parsed config = {:?}", cfg); From 11df5ab79d7c31e15479af48362a36942f4d0367 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 23 May 2024 13:37:56 -0700 Subject: [PATCH 06/36] vm_runner: add opensut_boot binary --- src/vm_runner/Cargo.lock | 7 +++++ src/vm_runner/Cargo.toml | 3 ++- src/vm_runner/src/bin/opensut_boot.rs | 7 +++++ src/vm_runner/src/lib.rs | 37 +++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/vm_runner/src/bin/opensut_boot.rs diff --git a/src/vm_runner/Cargo.lock b/src/vm_runner/Cargo.lock index 761684b7..dcbc5895 100644 --- a/src/vm_runner/Cargo.lock +++ b/src/vm_runner/Cargo.lock @@ -153,6 +153,7 @@ dependencies = [ "log", "nix", "serde", + "shlex", "toml", ] @@ -238,6 +239,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "syn" version = "2.0.65" diff --git a/src/vm_runner/Cargo.toml b/src/vm_runner/Cargo.toml index 335bd254..4d26dde4 100644 --- a/src/vm_runner/Cargo.toml +++ b/src/vm_runner/Cargo.toml @@ -4,12 +4,13 @@ version = "0.1.0" authors = ["Stuart Pernsteiner "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +default-run = "opensut_vm_runner" [dependencies] # Latest `nix` version with MSRV <= 1.63. Rust 1.63 is what's currently # provided in Debian Stable. nix = "0.26.4" +shlex = "1.3.0" serde = { version = "1", features = ["derive"] } toml = "0.7.3" diff --git a/src/vm_runner/src/bin/opensut_boot.rs b/src/vm_runner/src/bin/opensut_boot.rs new file mode 100644 index 00000000..1616ccb6 --- /dev/null +++ b/src/vm_runner/src/bin/opensut_boot.rs @@ -0,0 +1,7 @@ +use env_logger; +use opensut_vm_runner; + +fn main() { + env_logger::init(); + opensut_vm_runner::boot_main(); +} diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index c216a858..12d9a7db 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -11,8 +11,10 @@ use std::thread; use std::time::Duration; use log::trace; use nix; +use nix::mount::MsFlags; use nix::unistd::Pid; use nix::sys::wait::{WaitStatus, WaitPidFlag}; +use shlex::Shlex; use toml; use crate::config::{Config, Mode}; @@ -310,3 +312,38 @@ pub fn runner_main(config_path: impl AsRef) { Mode::Exec => run_exec(&cfg).unwrap(), } } + + +pub fn boot_main() { + // Find the device containing the application partition. + let cmdline = fs::read_to_string("/proc/cmdline").unwrap(); + let mut app_device = None; + for arg in Shlex::new(&cmdline) { + let (key, value) = match arg.split_once('=') { + Some(x) => x, + None => continue, + }; + if key != "opensut.app_device" { + continue; + } + app_device = Some(value.to_owned()); + } + let app_device = app_device + .unwrap_or_else(|| panic!("missing opensut.app_device in kernel command line")); + + // TODO: Open the device and check its signature + + // Mount the application device + const APP_MOUNT_POINT: &str = "/opt/opensut/app"; + fs::create_dir_all(APP_MOUNT_POINT).unwrap(); + nix::mount::mount( + Some(&app_device as &str), + APP_MOUNT_POINT, + Some("squashfs"), + MsFlags::MS_RDONLY, + None::<&str>, // No filesystem-specific data + ).unwrap(); + + // Start the runner using the application's config file + runner_main(Path::new(APP_MOUNT_POINT).join("runner.toml")); +} From 46f74ec7917ca5b124425f0656e78b9f5bed547b Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 23 May 2024 15:42:42 -0700 Subject: [PATCH 07/36] vm_runner: add scripts for building and installing inside a VM --- src/vm_runner/build_config.toml | 24 ++++++++++++++++++++++++ src/vm_runner/build_helper.sh | 29 +++++++++++++++++++++++++++++ src/vm_runner/install_config.toml | 24 ++++++++++++++++++++++++ src/vm_runner/install_helper.sh | 25 +++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 src/vm_runner/build_config.toml create mode 100644 src/vm_runner/build_helper.sh create mode 100644 src/vm_runner/install_config.toml create mode 100644 src/vm_runner/install_helper.sh diff --git a/src/vm_runner/build_config.toml b/src/vm_runner/build_config.toml new file mode 100644 index 00000000..82f4d843 --- /dev/null +++ b/src/vm_runner/build_config.toml @@ -0,0 +1,24 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run="/bin/bash /dev/vdb"' + +[process.disk.vda] +format = "qcow2" +path = "../pkvm_setup/vms/disk_host.img" + +[process.disk.vdb] +format = "raw" +path = "build_helper.sh" + +[process.9p.vm_runner] +path = "." + +# TODO: remove (currently needed because setup_host_interactive.sh adds outerfs +# to fstab) +[process.9p.outerfs] +path = "../pkvm_setup/outerfs" diff --git a/src/vm_runner/build_helper.sh b/src/vm_runner/build_helper.sh new file mode 100644 index 00000000..19b840e5 --- /dev/null +++ b/src/vm_runner/build_helper.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -euo pipefail + +# Script for building the `vm_runner` project inside a VM. + +if [[ "$(id -u)" -eq "0" ]]; then + # Drop privileges for the rest of the script. + cp "$0" /tmp/vm-script + chown user:user /tmp/vm-script + exec sudo -u user /bin/bash /tmp/vm-script "$@" +fi + +cd ~ + +sudo apt install -y rustc cargo + +mkdir -p vm_runner +sudo mount -t 9p -o trans=virtio,version=9p2000.L,rw vm_runner vm_runner + +cd vm_runner +ls -l + +# Passing `--target` to cargo causes it to use a target-specific build +# directory. This prevents confusion between artifacts built outside and +# inside the VM when the two are sharing the same directory. +rust_target="$(rustc -vV | sed -n 's/host: //p')" +cargo build --release --target "$rust_target" + +echo "Build succeeded" diff --git a/src/vm_runner/install_config.toml b/src/vm_runner/install_config.toml new file mode 100644 index 00000000..84412c78 --- /dev/null +++ b/src/vm_runner/install_config.toml @@ -0,0 +1,24 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run="/bin/bash /dev/vdb"' + +[process.disk.vda] +format = "qcow2" +path = "../pkvm_setup/vms/disk_host.img" + +[process.disk.vdb] +format = "raw" +path = "install_helper.sh" + +[process.9p.vm_runner] +path = "." + +# TODO: remove (currently needed because setup_host_interactive.sh adds outerfs +# to fstab) +[process.9p.outerfs] +path = "../pkvm_setup/outerfs" diff --git a/src/vm_runner/install_helper.sh b/src/vm_runner/install_helper.sh new file mode 100644 index 00000000..37f7ae10 --- /dev/null +++ b/src/vm_runner/install_helper.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -euo pipefail + +# Script for building the `vm_runner` project inside a VM. + +if [[ "$(id -u)" -eq "0" ]]; then + # Drop privileges for the rest of the script. + cp "$0" /tmp/vm-script + chown user:user /tmp/vm-script + exec sudo -u user /bin/bash /tmp/vm-script "$@" +fi + +cd ~ + +mkdir -p vm_runner +sudo mount -t 9p -o trans=virtio,version=9p2000.L,rw vm_runner vm_runner + +sudo mkdir -p /opt/opensut/bin +target=aarch64-unknown-linux-gnu +sudo cp -v \ + vm_runner/target/"$target"/release/opensut_vm_runner \ + vm_runner/target/"$target"/release/opensut_boot \ + /opt/opensut/bin + +# TODO: install systemd unit and configure to run on startup From fb4bf4ea2a1579e2ada483423ad4b6faf99ecd5e Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 23 May 2024 16:30:08 -0700 Subject: [PATCH 08/36] pkvm_setup: build separate disk images for development/interactive use --- src/pkvm_setup/create_disk_images.sh | 55 +++++++++++++++++++--------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/pkvm_setup/create_disk_images.sh b/src/pkvm_setup/create_disk_images.sh index 5f882aeb..155c521e 100644 --- a/src/pkvm_setup/create_disk_images.sh +++ b/src/pkvm_setup/create_disk_images.sh @@ -5,27 +5,46 @@ mkdir -p vms disk_base=vms/disk_base.img disk_host=vms/disk_host.img +disk_host_dev=vms/disk_host_dev.img disk_guest=vms/disk_guest.img +disk_guest_dev=vms/disk_guest_dev.img -# Building these disk images is rather expensive, so don't overwrite existing -# ones. -for disk in "$disk_base" "$disk_host" "$disk_guest"; do - if [[ -e "$disk" ]]; then - echo "error: refusing to overwrite existing $disk" 1>&2 - #exit 1 - fi -done +if [[ -e "$disk_base" ]]; then + echo "keeping existing $disk_base" 1>&2 +else + bash debian_image/create_base_vm.sh "$disk_base" + echo "created base image $disk_base" +fi -bash debian_image/create_base_vm.sh "$disk_base" +if [[ -e "$disk_host" ]]; then + echo "keeping existing $disk_host" 1>&2 +else + bash debian_image/clone_vm.sh "$disk_base" "$disk_host" + bash run_vm_script.sh "$disk_host" debian_image/setup_host_vm.sh + echo "created host image $disk_host" +fi -bash debian_image/clone_vm.sh "$disk_base" "$disk_host" -bash run_vm_script.sh "$disk_host" debian_image/setup_host_vm.sh -# This part is optional, but convenient for interactive use: -bash run_vm_script.sh "$disk_host" debian_image/setup_host_vm_interactive.sh +if [[ -e "$disk_host_dev" ]]; then + echo "keeping existing $disk_host_dev" 1>&2 +else + bash debian_image/clone_vm.sh "$disk_base" "$disk_host_dev" + bash run_vm_script.sh "$disk_host_dev" debian_image/setup_host_vm.sh + bash run_vm_script.sh "$disk_host_dev" debian_image/setup_host_vm_interactive.sh + echo "created host dev image $disk_host_dev" +fi -bash debian_image/clone_vm.sh "$disk_base" "$disk_guest" -bash run_vm_script.sh "$disk_guest" debian_image/setup_guest_vm.sh +if [[ -e "$disk_guest" ]]; then + echo "keeping existing $disk_guest" 1>&2 +else + bash debian_image/clone_vm.sh "$disk_base" "$disk_guest" + bash run_vm_script.sh "$disk_guest" debian_image/setup_guest_vm.sh + echo "created guest image $disk_guest" +fi -echo -echo "created host image $disk_host" -echo "created guest image $disk_guest" +if [[ -e "$disk_guest_dev" ]]; then + echo "keeping existing $disk_guest_dev" 1>&2 +else + bash debian_image/clone_vm.sh "$disk_base" "$disk_guest_dev" + bash run_vm_script.sh "$disk_guest_dev" debian_image/setup_guest_vm.sh + echo "created guest dev image $disk_guest_dev" +fi From 66c514a56c6070f87f7c4e8fff0512984dc2aa7d Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 23 May 2024 16:30:23 -0700 Subject: [PATCH 09/36] pkvm_setup: enable passwordless sudo in guest images --- src/pkvm_setup/debian_image/setup_guest_vm.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pkvm_setup/debian_image/setup_guest_vm.sh b/src/pkvm_setup/debian_image/setup_guest_vm.sh index 44577f83..157820d9 100644 --- a/src/pkvm_setup/debian_image/setup_guest_vm.sh +++ b/src/pkvm_setup/debian_image/setup_guest_vm.sh @@ -25,3 +25,9 @@ edo sed -i -e "s/verse-opensut-vm/$hostname/g" /etc/hosts # Generate new SSH host keys edo rm -f /etc/ssh/ssh_host_*_key /etc/ssh/ssh_host_*_key.pub edo ssh-keygen -A + + +# Enable passwordless sudo for `user` +edo tee -a /etc/sudoers < Date: Thu, 23 May 2024 16:31:42 -0700 Subject: [PATCH 10/36] vm_runner: update {build,install}_config.toml for pkvm_setup image changes --- src/vm_runner/build_config.toml | 7 +------ src/vm_runner/install_config.toml | 5 ----- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/vm_runner/build_config.toml b/src/vm_runner/build_config.toml index 82f4d843..c6b740fc 100644 --- a/src/vm_runner/build_config.toml +++ b/src/vm_runner/build_config.toml @@ -9,7 +9,7 @@ append = 'earlycon root=/dev/vda2 systemd.run="/bin/bash /dev/vdb"' [process.disk.vda] format = "qcow2" -path = "../pkvm_setup/vms/disk_host.img" +path = "../pkvm_setup/vms/disk_host_dev.img" [process.disk.vdb] format = "raw" @@ -17,8 +17,3 @@ path = "build_helper.sh" [process.9p.vm_runner] path = "." - -# TODO: remove (currently needed because setup_host_interactive.sh adds outerfs -# to fstab) -[process.9p.outerfs] -path = "../pkvm_setup/outerfs" diff --git a/src/vm_runner/install_config.toml b/src/vm_runner/install_config.toml index 84412c78..bad2c450 100644 --- a/src/vm_runner/install_config.toml +++ b/src/vm_runner/install_config.toml @@ -17,8 +17,3 @@ path = "install_helper.sh" [process.9p.vm_runner] path = "." - -# TODO: remove (currently needed because setup_host_interactive.sh adds outerfs -# to fstab) -[process.9p.outerfs] -path = "../pkvm_setup/outerfs" From ae417182c2bc3653770702fc4f078ba09196e6d8 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 10:28:34 -0700 Subject: [PATCH 11/36] vm_runner: add build_application_image.py --- src/vm_runner/build_application_image.py | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/vm_runner/build_application_image.py diff --git a/src/vm_runner/build_application_image.py b/src/vm_runner/build_application_image.py new file mode 100644 index 00000000..b4e5047b --- /dev/null +++ b/src/vm_runner/build_application_image.py @@ -0,0 +1,57 @@ +''' +Script for building an application image for use with `opensut_boot`. This is +a wrapper around `mksquashfs` that makes it easier to gather files from +multiple places into a single image. +''' + +import argparse +import os +import shutil +import subprocess +import tempfile + +def parse_args(): + parser = argparse.ArgumentParser( + description = 'build an opensut_boot application image') + parser.add_argument('--file', '-f', action='append', metavar='SRC[=DEST]', default=[], + help = 'include file SRC at path DEST (default: basename(SRC)) ' + 'in the generated image') + parser.add_argument('--dir', '-d', action='append', metavar='SRC[=DEST]', default=[], + help = 'include directory SRC at path DEST (default: basename(SRC)) ' + 'in the generated image') + parser.add_argument('--output', '-o', metavar='OUT.IMG', default='out.img', + help = 'where to write the generated image') + + return parser.parse_args() + +def main(): + args = parse_args() + + temp_dir = tempfile.TemporaryDirectory() + + for file_spec in args.file: + src, delim, dest = file_spec.partition('=') + if delim == '': + assert dest == '' + dest = os.path.basename(src) + assert not os.path.isabs(dest), 'destination path must be relative (got %r)' % dest + dest_full = os.path.join(temp_dir.name, dest) + os.makedirs(os.path.dirname(dest_full), exist_ok=True) + shutil.copyfile(src, dest_full) + shutil.copystat(src, dest_full) + + for dir_spec in args.dir: + src, delim, dest = dir_spec.partition('=') + if delim == '': + assert dest == '' + dest = os.path.basename(src) + assert not os.path.isabs(dest), 'destination path must be relative (got %r)' % dest + dest_full = os.path.join(temp_dir.name, dest) + os.makedirs(os.path.dirname(dest_full), exist_ok=True) + shutil.copytree(src, dest_full) + + subprocess.run(('mksquashfs', temp_dir.name, args.output, '-noappend'), + check = True) + +if __name__ == '__main__': + main() From d16f7f67813a2b783babbec84b51a6efbe17a971 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 10:28:43 -0700 Subject: [PATCH 12/36] vm_runner: add hello world test case --- src/vm_runner/tests/build_hello_img.sh | 9 +++++++++ src/vm_runner/tests/hello.sh | 2 ++ src/vm_runner/tests/hello.toml | 6 ++++++ src/vm_runner/tests/hello_single.toml | 16 ++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 src/vm_runner/tests/build_hello_img.sh create mode 100755 src/vm_runner/tests/hello.sh create mode 100644 src/vm_runner/tests/hello.toml create mode 100644 src/vm_runner/tests/hello_single.toml diff --git a/src/vm_runner/tests/build_hello_img.sh b/src/vm_runner/tests/build_hello_img.sh new file mode 100644 index 00000000..05e0bdb3 --- /dev/null +++ b/src/vm_runner/tests/build_hello_img.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -euo pipefail + +tests_dir="$(dirname "$0")" + +python3 "$tests_dir/../build_application_image.py" \ + -f "$tests_dir/hello.sh" \ + -f "$tests_dir/hello.toml=runner.toml" \ + -o "$tests_dir/hello.img" diff --git a/src/vm_runner/tests/hello.sh b/src/vm_runner/tests/hello.sh new file mode 100755 index 00000000..1e1c90a8 --- /dev/null +++ b/src/vm_runner/tests/hello.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo 'Hello, World!' diff --git a/src/vm_runner/tests/hello.toml b/src/vm_runner/tests/hello.toml new file mode 100644 index 00000000..49e123a8 --- /dev/null +++ b/src/vm_runner/tests/hello.toml @@ -0,0 +1,6 @@ +mode = "exec" + +[[process]] +type = "shell" +command = "/opt/opensut/app/hello.sh" + diff --git a/src/vm_runner/tests/hello_single.toml b/src/vm_runner/tests/hello_single.toml new file mode 100644 index 00000000..74a9084a --- /dev/null +++ b/src/vm_runner/tests/hello_single.toml @@ -0,0 +1,16 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' + +[process.disk.vda] +format = "qcow2" +path = "../pkvm_setup/vms/disk_host.img" + +[process.disk.vdb] +format = "raw" +path = "tests/hello.img" From d5de6ecf8fc51c0509abf4b0937cd193f1304c13 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 13:21:25 -0700 Subject: [PATCH 13/36] vm_runner: add read_only option for vm disks --- src/vm_runner/src/config.rs | 8 +++++--- src/vm_runner/src/lib.rs | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vm_runner/src/config.rs b/src/vm_runner/src/config.rs index 024a709a..2cb6bcd2 100644 --- a/src/vm_runner/src/config.rs +++ b/src/vm_runner/src/config.rs @@ -53,7 +53,7 @@ pub struct VmProcess { /// If set, use KVM. Otherwise, run emulation with no hardware support. /// /// Using KVM requires access to the `/dev/kvm` device. - #[serde(default = "const_true")] + #[serde(default = "const_bool::")] pub kvm: bool, /// Disk definitions. Devices must be named sequentially as `vda`, `vdb`, and so on. They @@ -79,8 +79,8 @@ pub struct VmProcess { pub gpio: IndexMap, } -fn const_true() -> bool { - true +fn const_bool() -> bool { + B } fn const_u32() -> u32 { @@ -92,6 +92,8 @@ fn const_u32() -> u32 { pub struct VmDisk { pub format: String, pub path: String, + #[serde(default = "const_bool::")] + pub read_only: bool, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index 12d9a7db..59dec73a 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -155,7 +155,9 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { "unsupported character in disk {} path: {:?}", name, d.path); assert!(["qcow2", "raw"].contains(&(&d.format as &str)), "unsupported format for disk {}: {:?}", name, d.format); - vm_cmd.args(&["-drive", &format!("if=virtio,format={},file={}", d.format, d.path)]); + let read_only = if d.read_only { "on" } else { "off" }; + vm_cmd.args(&["-drive", + &format!("if=virtio,format={},file={},read-only={}", d.format, d.path, read_only)]); } let config::VmNet { ref port_forward } = *net; From f58393fc4abe92b065ecb7a71a51ea989a061fe4 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 13:22:25 -0700 Subject: [PATCH 14/36] vm_runner: nested version of hello world test --- src/vm_runner/tests/build_hello_img.sh | 10 +++++++-- src/vm_runner/tests/hello_base_nested.toml | 21 +++++++++++++++++++ ...llo_single.toml => hello_base_single.toml} | 3 ++- .../tests/{hello.toml => hello_guest.toml} | 0 src/vm_runner/tests/hello_host.toml | 17 +++++++++++++++ 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 src/vm_runner/tests/hello_base_nested.toml rename src/vm_runner/tests/{hello_single.toml => hello_base_single.toml} (88%) rename src/vm_runner/tests/{hello.toml => hello_guest.toml} (100%) create mode 100644 src/vm_runner/tests/hello_host.toml diff --git a/src/vm_runner/tests/build_hello_img.sh b/src/vm_runner/tests/build_hello_img.sh index 05e0bdb3..c0a520e0 100644 --- a/src/vm_runner/tests/build_hello_img.sh +++ b/src/vm_runner/tests/build_hello_img.sh @@ -4,6 +4,12 @@ set -euo pipefail tests_dir="$(dirname "$0")" python3 "$tests_dir/../build_application_image.py" \ + -f "$tests_dir/hello_guest.toml=runner.toml" \ -f "$tests_dir/hello.sh" \ - -f "$tests_dir/hello.toml=runner.toml" \ - -o "$tests_dir/hello.img" + -o "$tests_dir/hello_guest.img" + +python3 "$tests_dir/../build_application_image.py" \ + -f "$tests_dir/hello_host.toml=runner.toml" \ + -f "$tests_dir/hello_guest.img" \ + -o "$tests_dir/hello_host.img" + diff --git a/src/vm_runner/tests/hello_base_nested.toml b/src/vm_runner/tests/hello_base_nested.toml new file mode 100644 index 00000000..86d3d02e --- /dev/null +++ b/src/vm_runner/tests/hello_base_nested.toml @@ -0,0 +1,21 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdc' + +[process.disk.vda] +format = "qcow2" +path = "../pkvm_setup/vms/disk_host.img" + +[process.disk.vdb] +format = "qcow2" +path = "../pkvm_setup/vms/disk_guest.img" + +[process.disk.vdc] +format = "raw" +path = "tests/hello_host.img" +read_only = true diff --git a/src/vm_runner/tests/hello_single.toml b/src/vm_runner/tests/hello_base_single.toml similarity index 88% rename from src/vm_runner/tests/hello_single.toml rename to src/vm_runner/tests/hello_base_single.toml index 74a9084a..a0aa67cb 100644 --- a/src/vm_runner/tests/hello_single.toml +++ b/src/vm_runner/tests/hello_base_single.toml @@ -13,4 +13,5 @@ path = "../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "raw" -path = "tests/hello.img" +path = "tests/hello_guest.img" +read_only = true diff --git a/src/vm_runner/tests/hello.toml b/src/vm_runner/tests/hello_guest.toml similarity index 100% rename from src/vm_runner/tests/hello.toml rename to src/vm_runner/tests/hello_guest.toml diff --git a/src/vm_runner/tests/hello_host.toml b/src/vm_runner/tests/hello_host.toml new file mode 100644 index 00000000..0df8572b --- /dev/null +++ b/src/vm_runner/tests/hello_host.toml @@ -0,0 +1,17 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = true +kernel = "/boot/vmlinuz" +initrd = "/boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' + +[process.disk.vda] +format = "raw" +path = "/dev/vdb" + +[process.disk.vdb] +format = "raw" +path = "/opt/opensut/app/hello_guest.img" +read_only = true From 66a5a19eac37f1ded1094a210c79ff950757996b Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 13:24:54 -0700 Subject: [PATCH 15/36] vm_runner: add config file for installing into the guest image --- src/vm_runner/install_config_guest.toml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/vm_runner/install_config_guest.toml diff --git a/src/vm_runner/install_config_guest.toml b/src/vm_runner/install_config_guest.toml new file mode 100644 index 00000000..bccca6a1 --- /dev/null +++ b/src/vm_runner/install_config_guest.toml @@ -0,0 +1,19 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run="/bin/bash /dev/vdb"' + +[process.disk.vda] +format = "qcow2" +path = "../pkvm_setup/vms/disk_guest.img" + +[process.disk.vdb] +format = "raw" +path = "install_helper.sh" + +[process.9p.vm_runner] +path = "." From 552dbd6698ec6faccc5885f729381b1e426ee721 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 15:45:10 -0700 Subject: [PATCH 16/36] vm_runner: add support for configuring serial ports --- src/vm_runner/src/config.rs | 32 +++++++++++++++++ src/vm_runner/src/lib.rs | 68 +++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/vm_runner/src/config.rs b/src/vm_runner/src/config.rs index 2cb6bcd2..ee8369a2 100644 --- a/src/vm_runner/src/config.rs +++ b/src/vm_runner/src/config.rs @@ -72,6 +72,14 @@ pub struct VmProcess { /// to `mount` in the guest to mount the filesystem. #[serde(default, rename = "9p")] pub fs_9p: HashMap, + /// Serial port / UART definitions. Devices must be named sequentially as `hvc0`, + /// `hvc1`, and so on. They will be presented to the guest in name order, so `hvc0` will + /// appear as `/dev/hvc0`, `hvc1` as `/dev/hvc1`, and so on. + /// + /// In addition, the default console can be configured by providing an entry named `ttyAMA0`. + /// Without such an entry, `ttyAMA0` will be automatically connected to stdio. + #[serde(default)] + pub serial: IndexMap, /// GPIO device definitions. Devices are added in order; the first one listed in the config /// file will be `/dev/gpiochip1`, the second will be `/dev/gpiochip2`, and so on. (Note that /// the guest will also have a `gpiochip0` provided automatically by QEMU.) @@ -116,6 +124,30 @@ pub struct Vm9P { pub path: String, } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "mode", rename_all = "snake_case")] +pub enum VmSerial { + /// Connect the serial port in the guest to stdin/stdout on the host. + Stdio, + /// Pass through one of the host's serial ports to the guest. + Passthrough(PassthroughSerial), + /// Listen for a Unix socket connection on the host, and connect it to the serial port in the + /// guest. + Unix(UnixSerial), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PassthroughSerial { + pub device: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct UnixSerial { + pub path: String, +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "mode", rename_all = "snake_case")] pub enum VmGpio { diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index 59dec73a..20c819c1 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -16,7 +16,7 @@ use nix::unistd::Pid; use nix::sys::wait::{WaitStatus, WaitPidFlag}; use shlex::Shlex; use toml; -use crate::config::{Config, Mode}; +use crate::config::{Config, Mode, VmSerial}; pub mod config; @@ -109,7 +109,7 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { let config::VmProcess { ref kernel, ref initrd, ref append, ram_mb, kvm, - ref disk, ref net, ref fs_9p, ref gpio, + ref disk, ref net, ref fs_9p, ref serial, ref gpio, } = *vm; let mut vm_cmd = Command::new("qemu-system-aarch64"); @@ -118,7 +118,6 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { vm_cmd.args(&["-M", "virt"]); vm_cmd.args(&["-smp", "4"]); vm_cmd.args(&["-m", &format!("{}", ram_mb)]); - vm_cmd.args(&["-nographic"]); // KVM if kvm { @@ -134,6 +133,7 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { vm_cmd.args(&["-device", "virtio-scsi-pci,id=scsi0"]); vm_cmd.args(&["-object", "rng-random,filename=/dev/urandom,id=rng0"]); vm_cmd.args(&["-device", "virtio-rng-pci,rng=rng0"]); + vm_cmd.args(&["-display", "none"]); // Kernel and related flags vm_cmd.args(&["-kernel", &kernel]); @@ -144,6 +144,68 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { vm_cmd.args(&["-append", &append]); } + + // Serial ports + + // Set up a character device for stdio and use it for the QEMU monitor. + vm_cmd.args(&["-chardev", "stdio,mux=on,id=char_stdio,signal=off"]); + vm_cmd.args(&["-mon", "chardev=char_stdio,mode=readline"]); + + /// Handle serial port configuration. This will add `-chardev` definitions to `cmd` if needed, + /// and will return the `chardev` name for use with `-serial` or `-device`. + /// + /// `name` is the name of the device being configured, which is used to generate unique + /// `chardev` names and for error reporting. + fn handle_serial(vm_cmd: &mut Command, name: &str, s: &VmSerial) -> String { + match *s { + VmSerial::Stdio => "char_stdio".to_string(), + VmSerial::Passthrough(ref ps) => { + assert!(!needs_escaping_for_qemu(&ps.device), + "unsupported character in serial {} device: {:?}", name, ps.device); + vm_cmd.args(&["-chardev", + &format!("serial,id=char_{},path={}", name, ps.device)]); + format!("char_{}", name) + }, + VmSerial::Unix(ref us) => { + assert!(!needs_escaping_for_qemu(&us.path), + "unsupported character in serial {} path: {:?}", name, us.path); + vm_cmd.args(&["-chardev", + &format!("socket,id=char_{},path={},server=on,wait=off", name, us.path)]); + format!("char_{}", name) + }, + } + } + + let serial_range; + const DEFAULT_SERIAL_NAME: &str = "ttyAMA0"; + if let Some(s) = serial.get(DEFAULT_SERIAL_NAME) { + let chardev = handle_serial(&mut vm_cmd, DEFAULT_SERIAL_NAME, s); + vm_cmd.args(&["-serial", &format!("chardev:{}", chardev)]); + serial_range = 0 .. serial.len() - 1; + } else { + // Default behavior: connect `ttyAMA0` to stdio + vm_cmd.args(&["-serial", "chardev:char_stdio"]); + serial_range = 0 .. serial.len(); + } + + // A `virtio-serial-pci` device provides the QEMU-internal `virtio-serial-bus`, which later + // `virtconsole` devices attach to. + vm_cmd.args(&["-device", "virtio-serial-pci"]); + // A single `virtio-serial-pci` provides 8 ports. + const MAX_SERIAL_DEVICES: usize = 8; + assert!(serial_range.end - serial_range.start <= MAX_SERIAL_DEVICES, + "too many serial devices (max = {})", MAX_SERIAL_DEVICES); + + for i in serial_range { + let name = format!("hvc{}", i); + let s = serial.get(&name) + .unwrap_or_else(|| panic!("non-contiguous serial port definitions: missing {}", name)); + let chardev = handle_serial(&mut vm_cmd, &name, s); + vm_cmd.args(&["-device", &format!("virtconsole,chardev={}", chardev)]); + } + + + // Disks for i in 0 .. disk.len() { let i = u8::try_from(i).unwrap(); let letter = (b'a' + i) as char; From ad48cac3b79ee7600af90b5d067bfa67bf6a5d8e Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 15:45:53 -0700 Subject: [PATCH 17/36] vm_runner: add mps test configs --- src/vm_runner/tests/build_mps_img.sh | 16 +++++++++++++++ src/vm_runner/tests/mps.sh | 7 +++++++ src/vm_runner/tests/mps_base_nested.toml | 25 ++++++++++++++++++++++++ src/vm_runner/tests/mps_base_single.toml | 21 ++++++++++++++++++++ src/vm_runner/tests/mps_guest.toml | 6 ++++++ src/vm_runner/tests/mps_host.toml | 21 ++++++++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 src/vm_runner/tests/build_mps_img.sh create mode 100755 src/vm_runner/tests/mps.sh create mode 100644 src/vm_runner/tests/mps_base_nested.toml create mode 100644 src/vm_runner/tests/mps_base_single.toml create mode 100644 src/vm_runner/tests/mps_guest.toml create mode 100644 src/vm_runner/tests/mps_host.toml diff --git a/src/vm_runner/tests/build_mps_img.sh b/src/vm_runner/tests/build_mps_img.sh new file mode 100644 index 00000000..0f6570d9 --- /dev/null +++ b/src/vm_runner/tests/build_mps_img.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -euo pipefail + +tests_dir="$(dirname "$0")" + +python3 "$tests_dir/../build_application_image.py" \ + -f "$tests_dir/mps_guest.toml=runner.toml" \ + -f "$tests_dir/../../../components/mission_protection_system/src/rts=mps" \ + -f "$tests_dir/mps.sh" \ + -o "$tests_dir/mps_guest.img" + +python3 "$tests_dir/../build_application_image.py" \ + -f "$tests_dir/mps_host.toml=runner.toml" \ + -f "$tests_dir/mps_guest.img" \ + -o "$tests_dir/mps_host.img" + diff --git a/src/vm_runner/tests/mps.sh b/src/vm_runner/tests/mps.sh new file mode 100755 index 00000000..57cb053c --- /dev/null +++ b/src/vm_runner/tests/mps.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -euo pipefail + +mps_bin="$(dirname "$0")/mps" +echo "Starting mps" +setsid -c "$mps_bin" /dev/hvc0 2>/dev/hvc0 +echo "mps exited with code $?" diff --git a/src/vm_runner/tests/mps_base_nested.toml b/src/vm_runner/tests/mps_base_nested.toml new file mode 100644 index 00000000..cd8db286 --- /dev/null +++ b/src/vm_runner/tests/mps_base_nested.toml @@ -0,0 +1,25 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdc' + +[process.disk.vda] +format = "qcow2" +path = "../pkvm_setup/vms/disk_host.img" + +[process.disk.vdb] +format = "qcow2" +path = "../pkvm_setup/vms/disk_guest.img" + +[process.disk.vdc] +format = "raw" +path = "tests/mps_host.img" +read_only = true + +[process.serial.hvc0] +mode = "unix" +path = "./serial.socket" diff --git a/src/vm_runner/tests/mps_base_single.toml b/src/vm_runner/tests/mps_base_single.toml new file mode 100644 index 00000000..13b3e98b --- /dev/null +++ b/src/vm_runner/tests/mps_base_single.toml @@ -0,0 +1,21 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' + +[process.disk.vda] +format = "qcow2" +path = "../pkvm_setup/vms/disk_host.img" + +[process.disk.vdb] +format = "raw" +path = "tests/mps_guest.img" +read_only = true + +[process.serial.hvc0] +mode = "unix" +path = "./serial.socket" diff --git a/src/vm_runner/tests/mps_guest.toml b/src/vm_runner/tests/mps_guest.toml new file mode 100644 index 00000000..674a31eb --- /dev/null +++ b/src/vm_runner/tests/mps_guest.toml @@ -0,0 +1,6 @@ +mode = "exec" + +[[process]] +type = "shell" +command = "/opt/opensut/app/mps.sh" + diff --git a/src/vm_runner/tests/mps_host.toml b/src/vm_runner/tests/mps_host.toml new file mode 100644 index 00000000..27de9732 --- /dev/null +++ b/src/vm_runner/tests/mps_host.toml @@ -0,0 +1,21 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = true +kernel = "/boot/vmlinuz" +initrd = "/boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' + +[process.disk.vda] +format = "raw" +path = "/dev/vdb" + +[process.disk.vdb] +format = "raw" +path = "/opt/opensut/app/mps_guest.img" +read_only = true + +[process.serial.hvc0] +mode = "passthrough" +device = "/dev/hvc0" From 5aad5c7f43018e9e9892b5756665497d645f1768 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 17:13:29 -0700 Subject: [PATCH 18/36] mps: update self_test_data/test_to_c.py for cryptol output format changes --- .../src/self_test_data/test.csv | 200 +++++++++++++++++- .../src/self_test_data/test_to_c.py | 12 +- .../src/self_test_data/tests.inc.c | 80 +++---- 3 files changed, 240 insertions(+), 52 deletions(-) diff --git a/components/mission_protection_system/src/self_test_data/test.csv b/components/mission_protection_system/src/self_test_data/test.csv index 60d3d069..9fc447ef 100644 --- a/components/mission_protection_system/src/self_test_data/test.csv +++ b/components/mission_protection_system/src/self_test_data/test.csv @@ -1,10 +1,190 @@ -0x1 [[0xb56b, 0x9b67], [0x116d, 0x305e], [0x1e6a, 0x6d2b], [0x4278, 0x3cac]] [[0xbea4, 0x8cff, 0x8276], [0x73c1, 0x3306, 0x4b66], [0x1306, 0x8017, 0x061b], [0x0681, 0xe654, 0x563f]] 0x1 0x2 -0x1 [[0xb4bf, 0xf2a5], [0xb729, 0x0592], [0xc445, 0x3267], [0xdb63, 0x98d5]] [[0x0af4, 0xb1ce, 0xa1c2], [0x3042, 0x23f3, 0x50b6], [0x578e, 0xc16a, 0xad24], [0xebd5, 0xc4b4, 0x6ea9]] 0x2 0x3 -0x1 [[0x9e64, 0x646e], [0xec38, 0xa171], [0x8b17, 0x26fa], [0x6e11, 0x13e9]] [[0x4301, 0xeed8, 0x407c], [0xbdd9, 0x5e64, 0x978f], [0x337e, 0xb6b8, 0xc261], [0xf272, 0xbec3, 0xe020]] 0x3 0x3 -0x1 [[0xc707, 0x909b], [0x5950, 0x5023], [0x2174, 0x7fbc], [0x9c8e, 0xb1c5]] [[0x450a, 0x5387, 0x4251], [0x54f7, 0x7331, 0x610c], [0x2246, 0xc23c, 0xe353], [0xd742, 0x11f0, 0x5c9b]] 0x2 0x2 -0x1 [[0x64a7, 0xfc58], [0x73c5, 0x0dca], [0x76be, 0x322f], [0xf275, 0x856e]] [[0x71fb, 0xe1fc, 0xc226], [0xdca3, 0x47de, 0x2a2c], [0x365b, 0xe8d8, 0x5e63], [0x7d75, 0xf509, 0x0d46]] 0x0 0x0 -0x1 [[0x3379, 0x097d], [0x8d0a, 0xf187], [0xb18d, 0x6bde], [0x31b3, 0x1656]] [[0x2a5f, 0xa347, 0xfd69], [0x99a0, 0xb82f, 0x9554], [0x2106, 0x7623, 0x08dd], [0xabf1, 0xc54c, 0x1237]] 0x0 0x2 -0x1 [[0xe71e, 0x506e], [0x84a3, 0xf7d7], [0x3c66, 0xfdb7], [0x354c, 0xa3bb]] [[0x79e7, 0xec8c, 0x73c2], [0x9d4c, 0x395b, 0x4a52], [0x783c, 0xf1cc, 0xd0ee], [0x8171, 0xc116, 0x08bd]] 0x0 0x0 -0x1 [[0x33fe, 0x41d3], [0x1842, 0xce9e], [0xa731, 0x6312], [0xbabb, 0xaa2e]] [[0xd683, 0x8077, 0x2d0d], [0x0c50, 0xa354, 0xb23e], [0xc806, 0xa680, 0x25d1], [0x965f, 0xba1f, 0x7f91]] 0x1 0x3 -0x3 [[0x8f55, 0xa308], [0x8910, 0xc8f3], [0xed53, 0xa96e], [0x6b72, 0xb094]] [[0x8024, 0x2af2, 0x8a77], [0xd392, 0x6b95, 0xc5e4], [0xd167, 0x78eb, 0xae62], [0xd786, 0x2183, 0xeda3]] 0x3 0x3 -0x1 [[0x03a4, 0x7579], [0xfef5, 0x193e], [0x8381, 0xbdd3], [0x649d, 0xae79]] [[0xc42c, 0xd33a, 0x2cc9], [0xa687, 0x657b, 0xbf3b], [0x48d2, 0xc9b1, 0x2b48], [0xb123, 0x8814, 0x497c]] 0x2 0x3 +0x1 [[0x753c, +0x8567], +[0x9941, +0x9047], +[0x010f, +0x619e], +[0x6c16, +0xa5e2]] [[0xbe86, +0x2ac0, +0x508e], +[0xf929, +0x7f97, +0x2e07], +[0x92f3, +0xdec1, +0xe896], +[0xca3a, +0x4532, +0x67ac]] 0x1 0x3 +0x1 [[0xe2bc, +0x48ad], +[0x20ab, +0x6b44], +[0x5074, +0x8063], +[0x74b5, +0xfadb]] [[0xe5ce, +0x151f, +0x69fc], +[0x933a, +0xe0e5, +0x372d], +[0x52d0, +0x5320, +0x6273], +[0x6d2a, +0xc90a, +0x6c50]] 0x3 0x1 +0x3 [[0xaf29, +0xfb7f], +[0xd293, +0xcaa6], +[0x101d, +0xb852], +[0x10da, +0xfa5c]] [[0x1f9b, +0xf7d2, +0x64c2], +[0xfa38, +0x35e3, +0xd88e], +[0x0660, +0xe4b8, +0xb256], +[0x5071, +0x5b45, +0xa947]] 0x3 0x1 +0x3 [[0xfd39, +0x21d4], +[0xdb77, +0xff43], +[0x899b, +0xd07b], +[0x576c, +0xe806]] [[0xf591, +0x3092, +0xdeab], +[0x53b7, +0xcdd8, +0x3125], +[0x71c2, +0x375c, +0x4d54], +[0xf62f, +0xe4f1, +0x7d1a]] 0x0 0x3 +0x1 [[0x2111, +0x72a4], +[0x5ef8, +0xc6af], +[0x702d, +0x1f3f], +[0x7030, +0x1828]] [[0x70bd, +0xa499, +0xb6a4], +[0x3749, +0x0bc8, +0x0170], +[0x90ef, +0x1291, +0xf908], +[0x66b2, +0xc0a7, +0x4c86]] 0x0 0x2 +0x1 [[0x205c, +0x813b], +[0xdfcc, +0xb70d], +[0x877f, +0x930a], +[0x4300, +0x3a90]] [[0x1966, +0xf30a, +0x1237], +[0xcbb6, +0xdf45, +0xb3a7], +[0xb5d1, +0x9b11, +0x1e04], +[0x4ca2, +0xa2c5, +0xe2b2]] 0x2 0x1 +0x1 [[0x64f6, +0x2140], +[0x839a, +0x55a7], +[0x6255, +0x27ed], +[0x9baa, +0xb966]] [[0xc51d, +0x1552, +0xc126], +[0x86c1, +0x66ae, +0x8294], +[0xb374, +0x4385, +0x5ffb], +[0x3ff4, +0x9a3c, +0xb897]] 0x2 0x0 +0x1 [[0x77b9, +0x9969], +[0x648b, +0x1459], +[0x1c41, +0x5f5e], +[0x30a9, +0x9941]] [[0xa6c2, +0xc89c, +0x854d], +[0xbff7, +0xd30a, +0xae30], +[0x733a, +0xacbc, +0x5680], +[0xde27, +0x0ba9, +0xcc1c]] 0x1 0x2 +0x3 [[0x95a8, +0xecfd], +[0x617d, +0xeb9b], +[0x74c8, +0xfb58], +[0x73e2, +0x378c]] [[0x1674, +0x53e5, +0xabbd], +[0x6ee4, +0xe0c2, +0xdd77], +[0x26ab, +0x9e64, +0x6e96], +[0x0c21, +0x13a3, +0x9d27]] 0x3 0x1 +0x3 [[0xd477, +0x4235], +[0xa5b1, +0xf5e3], +[0x30ec, +0xf265], +[0x014e, +0x8fd2]] [[0x31b9, +0x7d86, +0x15e4], +[0xea39, +0xf888, +0x9fbe], +[0x8247, +0x7fbd, +0x5b29], +[0xdb42, +0x301d, +0x7ed8]] 0x2 0x0 diff --git a/components/mission_protection_system/src/self_test_data/test_to_c.py b/components/mission_protection_system/src/self_test_data/test_to_c.py index 6e88ff76..474639fa 100755 --- a/components/mission_protection_system/src/self_test_data/test_to_c.py +++ b/components/mission_protection_system/src/self_test_data/test_to_c.py @@ -27,8 +27,16 @@ def render(test): return f"{test}" with open(sys.argv[1]) as csv_file: - reader = csv.reader(csv_file,delimiter='\t') - foos = [render(tc) for testcase in reader for tc in expand(testcase)] + line_buf = '' + foos = [] + for line in csv_file: + line = line.rstrip() + line_buf += line + if not line_buf.endswith(','): + testcase = line_buf.split('\t') + foos.extend(render(tc) for tc in expand(testcase)) + line_buf = '' + tests = ",\n".join(foos) print(f"// Tests generated from {sys.argv[1]}") print(tests) diff --git a/components/mission_protection_system/src/self_test_data/tests.inc.c b/components/mission_protection_system/src/self_test_data/tests.inc.c index 72900933..b8a9cfe6 100644 --- a/components/mission_protection_system/src/self_test_data/tests.inc.c +++ b/components/mission_protection_system/src/self_test_data/tests.inc.c @@ -1,41 +1,41 @@ // Tests generated from test.csv -{{{46443, 39783}, {4461, 12382}, {7786, 27947}, {17016, 15532}}, {{48804, 36095, 33398}, {29633, 13062, 19302}, {4870, 32791, 1563}, {1665, 58964, 22079}}, {1, 0}, 0, 0, 0}, -{{{46443, 39783}, {4461, 12382}, {7786, 27947}, {17016, 15532}}, {{48804, 36095, 33398}, {29633, 13062, 19302}, {4870, 32791, 1563}, {1665, 58964, 22079}}, {1, 0}, 1, 0, 0}, -{{{46443, 39783}, {4461, 12382}, {7786, 27947}, {17016, 15532}}, {{48804, 36095, 33398}, {29633, 13062, 19302}, {4870, 32791, 1563}, {1665, 58964, 22079}}, {1, 0}, 0, 1, 1}, -{{{46443, 39783}, {4461, 12382}, {7786, 27947}, {17016, 15532}}, {{48804, 36095, 33398}, {29633, 13062, 19302}, {4870, 32791, 1563}, {1665, 58964, 22079}}, {1, 0}, 1, 1, 1}, -{{{46271, 62117}, {46889, 1426}, {50245, 12903}, {56163, 39125}}, {{2804, 45518, 41410}, {12354, 9203, 20662}, {22414, 49514, 44324}, {60373, 50356, 28329}}, {2, 3}, 0, 0, 0}, -{{{46271, 62117}, {46889, 1426}, {50245, 12903}, {56163, 39125}}, {{2804, 45518, 41410}, {12354, 9203, 20662}, {22414, 49514, 44324}, {60373, 50356, 28329}}, {2, 3}, 1, 0, 0}, -{{{46271, 62117}, {46889, 1426}, {50245, 12903}, {56163, 39125}}, {{2804, 45518, 41410}, {12354, 9203, 20662}, {22414, 49514, 44324}, {60373, 50356, 28329}}, {2, 3}, 0, 1, 1}, -{{{46271, 62117}, {46889, 1426}, {50245, 12903}, {56163, 39125}}, {{2804, 45518, 41410}, {12354, 9203, 20662}, {22414, 49514, 44324}, {60373, 50356, 28329}}, {2, 3}, 1, 1, 1}, -{{{40548, 25710}, {60472, 41329}, {35607, 9978}, {28177, 5097}}, {{17153, 61144, 16508}, {48601, 24164, 38799}, {13182, 46776, 49761}, {62066, 48835, 57376}}, {3, 0}, 0, 0, 0}, -{{{40548, 25710}, {60472, 41329}, {35607, 9978}, {28177, 5097}}, {{17153, 61144, 16508}, {48601, 24164, 38799}, {13182, 46776, 49761}, {62066, 48835, 57376}}, {3, 0}, 1, 0, 0}, -{{{40548, 25710}, {60472, 41329}, {35607, 9978}, {28177, 5097}}, {{17153, 61144, 16508}, {48601, 24164, 38799}, {13182, 46776, 49761}, {62066, 48835, 57376}}, {3, 0}, 0, 1, 1}, -{{{40548, 25710}, {60472, 41329}, {35607, 9978}, {28177, 5097}}, {{17153, 61144, 16508}, {48601, 24164, 38799}, {13182, 46776, 49761}, {62066, 48835, 57376}}, {3, 0}, 1, 1, 1}, -{{{50951, 37019}, {22864, 20515}, {8564, 32700}, {40078, 45509}}, {{17674, 21383, 16977}, {21751, 29489, 24844}, {8774, 49724, 58195}, {55106, 4592, 23707}}, {2, 1}, 0, 0, 0}, -{{{50951, 37019}, {22864, 20515}, {8564, 32700}, {40078, 45509}}, {{17674, 21383, 16977}, {21751, 29489, 24844}, {8774, 49724, 58195}, {55106, 4592, 23707}}, {2, 1}, 1, 0, 0}, -{{{50951, 37019}, {22864, 20515}, {8564, 32700}, {40078, 45509}}, {{17674, 21383, 16977}, {21751, 29489, 24844}, {8774, 49724, 58195}, {55106, 4592, 23707}}, {2, 1}, 0, 1, 1}, -{{{50951, 37019}, {22864, 20515}, {8564, 32700}, {40078, 45509}}, {{17674, 21383, 16977}, {21751, 29489, 24844}, {8774, 49724, 58195}, {55106, 4592, 23707}}, {2, 1}, 1, 1, 1}, -{{{25767, 64600}, {29637, 3530}, {30398, 12847}, {62069, 34158}}, {{29179, 57852, 49702}, {56483, 18398, 10796}, {13915, 59608, 24163}, {32117, 62729, 3398}}, {0, 1}, 0, 0, 0}, -{{{25767, 64600}, {29637, 3530}, {30398, 12847}, {62069, 34158}}, {{29179, 57852, 49702}, {56483, 18398, 10796}, {13915, 59608, 24163}, {32117, 62729, 3398}}, {0, 1}, 1, 0, 0}, -{{{25767, 64600}, {29637, 3530}, {30398, 12847}, {62069, 34158}}, {{29179, 57852, 49702}, {56483, 18398, 10796}, {13915, 59608, 24163}, {32117, 62729, 3398}}, {0, 1}, 0, 1, 1}, -{{{25767, 64600}, {29637, 3530}, {30398, 12847}, {62069, 34158}}, {{29179, 57852, 49702}, {56483, 18398, 10796}, {13915, 59608, 24163}, {32117, 62729, 3398}}, {0, 1}, 1, 1, 1}, -{{{13177, 2429}, {36106, 61831}, {45453, 27614}, {12723, 5718}}, {{10847, 41799, 64873}, {39328, 47151, 38228}, {8454, 30243, 2269}, {44017, 50508, 4663}}, {0, 3}, 0, 0, 0}, -{{{13177, 2429}, {36106, 61831}, {45453, 27614}, {12723, 5718}}, {{10847, 41799, 64873}, {39328, 47151, 38228}, {8454, 30243, 2269}, {44017, 50508, 4663}}, {0, 3}, 1, 0, 0}, -{{{13177, 2429}, {36106, 61831}, {45453, 27614}, {12723, 5718}}, {{10847, 41799, 64873}, {39328, 47151, 38228}, {8454, 30243, 2269}, {44017, 50508, 4663}}, {0, 3}, 0, 1, 1}, -{{{13177, 2429}, {36106, 61831}, {45453, 27614}, {12723, 5718}}, {{10847, 41799, 64873}, {39328, 47151, 38228}, {8454, 30243, 2269}, {44017, 50508, 4663}}, {0, 3}, 1, 1, 1}, -{{{59166, 20590}, {33955, 63447}, {15462, 64951}, {13644, 41915}}, {{31207, 60556, 29634}, {40268, 14683, 19026}, {30780, 61900, 53486}, {33137, 49430, 2237}}, {0, 1}, 0, 0, 0}, -{{{59166, 20590}, {33955, 63447}, {15462, 64951}, {13644, 41915}}, {{31207, 60556, 29634}, {40268, 14683, 19026}, {30780, 61900, 53486}, {33137, 49430, 2237}}, {0, 1}, 1, 0, 0}, -{{{59166, 20590}, {33955, 63447}, {15462, 64951}, {13644, 41915}}, {{31207, 60556, 29634}, {40268, 14683, 19026}, {30780, 61900, 53486}, {33137, 49430, 2237}}, {0, 1}, 0, 1, 1}, -{{{59166, 20590}, {33955, 63447}, {15462, 64951}, {13644, 41915}}, {{31207, 60556, 29634}, {40268, 14683, 19026}, {30780, 61900, 53486}, {33137, 49430, 2237}}, {0, 1}, 1, 1, 1}, -{{{13310, 16851}, {6210, 52894}, {42801, 25362}, {47803, 43566}}, {{54915, 32887, 11533}, {3152, 41812, 45630}, {51206, 42624, 9681}, {38495, 47647, 32657}}, {1, 2}, 0, 0, 0}, -{{{13310, 16851}, {6210, 52894}, {42801, 25362}, {47803, 43566}}, {{54915, 32887, 11533}, {3152, 41812, 45630}, {51206, 42624, 9681}, {38495, 47647, 32657}}, {1, 2}, 1, 0, 0}, -{{{13310, 16851}, {6210, 52894}, {42801, 25362}, {47803, 43566}}, {{54915, 32887, 11533}, {3152, 41812, 45630}, {51206, 42624, 9681}, {38495, 47647, 32657}}, {1, 2}, 0, 1, 1}, -{{{13310, 16851}, {6210, 52894}, {42801, 25362}, {47803, 43566}}, {{54915, 32887, 11533}, {3152, 41812, 45630}, {51206, 42624, 9681}, {38495, 47647, 32657}}, {1, 2}, 1, 1, 1}, -{{{36693, 41736}, {35088, 51443}, {60755, 43374}, {27506, 45204}}, {{32804, 10994, 35447}, {54162, 27541, 50660}, {53607, 30955, 44642}, {55174, 8579, 60835}}, {3, 0}, 0, 0, 1}, -{{{36693, 41736}, {35088, 51443}, {60755, 43374}, {27506, 45204}}, {{32804, 10994, 35447}, {54162, 27541, 50660}, {53607, 30955, 44642}, {55174, 8579, 60835}}, {3, 0}, 1, 0, 1}, -{{{36693, 41736}, {35088, 51443}, {60755, 43374}, {27506, 45204}}, {{32804, 10994, 35447}, {54162, 27541, 50660}, {53607, 30955, 44642}, {55174, 8579, 60835}}, {3, 0}, 0, 1, 1}, -{{{36693, 41736}, {35088, 51443}, {60755, 43374}, {27506, 45204}}, {{32804, 10994, 35447}, {54162, 27541, 50660}, {53607, 30955, 44642}, {55174, 8579, 60835}}, {3, 0}, 1, 1, 1}, -{{{932, 30073}, {65269, 6462}, {33665, 48595}, {25757, 44665}}, {{50220, 54074, 11465}, {42631, 25979, 48955}, {18642, 51633, 11080}, {45347, 34836, 18812}}, {2, 3}, 0, 0, 0}, -{{{932, 30073}, {65269, 6462}, {33665, 48595}, {25757, 44665}}, {{50220, 54074, 11465}, {42631, 25979, 48955}, {18642, 51633, 11080}, {45347, 34836, 18812}}, {2, 3}, 1, 0, 0}, -{{{932, 30073}, {65269, 6462}, {33665, 48595}, {25757, 44665}}, {{50220, 54074, 11465}, {42631, 25979, 48955}, {18642, 51633, 11080}, {45347, 34836, 18812}}, {2, 3}, 0, 1, 1}, -{{{932, 30073}, {65269, 6462}, {33665, 48595}, {25757, 44665}}, {{50220, 54074, 11465}, {42631, 25979, 48955}, {18642, 51633, 11080}, {45347, 34836, 18812}}, {2, 3}, 1, 1, 1} +{{{30012, 34151}, {39233, 36935}, {271, 24990}, {27670, 42466}}, {{48774, 10944, 20622}, {63785, 32663, 11783}, {37619, 57025, 59542}, {51770, 17714, 26540}}, {1, 2}, 0, 0, 0}, +{{{30012, 34151}, {39233, 36935}, {271, 24990}, {27670, 42466}}, {{48774, 10944, 20622}, {63785, 32663, 11783}, {37619, 57025, 59542}, {51770, 17714, 26540}}, {1, 2}, 1, 0, 0}, +{{{30012, 34151}, {39233, 36935}, {271, 24990}, {27670, 42466}}, {{48774, 10944, 20622}, {63785, 32663, 11783}, {37619, 57025, 59542}, {51770, 17714, 26540}}, {1, 2}, 0, 1, 1}, +{{{30012, 34151}, {39233, 36935}, {271, 24990}, {27670, 42466}}, {{48774, 10944, 20622}, {63785, 32663, 11783}, {37619, 57025, 59542}, {51770, 17714, 26540}}, {1, 2}, 1, 1, 1}, +{{{58044, 18605}, {8363, 27460}, {20596, 32867}, {29877, 64219}}, {{58830, 5407, 27132}, {37690, 57573, 14125}, {21200, 21280, 25203}, {27946, 51466, 27728}}, {3, 1}, 0, 0, 0}, +{{{58044, 18605}, {8363, 27460}, {20596, 32867}, {29877, 64219}}, {{58830, 5407, 27132}, {37690, 57573, 14125}, {21200, 21280, 25203}, {27946, 51466, 27728}}, {3, 1}, 1, 0, 0}, +{{{58044, 18605}, {8363, 27460}, {20596, 32867}, {29877, 64219}}, {{58830, 5407, 27132}, {37690, 57573, 14125}, {21200, 21280, 25203}, {27946, 51466, 27728}}, {3, 1}, 0, 1, 1}, +{{{58044, 18605}, {8363, 27460}, {20596, 32867}, {29877, 64219}}, {{58830, 5407, 27132}, {37690, 57573, 14125}, {21200, 21280, 25203}, {27946, 51466, 27728}}, {3, 1}, 1, 1, 1}, +{{{44841, 64383}, {53907, 51878}, {4125, 47186}, {4314, 64092}}, {{8091, 63442, 25794}, {64056, 13795, 55438}, {1632, 58552, 45654}, {20593, 23365, 43335}}, {3, 1}, 0, 0, 1}, +{{{44841, 64383}, {53907, 51878}, {4125, 47186}, {4314, 64092}}, {{8091, 63442, 25794}, {64056, 13795, 55438}, {1632, 58552, 45654}, {20593, 23365, 43335}}, {3, 1}, 1, 0, 1}, +{{{44841, 64383}, {53907, 51878}, {4125, 47186}, {4314, 64092}}, {{8091, 63442, 25794}, {64056, 13795, 55438}, {1632, 58552, 45654}, {20593, 23365, 43335}}, {3, 1}, 0, 1, 1}, +{{{44841, 64383}, {53907, 51878}, {4125, 47186}, {4314, 64092}}, {{8091, 63442, 25794}, {64056, 13795, 55438}, {1632, 58552, 45654}, {20593, 23365, 43335}}, {3, 1}, 1, 1, 1}, +{{{64825, 8660}, {56183, 65347}, {35227, 53371}, {22380, 59398}}, {{62865, 12434, 57003}, {21431, 52696, 12581}, {29122, 14172, 19796}, {63023, 58609, 32026}}, {0, 1}, 0, 0, 1}, +{{{64825, 8660}, {56183, 65347}, {35227, 53371}, {22380, 59398}}, {{62865, 12434, 57003}, {21431, 52696, 12581}, {29122, 14172, 19796}, {63023, 58609, 32026}}, {0, 1}, 1, 0, 1}, +{{{64825, 8660}, {56183, 65347}, {35227, 53371}, {22380, 59398}}, {{62865, 12434, 57003}, {21431, 52696, 12581}, {29122, 14172, 19796}, {63023, 58609, 32026}}, {0, 1}, 0, 1, 1}, +{{{64825, 8660}, {56183, 65347}, {35227, 53371}, {22380, 59398}}, {{62865, 12434, 57003}, {21431, 52696, 12581}, {29122, 14172, 19796}, {63023, 58609, 32026}}, {0, 1}, 1, 1, 1}, +{{{8465, 29348}, {24312, 50863}, {28717, 7999}, {28720, 6184}}, {{28861, 42137, 46756}, {14153, 3016, 368}, {37103, 4753, 63752}, {26290, 49319, 19590}}, {0, 3}, 0, 0, 0}, +{{{8465, 29348}, {24312, 50863}, {28717, 7999}, {28720, 6184}}, {{28861, 42137, 46756}, {14153, 3016, 368}, {37103, 4753, 63752}, {26290, 49319, 19590}}, {0, 3}, 1, 0, 0}, +{{{8465, 29348}, {24312, 50863}, {28717, 7999}, {28720, 6184}}, {{28861, 42137, 46756}, {14153, 3016, 368}, {37103, 4753, 63752}, {26290, 49319, 19590}}, {0, 3}, 0, 1, 1}, +{{{8465, 29348}, {24312, 50863}, {28717, 7999}, {28720, 6184}}, {{28861, 42137, 46756}, {14153, 3016, 368}, {37103, 4753, 63752}, {26290, 49319, 19590}}, {0, 3}, 1, 1, 1}, +{{{8284, 33083}, {57292, 46861}, {34687, 37642}, {17152, 14992}}, {{6502, 62218, 4663}, {52150, 57157, 45991}, {46545, 39697, 7684}, {19618, 41669, 58034}}, {2, 0}, 0, 0, 0}, +{{{8284, 33083}, {57292, 46861}, {34687, 37642}, {17152, 14992}}, {{6502, 62218, 4663}, {52150, 57157, 45991}, {46545, 39697, 7684}, {19618, 41669, 58034}}, {2, 0}, 1, 0, 0}, +{{{8284, 33083}, {57292, 46861}, {34687, 37642}, {17152, 14992}}, {{6502, 62218, 4663}, {52150, 57157, 45991}, {46545, 39697, 7684}, {19618, 41669, 58034}}, {2, 0}, 0, 1, 1}, +{{{8284, 33083}, {57292, 46861}, {34687, 37642}, {17152, 14992}}, {{6502, 62218, 4663}, {52150, 57157, 45991}, {46545, 39697, 7684}, {19618, 41669, 58034}}, {2, 0}, 1, 1, 1}, +{{{25846, 8512}, {33690, 21927}, {25173, 10221}, {39850, 47462}}, {{50461, 5458, 49446}, {34497, 26286, 33428}, {45940, 17285, 24571}, {16372, 39484, 47255}}, {2, 3}, 0, 0, 0}, +{{{25846, 8512}, {33690, 21927}, {25173, 10221}, {39850, 47462}}, {{50461, 5458, 49446}, {34497, 26286, 33428}, {45940, 17285, 24571}, {16372, 39484, 47255}}, {2, 3}, 1, 0, 0}, +{{{25846, 8512}, {33690, 21927}, {25173, 10221}, {39850, 47462}}, {{50461, 5458, 49446}, {34497, 26286, 33428}, {45940, 17285, 24571}, {16372, 39484, 47255}}, {2, 3}, 0, 1, 1}, +{{{25846, 8512}, {33690, 21927}, {25173, 10221}, {39850, 47462}}, {{50461, 5458, 49446}, {34497, 26286, 33428}, {45940, 17285, 24571}, {16372, 39484, 47255}}, {2, 3}, 1, 1, 1}, +{{{30649, 39273}, {25739, 5209}, {7233, 24414}, {12457, 39233}}, {{42690, 51356, 34125}, {49143, 54026, 44592}, {29498, 44220, 22144}, {56871, 2985, 52252}}, {1, 0}, 0, 0, 0}, +{{{30649, 39273}, {25739, 5209}, {7233, 24414}, {12457, 39233}}, {{42690, 51356, 34125}, {49143, 54026, 44592}, {29498, 44220, 22144}, {56871, 2985, 52252}}, {1, 0}, 1, 0, 0}, +{{{30649, 39273}, {25739, 5209}, {7233, 24414}, {12457, 39233}}, {{42690, 51356, 34125}, {49143, 54026, 44592}, {29498, 44220, 22144}, {56871, 2985, 52252}}, {1, 0}, 0, 1, 1}, +{{{30649, 39273}, {25739, 5209}, {7233, 24414}, {12457, 39233}}, {{42690, 51356, 34125}, {49143, 54026, 44592}, {29498, 44220, 22144}, {56871, 2985, 52252}}, {1, 0}, 1, 1, 1}, +{{{38312, 60669}, {24957, 60315}, {29896, 64344}, {29666, 14220}}, {{5748, 21477, 43965}, {28388, 57538, 56695}, {9899, 40548, 28310}, {3105, 5027, 40231}}, {3, 1}, 0, 0, 1}, +{{{38312, 60669}, {24957, 60315}, {29896, 64344}, {29666, 14220}}, {{5748, 21477, 43965}, {28388, 57538, 56695}, {9899, 40548, 28310}, {3105, 5027, 40231}}, {3, 1}, 1, 0, 1}, +{{{38312, 60669}, {24957, 60315}, {29896, 64344}, {29666, 14220}}, {{5748, 21477, 43965}, {28388, 57538, 56695}, {9899, 40548, 28310}, {3105, 5027, 40231}}, {3, 1}, 0, 1, 1}, +{{{38312, 60669}, {24957, 60315}, {29896, 64344}, {29666, 14220}}, {{5748, 21477, 43965}, {28388, 57538, 56695}, {9899, 40548, 28310}, {3105, 5027, 40231}}, {3, 1}, 1, 1, 1}, +{{{54391, 16949}, {42417, 62947}, {12524, 62053}, {334, 36818}}, {{12729, 32134, 5604}, {59961, 63624, 40894}, {33351, 32701, 23337}, {56130, 12317, 32472}}, {2, 3}, 0, 0, 1}, +{{{54391, 16949}, {42417, 62947}, {12524, 62053}, {334, 36818}}, {{12729, 32134, 5604}, {59961, 63624, 40894}, {33351, 32701, 23337}, {56130, 12317, 32472}}, {2, 3}, 1, 0, 1}, +{{{54391, 16949}, {42417, 62947}, {12524, 62053}, {334, 36818}}, {{12729, 32134, 5604}, {59961, 63624, 40894}, {33351, 32701, 23337}, {56130, 12317, 32472}}, {2, 3}, 0, 1, 1}, +{{{54391, 16949}, {42417, 62947}, {12524, 62053}, {334, 36818}}, {{12729, 32134, 5604}, {59961, 63624, 40894}, {33351, 32701, 23337}, {56130, 12317, 32472}}, {2, 3}, 1, 1, 1} From e8a6d60ee5c037ce2149b850ce9683f55a7f8e6c Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 24 May 2024 17:13:52 -0700 Subject: [PATCH 19/36] mps: update tests/runner.py to support testing through a socket --- components/mission_protection_system/tests/runner.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/components/mission_protection_system/tests/runner.py b/components/mission_protection_system/tests/runner.py index 87a540ac..a74a1f9b 100755 --- a/components/mission_protection_system/tests/runner.py +++ b/components/mission_protection_system/tests/runner.py @@ -17,12 +17,15 @@ # Run a test on a single input import pexpect +import pexpect.fdpexpect import sys import os +import socket import time RTS_BIN = os.environ.get("RTS_BIN") +RTS_SOCKET = os.environ.get("RTS_SOCKET") RTS_DEBUG = os.environ.get("RTS_DEBUG") is not None def try_expect(p,expected,timeout=1,retries=60): @@ -83,7 +86,12 @@ def run_script(p, cmds): return True def run(script, args): - p = pexpect.spawn(RTS_BIN) + if RTS_SOCKET is None: + p = pexpect.spawn(RTS_BIN) + else: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(RTS_SOCKET) + p = pexpect.fdpexpect.fdspawn(sock.fileno()) time.sleep(0.1) with open(script) as f: cmds = f.readlines() From d2122e8e18a7a58f3ab86639be6719d2fe1eb05d Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 13:45:40 -0700 Subject: [PATCH 20/36] mps: add reset (`R`) command --- .../mission_protection_system/src/posix_main.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/mission_protection_system/src/posix_main.c b/components/mission_protection_system/src/posix_main.c index 2b47e712..e50e4d43 100644 --- a/components/mission_protection_system/src/posix_main.c +++ b/components/mission_protection_system/src/posix_main.c @@ -83,6 +83,10 @@ void update_display() { } } +// A copy of the `argv` that was passed to main. This is used to implement the +// reset (`R`) command inside `read_rts_command`. +static char** main_argv = NULL; + int read_rts_command(struct rts_command *cmd) { int ok = 0; uint8_t device, on, div, ch, mode, sensor; @@ -166,8 +170,13 @@ int read_rts_command(struct rts_command *cmd) { sensor,ch,val)); #endif } else if (line[0] == 'Q') { - // printf(" read_rts_command QUIT\n"); + printf(" read_rts_command QUIT\n"); exit(0); + } else if (line[0] == 'R') { + printf(" read_rts_command RESET\n"); + // Re-exec the RTS binary with the same arguments and environment. This + // has the effect of resetting the entire RTS to its initial state. + execv("/proc/self/exe", main_argv); } else if (line[0] == 'D') { DEBUG_PRINTF((" read_rts_command UPDATE DISPLAY\n")); update_display(); @@ -363,6 +372,7 @@ uint32_t time_in_s() } int main(int argc, char **argv) { + main_argv = argv; struct rts_command *cmd = (struct rts_command *)malloc(sizeof(*cmd)); core_init(&core); From 13614dbe9b28999b8a09c4db0b95d2842d50269a Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 13:54:42 -0700 Subject: [PATCH 21/36] mps: support running tests through a unix socket --- .../tests/run_all.py | 42 +++++++++++++++---- .../mission_protection_system/tests/runner.py | 5 ++- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/components/mission_protection_system/tests/run_all.py b/components/mission_protection_system/tests/run_all.py index 69619a40..2a28591f 100755 --- a/components/mission_protection_system/tests/run_all.py +++ b/components/mission_protection_system/tests/run_all.py @@ -18,6 +18,7 @@ import subprocess import glob import os +import sys # Turn off screen clearing ANSI os.environ["RTS_NOCLEAR"] = "1" @@ -34,18 +35,41 @@ "scenarios/exceptional_4e", ] +fail_count = 0 for test in sorted(glob.glob("scenarios/*")): fn, ext = os.path.splitext(test) if ext == ".cases": continue - bin = "../src/rts.no_self_test" - if fn in NEEDS_SELF_TEST: - bin = "../src/rts.self_test" - os.environ["RTS_BIN"] = bin - print(f"{fn} ({bin})") - - if os.path.exists(fn + ".cases"): - subprocess.run(["./test.py", fn, fn + ".cases"],check=True) + if not os.environ.get("RTS_SOCKET"): + bin = "../src/rts.no_self_test" + if fn in NEEDS_SELF_TEST: + bin = "../src/rts.self_test" + os.environ["RTS_BIN"] = bin + os.environ.pop("RTS_SOCKET", None) + print(f"{fn} ({bin})") else: - subprocess.run(["./test.py", fn],check=True) + if fn in NEEDS_SELF_TEST: + # Most tests require an RTS binary built with SELF_TEST=Disabled, + # but a few need SELF_TEST=Enabled instead. Since we can't switch + # binaries when testing through a socket, we run only the + # SELF_TEST=Disabled part of the test suite. + print('skipping test %r: requires SELF_TEST=Enabled' % fn) + continue + # Remove RTS_BIN from the environment, if it's present. + os.environ.pop("RTS_BIN", None) + print(f"{fn} ({os.environ['RTS_SOCKET']})") + + + try: + if os.path.exists(fn + ".cases"): + subprocess.run(["./test.py", fn, fn + ".cases"],check=True) + else: + subprocess.run(["./test.py", fn],check=True) + except subprocess.CalledProcessError: + import traceback + traceback.print_exc() + fail_count += 1 + +if fail_count > 0: + sys.exit(1) diff --git a/components/mission_protection_system/tests/runner.py b/components/mission_protection_system/tests/runner.py index a74a1f9b..f3a06809 100755 --- a/components/mission_protection_system/tests/runner.py +++ b/components/mission_protection_system/tests/runner.py @@ -86,12 +86,15 @@ def run_script(p, cmds): return True def run(script, args): - if RTS_SOCKET is None: + if not RTS_SOCKET: p = pexpect.spawn(RTS_BIN) else: sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(RTS_SOCKET) p = pexpect.fdpexpect.fdspawn(sock.fileno()) + # Reset the RTS to its initial state. + p.sendline('R') + try_expect(p, 'RESET') time.sleep(0.1) with open(script) as f: cmds = f.readlines() From 04ff67aeec883781164d67ff700e63b9626fd1e0 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 14:33:02 -0700 Subject: [PATCH 22/36] vm_runner: use PathBuf instead of String in Config fields --- src/vm_runner/src/config.rs | 15 +++--- src/vm_runner/src/lib.rs | 98 +++++++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 43 deletions(-) diff --git a/src/vm_runner/src/config.rs b/src/vm_runner/src/config.rs index ee8369a2..3db1b135 100644 --- a/src/vm_runner/src/config.rs +++ b/src/vm_runner/src/config.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::path::PathBuf; use indexmap::IndexMap; use serde::{Serialize, Deserialize}; @@ -42,8 +43,8 @@ pub struct ShellProcess { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct VmProcess { - pub kernel: String, - pub initrd: Option, + pub kernel: PathBuf, + pub initrd: Option, #[serde(default)] pub append: String, @@ -99,7 +100,7 @@ fn const_u32() -> u32 { #[serde(deny_unknown_fields)] pub struct VmDisk { pub format: String, - pub path: String, + pub path: PathBuf, #[serde(default = "const_bool::")] pub read_only: bool, } @@ -121,7 +122,7 @@ pub struct PortForward { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Vm9P { - pub path: String, + pub path: PathBuf, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -139,13 +140,13 @@ pub enum VmSerial { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct PassthroughSerial { - pub device: String, + pub device: PathBuf, } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct UnixSerial { - pub path: String, + pub path: PathBuf, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -158,5 +159,5 @@ pub enum VmGpio { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct PassthroughGpio { - pub device: String, + pub device: PathBuf, } diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index 20c819c1..e264eb2b 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -101,8 +101,14 @@ fn build_commands(processes: &[config::Process]) -> Commands { cmds } -fn needs_escaping_for_qemu(arg: &str) -> bool { - arg.contains(&[',', '=', ':']) +fn needs_escaping_for_qemu(path: impl AsRef) -> bool { + let s = match path.as_ref().to_str() { + Some(x) => x, + // If the path isn't valid UTF-8, we can't examine it, so conservatively assume it might + // need escaping. + None => return true, + }; + s.contains(&[',', '=', ':']) } fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { @@ -114,42 +120,58 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { let mut vm_cmd = Command::new("qemu-system-aarch64"); + macro_rules! args { + ($l:literal $($rest:tt)*) => {{ + vm_cmd.arg($l); + args!($($rest)*); + }}; + (($e:expr) $($rest:tt)*) => {{ + vm_cmd.arg($e); + args!($($rest)*); + }}; + ($i:ident $($rest:tt)*) => {{ + vm_cmd.arg($i); + args!($($rest)*); + }}; + () => { () }; + } + // Basic machine configuration - vm_cmd.args(&["-M", "virt"]); - vm_cmd.args(&["-smp", "4"]); - vm_cmd.args(&["-m", &format!("{}", ram_mb)]); + args!("-M" "virt"); + args!("-smp" "4"); + args!("-m" (format!("{}", ram_mb))); // KVM if kvm { - vm_cmd.args(&["-cpu", "host"]); - vm_cmd.args(&["-enable-kvm"]); + args!("-cpu" "host"); + args!("-enable-kvm"); } else { - vm_cmd.args(&["-cpu", "cortex-a72"]); - vm_cmd.args(&["-machine", "virtualization=true"]); - vm_cmd.args(&["-machine", "virt,gic-version=3"]); + args!("-cpu" "cortex-a72"); + args!("-machine" "virtualization=true"); + args!("-machine" "virt,gic-version=3"); } // Non-configurable devices - vm_cmd.args(&["-device", "virtio-scsi-pci,id=scsi0"]); - vm_cmd.args(&["-object", "rng-random,filename=/dev/urandom,id=rng0"]); - vm_cmd.args(&["-device", "virtio-rng-pci,rng=rng0"]); - vm_cmd.args(&["-display", "none"]); + args!("-device" "virtio-scsi-pci,id=scsi0"); + args!("-object" "rng-random,filename=/dev/urandom,id=rng0"); + args!("-device" "virtio-rng-pci,rng=rng0"); + args!("-display" "none"); // Kernel and related flags - vm_cmd.args(&["-kernel", &kernel]); + args!("-kernel" kernel); if let Some(ref initrd) = initrd { - vm_cmd.args(&["-initrd", &initrd]); + args!("-initrd" initrd); } if append.len() > 0 { - vm_cmd.args(&["-append", &append]); + args!("-append" append); } // Serial ports // Set up a character device for stdio and use it for the QEMU monitor. - vm_cmd.args(&["-chardev", "stdio,mux=on,id=char_stdio,signal=off"]); - vm_cmd.args(&["-mon", "chardev=char_stdio,mode=readline"]); + args!("-chardev" "stdio,mux=on,id=char_stdio,signal=off"); + args!("-mon" "chardev=char_stdio,mode=readline"); /// Handle serial port configuration. This will add `-chardev` definitions to `cmd` if needed, /// and will return the `chardev` name for use with `-serial` or `-device`. @@ -162,15 +184,17 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { VmSerial::Passthrough(ref ps) => { assert!(!needs_escaping_for_qemu(&ps.device), "unsupported character in serial {} device: {:?}", name, ps.device); + let device = ps.device.to_str().unwrap(); vm_cmd.args(&["-chardev", - &format!("serial,id=char_{},path={}", name, ps.device)]); + &format!("serial,id=char_{},path={}", name, device)]); format!("char_{}", name) }, VmSerial::Unix(ref us) => { assert!(!needs_escaping_for_qemu(&us.path), "unsupported character in serial {} path: {:?}", name, us.path); + let path = us.path.to_str().unwrap(); vm_cmd.args(&["-chardev", - &format!("socket,id=char_{},path={},server=on,wait=off", name, us.path)]); + &format!("socket,id=char_{},path={},server=on,wait=off", name, path)]); format!("char_{}", name) }, } @@ -180,17 +204,17 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { const DEFAULT_SERIAL_NAME: &str = "ttyAMA0"; if let Some(s) = serial.get(DEFAULT_SERIAL_NAME) { let chardev = handle_serial(&mut vm_cmd, DEFAULT_SERIAL_NAME, s); - vm_cmd.args(&["-serial", &format!("chardev:{}", chardev)]); + args!("-serial" (format!("chardev:{}", chardev))); serial_range = 0 .. serial.len() - 1; } else { // Default behavior: connect `ttyAMA0` to stdio - vm_cmd.args(&["-serial", "chardev:char_stdio"]); + args!("-serial" "chardev:char_stdio"); serial_range = 0 .. serial.len(); } // A `virtio-serial-pci` device provides the QEMU-internal `virtio-serial-bus`, which later // `virtconsole` devices attach to. - vm_cmd.args(&["-device", "virtio-serial-pci"]); + args!("-device" "virtio-serial-pci"); // A single `virtio-serial-pci` provides 8 ports. const MAX_SERIAL_DEVICES: usize = 8; assert!(serial_range.end - serial_range.start <= MAX_SERIAL_DEVICES, @@ -201,7 +225,7 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { let s = serial.get(&name) .unwrap_or_else(|| panic!("non-contiguous serial port definitions: missing {}", name)); let chardev = handle_serial(&mut vm_cmd, &name, s); - vm_cmd.args(&["-device", &format!("virtconsole,chardev={}", chardev)]); + args!("-device" (format!("virtconsole,chardev={}", chardev))); } @@ -217,9 +241,10 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { "unsupported character in disk {} path: {:?}", name, d.path); assert!(["qcow2", "raw"].contains(&(&d.format as &str)), "unsupported format for disk {}: {:?}", name, d.format); + let path = d.path.to_str().unwrap(); let read_only = if d.read_only { "on" } else { "off" }; - vm_cmd.args(&["-drive", - &format!("if=virtio,format={},file={},read-only={}", d.format, d.path, read_only)]); + args!("-drive" + (format!("if=virtio,format={},file={},read-only={}", d.format, path, read_only))); } let config::VmNet { ref port_forward } = *net; @@ -228,24 +253,25 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { write!(netdev_str, ",hostfwd=tcp:127.0.0.1:{}-:{}", pf.outer_port, pf.inner_port) .unwrap(); } - vm_cmd.args(&["-device", "virtio-net-pci,netdev=net0"]); - vm_cmd.args(&["-netdev", &netdev_str]); + args!("-device" "virtio-net-pci,netdev=net0"); + args!("-netdev" netdev_str); for (name, fs) in fs_9p { assert!(!needs_escaping_for_qemu(name), "unsupported character in 9p name {:?}", name); assert!(!needs_escaping_for_qemu(&fs.path), "unsupported character in 9p {} path: {:?}", name, fs.path); - vm_cmd.args(&["-fsdev", - &format!("local,id=fs_9p__{},path={},security_model=mapped-xattr", name, fs.path)]); - vm_cmd.args(&["-device", - &format!("virtio-9p-pci,fsdev=fs_9p__{},mount_tag={}", name, name)]); + let path = fs.path.to_str().unwrap(); + args!("-fsdev" + (format!("local,id=fs_9p__{},path={},security_model=mapped-xattr", name, path))); + args!("-device" + (format!("virtio-9p-pci,fsdev=fs_9p__{},mount_tag={}", name, name))); } if gpio.len() > 0 { - vm_cmd.args(&["-object", - &format!("memory-backend-file,id=mem,size={}M,mem_path=/dev/shm,share=on", ram_mb)]); - vm_cmd.args(&["-numa", "node,memdev=mem"]); + args!("-object" + (format!("memory-backend-file,id=mem,size={}M,mem_path=/dev/shm,share=on", ram_mb))); + args!("-numa" "node,memdev=mem"); } for (name, g) in gpio { todo!(); From 877ba07cb7b55f1c91887bcff8b48a943525bfc3 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 14:34:03 -0700 Subject: [PATCH 23/36] vm_runner: add cwd field to ShellCommand --- src/vm_runner/src/config.rs | 3 +++ src/vm_runner/src/lib.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/vm_runner/src/config.rs b/src/vm_runner/src/config.rs index 3db1b135..610eb93c 100644 --- a/src/vm_runner/src/config.rs +++ b/src/vm_runner/src/config.rs @@ -34,6 +34,9 @@ pub enum Process { #[serde(deny_unknown_fields)] pub struct ShellProcess { pub command: String, + /// Directory to use as the current directory when running `command`. + #[serde(default)] + pub cwd: PathBuf, } /// Spawn a VM. diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index e264eb2b..1285dd0e 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -90,6 +90,7 @@ fn build_commands(processes: &[config::Process]) -> Commands { match *process { config::Process::Shell(ref shell) => { let mut cmd = Command::new("/bin/sh"); + cmd.current_dir(&shell.cwd); cmd.args(&["-c", &shell.command]); cmds.commands.push(cmd); }, From b217774b25d3e94a173b254a3a359930263acd20 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 14:52:31 -0700 Subject: [PATCH 24/36] vm_runner: interpret paths relative to the config file's parent dir --- src/vm_runner/src/config.rs | 123 ++++++++++++++++++++- src/vm_runner/src/lib.rs | 4 +- src/vm_runner/tests/hello_base_nested.toml | 10 +- src/vm_runner/tests/hello_base_single.toml | 8 +- src/vm_runner/tests/hello_guest.toml | 2 +- src/vm_runner/tests/mps_base_nested.toml | 12 +- src/vm_runner/tests/mps_base_single.toml | 10 +- src/vm_runner/tests/mps_guest.toml | 2 +- 8 files changed, 146 insertions(+), 25 deletions(-) diff --git a/src/vm_runner/src/config.rs b/src/vm_runner/src/config.rs index 610eb93c..6a152176 100644 --- a/src/vm_runner/src/config.rs +++ b/src/vm_runner/src/config.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use indexmap::IndexMap; +use log::trace; use serde::{Serialize, Deserialize}; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -34,7 +35,9 @@ pub enum Process { #[serde(deny_unknown_fields)] pub struct ShellProcess { pub command: String, - /// Directory to use as the current directory when running `command`. + /// Directory to use as the current directory when running `command`. By default, this is the + /// directory containing the config file, so any paths in `command` are interpreted relative to + /// the config directory. #[serde(default)] pub cwd: PathBuf, } @@ -164,3 +167,119 @@ pub enum VmGpio { pub struct PassthroughGpio { pub device: PathBuf, } + + +fn resolve_relative_path(path: &mut PathBuf, base: &Path) { + let new = base.join(&*path); + trace!("resolve_relative_path: {:?} -> {:?}", path, new); + *path = new; + //*path = base.join(&*path); +} + +impl Config { + /// Resolve any relative paths within `self` relative to `base`. + pub fn resolve_relative_paths(&mut self, base: &Path) { + let Config { mode: _, ref mut process } = *self; + for p in process { + p.resolve_relative_paths(base); + } + } +} + +impl Process { + pub fn resolve_relative_paths(&mut self, base: &Path) { + match *self { + Process::Shell(ref mut sp) => sp.resolve_relative_paths(base), + Process::Vm(ref mut vp) => vp.resolve_relative_paths(base), + } + } +} + +impl ShellProcess { + pub fn resolve_relative_paths(&mut self, base: &Path) { + let ShellProcess { command: _, ref mut cwd } = *self; + resolve_relative_path(cwd, base); + } +} + +impl VmProcess { + pub fn resolve_relative_paths(&mut self, base: &Path) { + let VmProcess { + ref mut kernel, ref mut initrd, append: _, + ram_mb: _, kvm: _, + ref mut disk, net: _, ref mut fs_9p, ref mut serial, ref mut gpio, + } = *self; + + resolve_relative_path(kernel, base); + if let Some(ref mut initrd) = *initrd { + resolve_relative_path(initrd, base); + } + + for x in disk.values_mut() { + x.resolve_relative_paths(base); + } + for x in fs_9p.values_mut() { + x.resolve_relative_paths(base); + } + for x in serial.values_mut() { + x.resolve_relative_paths(base); + } + for x in gpio.values_mut() { + x.resolve_relative_paths(base); + } + } +} + +impl VmDisk { + pub fn resolve_relative_paths(&mut self, base: &Path) { + let VmDisk { format: _, ref mut path, read_only: _ } = *self; + resolve_relative_path(path, base); + } +} + +impl Vm9P { + pub fn resolve_relative_paths(&mut self, base: &Path) { + let Vm9P { ref mut path } = *self; + resolve_relative_path(path, base); + } +} + +impl VmSerial { + pub fn resolve_relative_paths(&mut self, base: &Path) { + match *self { + VmSerial::Stdio => {}, + VmSerial::Passthrough(ref mut ps) => ps.resolve_relative_paths(base), + VmSerial::Unix(ref mut us) => us.resolve_relative_paths(base), + } + } +} + +impl PassthroughSerial { + pub fn resolve_relative_paths(&mut self, base: &Path) { + let PassthroughSerial { ref mut device } = *self; + resolve_relative_path(device, base); + } +} + +impl UnixSerial { + pub fn resolve_relative_paths(&mut self, base: &Path) { + let UnixSerial { ref mut path } = *self; + resolve_relative_path(path, base); + } +} + +impl VmGpio { + pub fn resolve_relative_paths(&mut self, base: &Path) { + match *self { + VmGpio::External => {}, + VmGpio::Passthrough(ref mut ps) => ps.resolve_relative_paths(base), + } + } +} + +impl PassthroughGpio { + pub fn resolve_relative_paths(&mut self, base: &Path) { + let PassthroughGpio { ref mut device } = *self; + resolve_relative_path(device, base); + } +} diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index 1285dd0e..f8aba2b7 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -393,8 +393,10 @@ pub fn run_exec(cfg: &Config) -> io::Result<()> { pub fn runner_main(config_path: impl AsRef) { + let config_path = config_path.as_ref(); let config_str = fs::read_to_string(config_path).unwrap(); - let cfg: Config = toml::from_str(&config_str).unwrap(); + let mut cfg: Config = toml::from_str(&config_str).unwrap(); + cfg.resolve_relative_paths(config_path.parent().unwrap()); trace!("parsed config = {:?}", cfg); diff --git a/src/vm_runner/tests/hello_base_nested.toml b/src/vm_runner/tests/hello_base_nested.toml index 86d3d02e..d55572af 100644 --- a/src/vm_runner/tests/hello_base_nested.toml +++ b/src/vm_runner/tests/hello_base_nested.toml @@ -3,19 +3,19 @@ mode = "exec" [[process]] type = "vm" kvm = false -kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" -initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +kernel = "../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../pkvm_setup/vms/debian-boot/initrd.img" append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdc' [process.disk.vda] format = "qcow2" -path = "../pkvm_setup/vms/disk_host.img" +path = "../../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "qcow2" -path = "../pkvm_setup/vms/disk_guest.img" +path = "../../pkvm_setup/vms/disk_guest.img" [process.disk.vdc] format = "raw" -path = "tests/hello_host.img" +path = "hello_host.img" read_only = true diff --git a/src/vm_runner/tests/hello_base_single.toml b/src/vm_runner/tests/hello_base_single.toml index a0aa67cb..95ca9a25 100644 --- a/src/vm_runner/tests/hello_base_single.toml +++ b/src/vm_runner/tests/hello_base_single.toml @@ -3,15 +3,15 @@ mode = "exec" [[process]] type = "vm" kvm = false -kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" -initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +kernel = "../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../pkvm_setup/vms/debian-boot/initrd.img" append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' [process.disk.vda] format = "qcow2" -path = "../pkvm_setup/vms/disk_host.img" +path = "../../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "raw" -path = "tests/hello_guest.img" +path = "hello_guest.img" read_only = true diff --git a/src/vm_runner/tests/hello_guest.toml b/src/vm_runner/tests/hello_guest.toml index 49e123a8..9f51b2cf 100644 --- a/src/vm_runner/tests/hello_guest.toml +++ b/src/vm_runner/tests/hello_guest.toml @@ -2,5 +2,5 @@ mode = "exec" [[process]] type = "shell" -command = "/opt/opensut/app/hello.sh" +command = "./hello.sh" diff --git a/src/vm_runner/tests/mps_base_nested.toml b/src/vm_runner/tests/mps_base_nested.toml index cd8db286..50dd67cf 100644 --- a/src/vm_runner/tests/mps_base_nested.toml +++ b/src/vm_runner/tests/mps_base_nested.toml @@ -3,23 +3,23 @@ mode = "exec" [[process]] type = "vm" kvm = false -kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" -initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +kernel = "../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../pkvm_setup/vms/debian-boot/initrd.img" append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdc' [process.disk.vda] format = "qcow2" -path = "../pkvm_setup/vms/disk_host.img" +path = "../../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "qcow2" -path = "../pkvm_setup/vms/disk_guest.img" +path = "../../pkvm_setup/vms/disk_guest.img" [process.disk.vdc] format = "raw" -path = "tests/mps_host.img" +path = "mps_host.img" read_only = true [process.serial.hvc0] mode = "unix" -path = "./serial.socket" +path = "../serial.socket" diff --git a/src/vm_runner/tests/mps_base_single.toml b/src/vm_runner/tests/mps_base_single.toml index 13b3e98b..207c41aa 100644 --- a/src/vm_runner/tests/mps_base_single.toml +++ b/src/vm_runner/tests/mps_base_single.toml @@ -3,19 +3,19 @@ mode = "exec" [[process]] type = "vm" kvm = false -kernel = "../pkvm_setup/vms/debian-boot/vmlinuz" -initrd = "../pkvm_setup/vms/debian-boot/initrd.img" +kernel = "../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../pkvm_setup/vms/debian-boot/initrd.img" append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' [process.disk.vda] format = "qcow2" -path = "../pkvm_setup/vms/disk_host.img" +path = "../../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "raw" -path = "tests/mps_guest.img" +path = "mps_guest.img" read_only = true [process.serial.hvc0] mode = "unix" -path = "./serial.socket" +path = "../serial.socket" diff --git a/src/vm_runner/tests/mps_guest.toml b/src/vm_runner/tests/mps_guest.toml index 674a31eb..7cfa38c6 100644 --- a/src/vm_runner/tests/mps_guest.toml +++ b/src/vm_runner/tests/mps_guest.toml @@ -2,5 +2,5 @@ mode = "exec" [[process]] type = "shell" -command = "/opt/opensut/app/mps.sh" +command = "./mps.sh" From fb8fe18bab49e01335f8bf9d153ea60baaf62a3d Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 14:58:55 -0700 Subject: [PATCH 25/36] vm_runner: mark gpio devices as not yet implemented --- src/vm_runner/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vm_runner/src/lib.rs b/src/vm_runner/src/lib.rs index f8aba2b7..edf5c5b3 100644 --- a/src/vm_runner/src/lib.rs +++ b/src/vm_runner/src/lib.rs @@ -274,8 +274,9 @@ fn build_vm_command(vm: &config::VmProcess, cmds: &mut Commands) { (format!("memory-backend-file,id=mem,size={}M,mem_path=/dev/shm,share=on", ram_mb))); args!("-numa" "node,memdev=mem"); } - for (name, g) in gpio { - todo!(); + for (_name, _g) in gpio { + // TODO: add vhost-device-gpio as an early_command, and add a -device flag to vm_cmd + todo!("gpio devices are not yet implemented"); } cmds.commands.push(vm_cmd); From 10c4e6706eef598e686e13405994f717debdb910 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 15:40:33 -0700 Subject: [PATCH 26/36] vm_runner: add README.md --- src/vm_runner/README.md | 112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/vm_runner/README.md diff --git a/src/vm_runner/README.md b/src/vm_runner/README.md new file mode 100644 index 00000000..e3b32c27 --- /dev/null +++ b/src/vm_runner/README.md @@ -0,0 +1,112 @@ +# OpenSUT VM Runner + +This is a tool for running QEMU VMs or other processes according to a config +file. The project has two binaries, corresponding to its two modes of +operation: + +* `opensut_vm_runner`: Takes a config file as an argument and runs the + processes described in that config. This is useful for running on the base + system to start up the host VM. +* `opensut_boot`: Reads a device path from the kernel command line, mounts that + device, and runs `opensut_vm_runner` on a config file found there. This is + designed to be run in the VM upon boot as a system service or `systemd.run` + command. In the future, this can be extended to check for a specific + signature before mounting the device. + +A typical boot process works like this: + +* The user runs `opensut_vm_runner` on the base system to start up the host VM. +* The host VM boots and runs `opensut_boot`. +* In the host, `opensut_boot` mounts the host application partition, reads the + config file, and starts up the guest VM. +* The guest VM boots and runs `opensut_boot`. +* In the guest, `opensut_boot` mounts the guest application partition, reads + the config file, and starts up some application process such as MPS. + + +## Setup + +First, run the setup steps in `../pkvm_setup/` to build the base disk images +for the host and guest VMs. + +There are two options for building the `vm_runner` binaries: + +1. Cross-compile for aarch64 from the local machine: + + ```sh + cargo build --release --target aarch64-unknown-linux-gnu + ``` + + This may require installing additional Rust target libraries. + +2. Compile inside a VM: + + ```sh + cargo run -- build_config.toml + ``` + + This will start up the development version of the host VM, install a Rust + toolchain (if needed), and compile `vm_runner`. This will usually be + slower than the cross-compiling option. + +Finally, install the new `vm_runner` binaries into the host and guest VMs: + +```sh +cargo run -- install_config.toml +cargo run -- install_config_guest.toml +``` + + +## Usage + +The `tests/` directory contains some example configurations. + +To run a trivial Hello World script: + +```sh +cargo run -- tests/hello_guest.toml +``` + +This should print "Hello, World!" + +The remaining tests require building Hello World application images first: + +```sh +bash tests/build_hello_image.sh +``` + +To run the Hello World script inside a VM: + +```sh +cargo run -- tests/hello_base_single.toml +``` + +This will start up a VM with an application image containing `hello_guest.toml` +and `hello.sh`. This will produce a large amount of output as Linux boots +within the VM, consisting of kernel messages, a group of "starting service" +messages, and a group of "stopped service" messages. Between the start group +and the stop group, output like the following should appear: + +``` + Starting kernel-command-li…ommand from Kernel Command Line... +[ 19.241859] squashfs: version 4.0 (2009/01/31) Phillip Lougher +[ 19.326691] opensut_boot[303]: Hello, World! +[ OK ] Finished kernel-command-li… Command from Kernel Command Line. +``` + +To run the Hello World script inside two nested VMs: + +```sh +cargo run -- tests/hello_base_nested.toml +``` + +This is similar to the previous example, but there will be two sets of Linux +startup and shutdown messages, one for the host and one for the guest. In the +middle should be output like this: + +``` +[ 68.312436] opensut_boot[301]: Starting kernel-command-li…ommand from Kernel Command Line... +[ 68.498854] opensut_boot[301]: [ 45.040450] squashfs: version 4.0 (2009/01/31) Phillip Lougher +[ 68.757693] opensut_boot[301]: [ 42.171542] opensut_boot[307]: Hello, World! +[ 68.814990] opensut_boot[301]: [ OK ] Finished kernel-command-li… Command from Kernel Command Line. +``` From 1c42ed928ec1bb783a55bad3784fb6a4ecde5d73 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 16:07:29 -0700 Subject: [PATCH 27/36] mps: report pass/fail/skip counts after running test suite --- components/mission_protection_system/tests/run_all.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/mission_protection_system/tests/run_all.py b/components/mission_protection_system/tests/run_all.py index 2a28591f..5a5efa6c 100755 --- a/components/mission_protection_system/tests/run_all.py +++ b/components/mission_protection_system/tests/run_all.py @@ -35,7 +35,9 @@ "scenarios/exceptional_4e", ] +pass_count = 0 fail_count = 0 +skip_count = 0 for test in sorted(glob.glob("scenarios/*")): fn, ext = os.path.splitext(test) if ext == ".cases": @@ -55,6 +57,7 @@ # binaries when testing through a socket, we run only the # SELF_TEST=Disabled part of the test suite. print('skipping test %r: requires SELF_TEST=Enabled' % fn) + skip_count += 1 continue # Remove RTS_BIN from the environment, if it's present. os.environ.pop("RTS_BIN", None) @@ -66,10 +69,12 @@ subprocess.run(["./test.py", fn, fn + ".cases"],check=True) else: subprocess.run(["./test.py", fn],check=True) + pass_count += 1 except subprocess.CalledProcessError: import traceback traceback.print_exc() fail_count += 1 +print('\n%d tests passed, %d failed, %d skipped' % (pass_count, fail_count, skip_count)) if fail_count > 0: sys.exit(1) From c2699235c5df906eb00495a85f87c8400b1e9f1d Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 May 2024 16:09:50 -0700 Subject: [PATCH 28/36] mps: update tests/README.md with instructions for testing with vm_runner --- .../mission_protection_system/tests/README.md | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/components/mission_protection_system/tests/README.md b/components/mission_protection_system/tests/README.md index 0bd491d1..36d71663 100644 --- a/components/mission_protection_system/tests/README.md +++ b/components/mission_protection_system/tests/README.md @@ -72,7 +72,82 @@ test, that is not quite equivalent, would be look for a UI state in which at least two sensor values differ: clearly this is quite a complicated regular expression. -## License + +# Running Tests under `vm_runner` + +This section describes how to run the test suite against an instance of MPS +that is running under a guest VM managed by the OpenSUT `vm_runner`. + +First, cross-compile MPS for aarch64 in the appropriate configuration: + +```sh +# In the mission_protection_system/src/ directory: +make clean +make \ + CC=aarch64-linux-gnu-g++ \ + CXX=aarch64-linux-gnu-g++ \ + SENSORS=NotSimulated \ + SELF_TEST=Disabled +``` + +This will produce an aarch64 `rts` binary. On Debian, the necessary aarch64 +toolchain can be installed from the `g++-aarch64-linux-gnu` package. + +Then, in the `vm_runner` directory, prepare host and guest application images +for running MPS: + +```sh +# In the vm_runner/ directory: +bash tests/build_mps_image.sh +``` + +This copies the aarch64 `rts` binary produced above into the images. + +Start the VMs and MPS by running: + +```sh +# In the vm_runner/ directory: +cargo run -- tests/hello_mps_nested.toml +``` + +This will start a host VM, a guest VM, and MPS inside the guest VM. It will +print various Linux boot messages, followed by a line like this: + +``` +[ 65.044425] opensut_boot[302]: [ 39.737218] opensut_boot[305]: Starting mps +``` + +At this point, MPS is running inside the VM. + +For manual testing, you can connect to MPS via `socat`: + +```sh +# In the vm_runner/ directory: +socat - unix:./serial.socket +``` + +This should display the usual MPS status screen and accept commands as normal. +Press ^C to disconnect (MPS will continue running). To restart MPS, enter the +`R` (reset) command. + +In this setup, `serial.socket` on the base system is connected to an emulated +UART in the host VM, which is forwarded to an emulated UART in the guest VM, +and MPS communicates over that emulated UART. See `vm_runner/tests/mps*.toml` +for details. + +The MPS test suite can now be run against `serial.socket`, which allows testing +MPS as it runs inside the VM. In the MPS `tests/` subdirectory, run the suite +with the appropriate environment variables set: + +```sh +# In the mission_protection_system/tests/ directory: +RTS_SOCKET=/path/to/vm_runner/serial.socket python3 run_all.py +``` + +All tests should pass as normal in this configuration. + + +# License Copyright 2021, 2022, 2023 Galois, Inc. From cc6ea9634c7b111346c92136d4acec087cf3a900 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 31 May 2024 09:24:38 -0700 Subject: [PATCH 29/36] vm_runner: rearrange test files --- src/vm_runner/.gitignore | 1 + src/vm_runner/tests/build_hello_img.sh | 15 --------------- src/vm_runner/tests/build_mps_img.sh | 16 ---------------- .../base_nested.toml} | 10 +++++----- .../base_single.toml} | 8 ++++---- src/vm_runner/tests/hello/build_img.sh | 15 +++++++++++++++ .../tests/{hello_guest.toml => hello/guest.toml} | 0 src/vm_runner/tests/{ => hello}/hello.sh | 0 .../tests/{hello_host.toml => hello/host.toml} | 2 +- .../base_nested.toml} | 12 ++++++------ .../base_single.toml} | 10 +++++----- src/vm_runner/tests/mps/build_img.sh | 16 ++++++++++++++++ .../tests/{mps_guest.toml => mps/guest.toml} | 0 .../tests/{mps_host.toml => mps/host.toml} | 2 +- src/vm_runner/tests/{ => mps}/mps.sh | 0 15 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 src/vm_runner/.gitignore delete mode 100644 src/vm_runner/tests/build_hello_img.sh delete mode 100644 src/vm_runner/tests/build_mps_img.sh rename src/vm_runner/tests/{hello_base_nested.toml => hello/base_nested.toml} (55%) rename src/vm_runner/tests/{hello_base_single.toml => hello/base_single.toml} (57%) create mode 100644 src/vm_runner/tests/hello/build_img.sh rename src/vm_runner/tests/{hello_guest.toml => hello/guest.toml} (100%) rename src/vm_runner/tests/{ => hello}/hello.sh (100%) rename src/vm_runner/tests/{hello_host.toml => hello/host.toml} (88%) rename src/vm_runner/tests/{mps_base_nested.toml => mps/base_nested.toml} (55%) rename src/vm_runner/tests/{mps_base_single.toml => mps/base_single.toml} (57%) create mode 100644 src/vm_runner/tests/mps/build_img.sh rename src/vm_runner/tests/{mps_guest.toml => mps/guest.toml} (100%) rename src/vm_runner/tests/{mps_host.toml => mps/host.toml} (90%) rename src/vm_runner/tests/{ => mps}/mps.sh (100%) diff --git a/src/vm_runner/.gitignore b/src/vm_runner/.gitignore new file mode 100644 index 00000000..a89285e5 --- /dev/null +++ b/src/vm_runner/.gitignore @@ -0,0 +1 @@ +*.img diff --git a/src/vm_runner/tests/build_hello_img.sh b/src/vm_runner/tests/build_hello_img.sh deleted file mode 100644 index c0a520e0..00000000 --- a/src/vm_runner/tests/build_hello_img.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -euo pipefail - -tests_dir="$(dirname "$0")" - -python3 "$tests_dir/../build_application_image.py" \ - -f "$tests_dir/hello_guest.toml=runner.toml" \ - -f "$tests_dir/hello.sh" \ - -o "$tests_dir/hello_guest.img" - -python3 "$tests_dir/../build_application_image.py" \ - -f "$tests_dir/hello_host.toml=runner.toml" \ - -f "$tests_dir/hello_guest.img" \ - -o "$tests_dir/hello_host.img" - diff --git a/src/vm_runner/tests/build_mps_img.sh b/src/vm_runner/tests/build_mps_img.sh deleted file mode 100644 index 0f6570d9..00000000 --- a/src/vm_runner/tests/build_mps_img.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -set -euo pipefail - -tests_dir="$(dirname "$0")" - -python3 "$tests_dir/../build_application_image.py" \ - -f "$tests_dir/mps_guest.toml=runner.toml" \ - -f "$tests_dir/../../../components/mission_protection_system/src/rts=mps" \ - -f "$tests_dir/mps.sh" \ - -o "$tests_dir/mps_guest.img" - -python3 "$tests_dir/../build_application_image.py" \ - -f "$tests_dir/mps_host.toml=runner.toml" \ - -f "$tests_dir/mps_guest.img" \ - -o "$tests_dir/mps_host.img" - diff --git a/src/vm_runner/tests/hello_base_nested.toml b/src/vm_runner/tests/hello/base_nested.toml similarity index 55% rename from src/vm_runner/tests/hello_base_nested.toml rename to src/vm_runner/tests/hello/base_nested.toml index d55572af..3580d1be 100644 --- a/src/vm_runner/tests/hello_base_nested.toml +++ b/src/vm_runner/tests/hello/base_nested.toml @@ -3,19 +3,19 @@ mode = "exec" [[process]] type = "vm" kvm = false -kernel = "../../pkvm_setup/vms/debian-boot/vmlinuz" -initrd = "../../pkvm_setup/vms/debian-boot/initrd.img" +kernel = "../../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../../pkvm_setup/vms/debian-boot/initrd.img" append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdc' [process.disk.vda] format = "qcow2" -path = "../../pkvm_setup/vms/disk_host.img" +path = "../../../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "qcow2" -path = "../../pkvm_setup/vms/disk_guest.img" +path = "../../../pkvm_setup/vms/disk_guest.img" [process.disk.vdc] format = "raw" -path = "hello_host.img" +path = "host.img" read_only = true diff --git a/src/vm_runner/tests/hello_base_single.toml b/src/vm_runner/tests/hello/base_single.toml similarity index 57% rename from src/vm_runner/tests/hello_base_single.toml rename to src/vm_runner/tests/hello/base_single.toml index 95ca9a25..47e68965 100644 --- a/src/vm_runner/tests/hello_base_single.toml +++ b/src/vm_runner/tests/hello/base_single.toml @@ -3,15 +3,15 @@ mode = "exec" [[process]] type = "vm" kvm = false -kernel = "../../pkvm_setup/vms/debian-boot/vmlinuz" -initrd = "../../pkvm_setup/vms/debian-boot/initrd.img" +kernel = "../../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../../pkvm_setup/vms/debian-boot/initrd.img" append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' [process.disk.vda] format = "qcow2" -path = "../../pkvm_setup/vms/disk_host.img" +path = "../../../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "raw" -path = "hello_guest.img" +path = "guest.img" read_only = true diff --git a/src/vm_runner/tests/hello/build_img.sh b/src/vm_runner/tests/hello/build_img.sh new file mode 100644 index 00000000..2fecde1a --- /dev/null +++ b/src/vm_runner/tests/hello/build_img.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euo pipefail + +hello_dir="$(dirname "$0")" + +python3 "$hello_dir/../../build_application_image.py" \ + -f "$hello_dir/guest.toml=runner.toml" \ + -f "$hello_dir/hello.sh" \ + -o "$hello_dir/guest.img" + +python3 "$hello_dir/../../build_application_image.py" \ + -f "$hello_dir/host.toml=runner.toml" \ + -f "$hello_dir/guest.img" \ + -o "$hello_dir/host.img" + diff --git a/src/vm_runner/tests/hello_guest.toml b/src/vm_runner/tests/hello/guest.toml similarity index 100% rename from src/vm_runner/tests/hello_guest.toml rename to src/vm_runner/tests/hello/guest.toml diff --git a/src/vm_runner/tests/hello.sh b/src/vm_runner/tests/hello/hello.sh similarity index 100% rename from src/vm_runner/tests/hello.sh rename to src/vm_runner/tests/hello/hello.sh diff --git a/src/vm_runner/tests/hello_host.toml b/src/vm_runner/tests/hello/host.toml similarity index 88% rename from src/vm_runner/tests/hello_host.toml rename to src/vm_runner/tests/hello/host.toml index 0df8572b..1f443f7e 100644 --- a/src/vm_runner/tests/hello_host.toml +++ b/src/vm_runner/tests/hello/host.toml @@ -13,5 +13,5 @@ path = "/dev/vdb" [process.disk.vdb] format = "raw" -path = "/opt/opensut/app/hello_guest.img" +path = "/opt/opensut/app/guest.img" read_only = true diff --git a/src/vm_runner/tests/mps_base_nested.toml b/src/vm_runner/tests/mps/base_nested.toml similarity index 55% rename from src/vm_runner/tests/mps_base_nested.toml rename to src/vm_runner/tests/mps/base_nested.toml index 50dd67cf..84ed17f3 100644 --- a/src/vm_runner/tests/mps_base_nested.toml +++ b/src/vm_runner/tests/mps/base_nested.toml @@ -3,23 +3,23 @@ mode = "exec" [[process]] type = "vm" kvm = false -kernel = "../../pkvm_setup/vms/debian-boot/vmlinuz" -initrd = "../../pkvm_setup/vms/debian-boot/initrd.img" +kernel = "../../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../../pkvm_setup/vms/debian-boot/initrd.img" append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdc' [process.disk.vda] format = "qcow2" -path = "../../pkvm_setup/vms/disk_host.img" +path = "../../../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "qcow2" -path = "../../pkvm_setup/vms/disk_guest.img" +path = "../../../pkvm_setup/vms/disk_guest.img" [process.disk.vdc] format = "raw" -path = "mps_host.img" +path = "host.img" read_only = true [process.serial.hvc0] mode = "unix" -path = "../serial.socket" +path = "../../serial.socket" diff --git a/src/vm_runner/tests/mps_base_single.toml b/src/vm_runner/tests/mps/base_single.toml similarity index 57% rename from src/vm_runner/tests/mps_base_single.toml rename to src/vm_runner/tests/mps/base_single.toml index 207c41aa..acaae0c8 100644 --- a/src/vm_runner/tests/mps_base_single.toml +++ b/src/vm_runner/tests/mps/base_single.toml @@ -3,19 +3,19 @@ mode = "exec" [[process]] type = "vm" kvm = false -kernel = "../../pkvm_setup/vms/debian-boot/vmlinuz" -initrd = "../../pkvm_setup/vms/debian-boot/initrd.img" +kernel = "../../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../../pkvm_setup/vms/debian-boot/initrd.img" append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' [process.disk.vda] format = "qcow2" -path = "../../pkvm_setup/vms/disk_host.img" +path = "../../../pkvm_setup/vms/disk_host.img" [process.disk.vdb] format = "raw" -path = "mps_guest.img" +path = "guest.img" read_only = true [process.serial.hvc0] mode = "unix" -path = "../serial.socket" +path = "../../serial.socket" diff --git a/src/vm_runner/tests/mps/build_img.sh b/src/vm_runner/tests/mps/build_img.sh new file mode 100644 index 00000000..dde1d488 --- /dev/null +++ b/src/vm_runner/tests/mps/build_img.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -euo pipefail + +mps_dir="$(dirname "$0")" + +python3 "$mps_dir/../../build_application_image.py" \ + -f "$mps_dir/guest.toml=runner.toml" \ + -f "$mps_dir/../../../../components/mission_protection_system/src/rts=mps" \ + -f "$mps_dir/mps.sh" \ + -o "$mps_dir/guest.img" + +python3 "$mps_dir/../../build_application_image.py" \ + -f "$mps_dir/host.toml=runner.toml" \ + -f "$mps_dir/guest.img" \ + -o "$mps_dir/host.img" + diff --git a/src/vm_runner/tests/mps_guest.toml b/src/vm_runner/tests/mps/guest.toml similarity index 100% rename from src/vm_runner/tests/mps_guest.toml rename to src/vm_runner/tests/mps/guest.toml diff --git a/src/vm_runner/tests/mps_host.toml b/src/vm_runner/tests/mps/host.toml similarity index 90% rename from src/vm_runner/tests/mps_host.toml rename to src/vm_runner/tests/mps/host.toml index 27de9732..5431f3d8 100644 --- a/src/vm_runner/tests/mps_host.toml +++ b/src/vm_runner/tests/mps/host.toml @@ -13,7 +13,7 @@ path = "/dev/vdb" [process.disk.vdb] format = "raw" -path = "/opt/opensut/app/mps_guest.img" +path = "/opt/opensut/app/guest.img" read_only = true [process.serial.hvc0] diff --git a/src/vm_runner/tests/mps.sh b/src/vm_runner/tests/mps/mps.sh similarity index 100% rename from src/vm_runner/tests/mps.sh rename to src/vm_runner/tests/mps/mps.sh From 1dd5c4e1fc7c57e843ded916536f0277691acdff Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 31 May 2024 10:31:35 -0700 Subject: [PATCH 30/36] vm_runner: add configs for running the MPS test suite --- .../tests/mps_tests/base_nested.toml | 21 +++++++++++++++++++ .../tests/mps_tests/base_single.toml | 17 +++++++++++++++ src/vm_runner/tests/mps_tests/build_img.sh | 18 ++++++++++++++++ src/vm_runner/tests/mps_tests/guest.toml | 6 ++++++ src/vm_runner/tests/mps_tests/host.toml | 17 +++++++++++++++ src/vm_runner/tests/mps_tests/mps_tests.sh | 8 +++++++ 6 files changed, 87 insertions(+) create mode 100644 src/vm_runner/tests/mps_tests/base_nested.toml create mode 100644 src/vm_runner/tests/mps_tests/base_single.toml create mode 100644 src/vm_runner/tests/mps_tests/build_img.sh create mode 100644 src/vm_runner/tests/mps_tests/guest.toml create mode 100644 src/vm_runner/tests/mps_tests/host.toml create mode 100755 src/vm_runner/tests/mps_tests/mps_tests.sh diff --git a/src/vm_runner/tests/mps_tests/base_nested.toml b/src/vm_runner/tests/mps_tests/base_nested.toml new file mode 100644 index 00000000..3580d1be --- /dev/null +++ b/src/vm_runner/tests/mps_tests/base_nested.toml @@ -0,0 +1,21 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdc' + +[process.disk.vda] +format = "qcow2" +path = "../../../pkvm_setup/vms/disk_host.img" + +[process.disk.vdb] +format = "qcow2" +path = "../../../pkvm_setup/vms/disk_guest.img" + +[process.disk.vdc] +format = "raw" +path = "host.img" +read_only = true diff --git a/src/vm_runner/tests/mps_tests/base_single.toml b/src/vm_runner/tests/mps_tests/base_single.toml new file mode 100644 index 00000000..47e68965 --- /dev/null +++ b/src/vm_runner/tests/mps_tests/base_single.toml @@ -0,0 +1,17 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = false +kernel = "../../../pkvm_setup/vms/debian-boot/vmlinuz" +initrd = "../../../pkvm_setup/vms/debian-boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' + +[process.disk.vda] +format = "qcow2" +path = "../../../pkvm_setup/vms/disk_host.img" + +[process.disk.vdb] +format = "raw" +path = "guest.img" +read_only = true diff --git a/src/vm_runner/tests/mps_tests/build_img.sh b/src/vm_runner/tests/mps_tests/build_img.sh new file mode 100644 index 00000000..859a3c97 --- /dev/null +++ b/src/vm_runner/tests/mps_tests/build_img.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -euo pipefail + +mps_dir="$(dirname "$0")" + +python3 "$mps_dir/../../build_application_image.py" \ + -f "$mps_dir/guest.toml=runner.toml" \ + -f "$mps_dir/../../../../components/mission_protection_system/src/rts.self_test.aarch64=src/rts.self_test" \ + -f "$mps_dir/../../../../components/mission_protection_system/src/rts.no_self_test.aarch64=src/rts.no_self_test" \ + -d "$mps_dir/../../../../components/mission_protection_system/tests" \ + -f "$mps_dir/mps_tests.sh" \ + -o "$mps_dir/guest.img" + +python3 "$mps_dir/../../build_application_image.py" \ + -f "$mps_dir/host.toml=runner.toml" \ + -f "$mps_dir/guest.img" \ + -o "$mps_dir/host.img" + diff --git a/src/vm_runner/tests/mps_tests/guest.toml b/src/vm_runner/tests/mps_tests/guest.toml new file mode 100644 index 00000000..ecc2a851 --- /dev/null +++ b/src/vm_runner/tests/mps_tests/guest.toml @@ -0,0 +1,6 @@ +mode = "exec" + +[[process]] +type = "shell" +command = "./mps_tests.sh" + diff --git a/src/vm_runner/tests/mps_tests/host.toml b/src/vm_runner/tests/mps_tests/host.toml new file mode 100644 index 00000000..1f443f7e --- /dev/null +++ b/src/vm_runner/tests/mps_tests/host.toml @@ -0,0 +1,17 @@ +mode = "exec" + +[[process]] +type = "vm" +kvm = true +kernel = "/boot/vmlinuz" +initrd = "/boot/initrd.img" +append = 'earlycon root=/dev/vda2 systemd.run=/opt/opensut/bin/opensut_boot opensut.app_device=/dev/vdb' + +[process.disk.vda] +format = "raw" +path = "/dev/vdb" + +[process.disk.vdb] +format = "raw" +path = "/opt/opensut/app/guest.img" +read_only = true diff --git a/src/vm_runner/tests/mps_tests/mps_tests.sh b/src/vm_runner/tests/mps_tests/mps_tests.sh new file mode 100755 index 00000000..bb40cfc0 --- /dev/null +++ b/src/vm_runner/tests/mps_tests/mps_tests.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -euo pipefail + +apt install -y python3-pexpect + +echo "Starting test suite" +cd tests +RTS_DEBUG=1 python3 run_all.py From 0fa8304ba22b0053209cdd846afdc6920f6c326e Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 31 May 2024 15:53:48 -0700 Subject: [PATCH 31/36] mps: test runner: adjust delays when waiting for replies Previously, while `runner.py` was waiting for a response from the RTS, it would send an update-display (`D`) command every second. In the nested-VM setup, updating the display is fairly expensive due to the I/O involved. If the MPS got behind for any reason, in order to catch up, it would need to process not only the pending command but all the update-display commands that were sent while the test runner was waiting. Sending an update-display command every second thus severely exacerbated the slow performance of the nested-VM setup. This commit changes `runner.py` to send the update-display command every 10 seconds instead of every second. The test suite now behaves more reliably in the nested-VM setup. --- components/mission_protection_system/tests/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/mission_protection_system/tests/runner.py b/components/mission_protection_system/tests/runner.py index f3a06809..d7021127 100755 --- a/components/mission_protection_system/tests/runner.py +++ b/components/mission_protection_system/tests/runner.py @@ -28,7 +28,7 @@ RTS_SOCKET = os.environ.get("RTS_SOCKET") RTS_DEBUG = os.environ.get("RTS_DEBUG") is not None -def try_expect(p,expected,timeout=1,retries=60): +def try_expect(p,expected,timeout=10,retries=10): expected = expected.strip() if RTS_DEBUG: print(f"CHECKING: {expected}") From a2f7dacc17862965dfaeedd921687457390b44ed Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 31 May 2024 16:03:09 -0700 Subject: [PATCH 32/36] vm_runner: minor updates to mps config files --- src/vm_runner/tests/mps/build_img.sh | 2 +- src/vm_runner/tests/mps/mps.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vm_runner/tests/mps/build_img.sh b/src/vm_runner/tests/mps/build_img.sh index dde1d488..b39a9629 100644 --- a/src/vm_runner/tests/mps/build_img.sh +++ b/src/vm_runner/tests/mps/build_img.sh @@ -5,7 +5,7 @@ mps_dir="$(dirname "$0")" python3 "$mps_dir/../../build_application_image.py" \ -f "$mps_dir/guest.toml=runner.toml" \ - -f "$mps_dir/../../../../components/mission_protection_system/src/rts=mps" \ + -f "$mps_dir/../../../../components/mission_protection_system/src/rts.no_self_test.aarch64=mps" \ -f "$mps_dir/mps.sh" \ -o "$mps_dir/guest.img" diff --git a/src/vm_runner/tests/mps/mps.sh b/src/vm_runner/tests/mps/mps.sh index 57cb053c..e7b1320f 100755 --- a/src/vm_runner/tests/mps/mps.sh +++ b/src/vm_runner/tests/mps/mps.sh @@ -2,6 +2,7 @@ set -euo pipefail mps_bin="$(dirname "$0")/mps" +sha256sum "$mps_bin" echo "Starting mps" setsid -c "$mps_bin" /dev/hvc0 2>/dev/hvc0 echo "mps exited with code $?" From f9fc26c90edceda8bb53ab45d9a41627ead470ac Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 31 May 2024 16:43:43 -0700 Subject: [PATCH 33/36] mps: add build_aarch64.sh script --- components/mission_protection_system/build_aarch64.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100755 components/mission_protection_system/build_aarch64.sh diff --git a/components/mission_protection_system/build_aarch64.sh b/components/mission_protection_system/build_aarch64.sh new file mode 100755 index 00000000..49c0325c --- /dev/null +++ b/components/mission_protection_system/build_aarch64.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -euo pipefail + +cd "$(dirname "$0")/src" +make clean +make CC=aarch64-linux-gnu-g++ CXX=aarch64-linux-gnu-g++ SENSORS=NotSimulated SELF_TEST=Disabled +cp -v rts rts.no_self_test.aarch64 +make clean +make CC=aarch64-linux-gnu-g++ CXX=aarch64-linux-gnu-g++ SENSORS=NotSimulated SELF_TEST=Enabled +cp -v rts rts.self_test.aarch64 From f6ecfb090ea63aa35487fb36a18f3e9855c5a007 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 31 May 2024 16:44:01 -0700 Subject: [PATCH 34/36] mps: update instructions for running tests under pKVM --- .../mission_protection_system/tests/README.md | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/components/mission_protection_system/tests/README.md b/components/mission_protection_system/tests/README.md index 36d71663..dc79ebd9 100644 --- a/components/mission_protection_system/tests/README.md +++ b/components/mission_protection_system/tests/README.md @@ -81,24 +81,20 @@ that is running under a guest VM managed by the OpenSUT `vm_runner`. First, cross-compile MPS for aarch64 in the appropriate configuration: ```sh -# In the mission_protection_system/src/ directory: -make clean -make \ - CC=aarch64-linux-gnu-g++ \ - CXX=aarch64-linux-gnu-g++ \ - SENSORS=NotSimulated \ - SELF_TEST=Disabled +# In the mission_protection_system/ directory: +./build_aarch64.sh ``` -This will produce an aarch64 `rts` binary. On Debian, the necessary aarch64 -toolchain can be installed from the `g++-aarch64-linux-gnu` package. +This will produce an aarch64 `rts.no_self_test.aarch64` binary. On Debian, the +necessary aarch64 toolchain can be installed from the `g++-aarch64-linux-gnu` +package. Then, in the `vm_runner` directory, prepare host and guest application images for running MPS: ```sh # In the vm_runner/ directory: -bash tests/build_mps_image.sh +bash tests/mps/build_image.sh ``` This copies the aarch64 `rts` binary produced above into the images. @@ -107,7 +103,7 @@ Start the VMs and MPS by running: ```sh # In the vm_runner/ directory: -cargo run -- tests/hello_mps_nested.toml +cargo run -- tests/mps/base_nested.toml ``` This will start a host VM, a guest VM, and MPS inside the guest VM. It will @@ -127,8 +123,8 @@ socat - unix:./serial.socket ``` This should display the usual MPS status screen and accept commands as normal. -Press ^C to disconnect (MPS will continue running). To restart MPS, enter the -`R` (reset) command. +Press ^C to disconnect (MPS will continue running). To restart MPS and reset +it to its initial state, enter the `R` (reset) command. In this setup, `serial.socket` on the base system is connected to an emulated UART in the host VM, which is forwarded to an emulated UART in the guest VM, From 70f610cfb6e321cd1ad55280065cdbb7ab08136e Mon Sep 17 00:00:00 2001 From: Michal Podhradsky Date: Wed, 5 Jun 2024 13:57:27 -0700 Subject: [PATCH 35/36] Run VM scripts in the CI (#60) Builds the VM runner in the CI, some small CI improvements --------- Co-authored-by: spernsteiner --- .cargo/config.toml | 3 ++ .github/workflows/main.yml | 68 +++++++++++++++++++++++++----------- .github/workflows/proofs.yml | 4 +-- 3 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..6b80765e --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" + diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c9c84b4..3472cd68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,24 +12,8 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - mps-build: - runs-on: ubuntu-latest - steps: - - name: Checkout repository and submodules - uses: actions/checkout@v4 - - name: Build MPS - uses: addnab/docker-run-action@v3 - with: - image: galoisinc/hardens:latest - options: -v ${{ github.workspace }}:/HARDENS - run: | - cd components/mission_protection_system/src - SENSORS=NotSimulated SELF_TEST=Enabled make rts - make clean - SENSORS=NotSimulated SELF_TEST=Enabled make rts_bottom - - mps-test: - runs-on: ubuntu-latest + mps-build: + runs-on: ubuntu-22.04 steps: - name: Checkout repository and submodules uses: actions/checkout@v4 @@ -40,12 +24,54 @@ jobs: options: -v ${{ github.workspace }}:/HARDENS run: | cd components/mission_protection_system/src + SENSORS=NotSimulated SELF_TEST=Enabled make rts_bottom make clean SENSORS=NotSimulated SELF_TEST=Disabled make rts mv rts rts.no_self_test make clean SENSORS=NotSimulated SELF_TEST=Enabled make rts mv rts rts.self_test - cd ../tests - pip3 install -r requirements.txt - RTS_DEBUG=1 QUICK=1 python3 ./run_all.py + - name: Upload MPS binaries + uses: actions/upload-artifact@v4 + with: + name: mps-binaries + path: components/mission_protection_system/src/rts.* + + mps-test: + runs-on: ubuntu-22.04 + needs: mps-build + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v4 + - name: Download MPS binaries + uses: actions/download-artifact@v4 + with: + name: mps-binaries + - name: Display structure of downloaded files + run: | + chmod +x rts.* + mv rts.* components/mission_protection_system/src/. + - name: Test MPS + uses: addnab/docker-run-action@v3 + with: + image: galoisinc/hardens:latest + options: -v ${{ github.workspace }}:/HARDENS + run: | + cd components/mission_protection_system/tests + pip3 install -r requirements.txt + RTS_DEBUG=1 QUICK=1 python3 ./run_all.py + + vmrunner: + runs-on: ubuntu-22.04 + steps: + - name: Install aarch64 toolchain + run: sudo apt-get install -y gcc-aarch64-linux-gnu + - uses: hecrj/setup-rust-action@v2 + with: + rust-version: 1.74 + targets: aarch64-unknown-linux-gnu + - uses: actions/checkout@master + - name: Build VM runner + run: | + cd src/vm_runner + cargo build --release --target aarch64-unknown-linux-gnu diff --git a/.github/workflows/proofs.yml b/.github/workflows/proofs.yml index 3eb721fa..f4306b68 100644 --- a/.github/workflows/proofs.yml +++ b/.github/workflows/proofs.yml @@ -14,7 +14,7 @@ on: jobs: mps-verify-cn: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout repository and submodules uses: actions/checkout@v4 @@ -32,7 +32,7 @@ jobs: make -f cn.mk proofs mps-verify-frama-c: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout repository and submodules uses: actions/checkout@v4 From f5474f1d9d5698ebea632dec16cd323515645e28 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 7 Jun 2024 11:38:51 -0700 Subject: [PATCH 36/36] vm_runner: update comments in install_helper.sh --- src/vm_runner/install_helper.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vm_runner/install_helper.sh b/src/vm_runner/install_helper.sh index 37f7ae10..eb27c70c 100644 --- a/src/vm_runner/install_helper.sh +++ b/src/vm_runner/install_helper.sh @@ -1,7 +1,7 @@ #!/bin/bash set -euo pipefail -# Script for building the `vm_runner` project inside a VM. +# Script for installing `vm_runner` binaries into the current VM image. if [[ "$(id -u)" -eq "0" ]]; then # Drop privileges for the rest of the script. @@ -21,5 +21,3 @@ sudo cp -v \ vm_runner/target/"$target"/release/opensut_vm_runner \ vm_runner/target/"$target"/release/opensut_boot \ /opt/opensut/bin - -# TODO: install systemd unit and configure to run on startup