diff options
author | Alyssa Ross <hi@alyssa.is> | 2023-06-18 13:14:14 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2023-06-24 08:12:25 +0000 |
commit | a72481208b75d40b350b330b91a2f4d881caa2c5 (patch) | |
tree | a7cb656ac734471bf0d10d7ba102dc1dc089d55e | |
parent | 35c8269325c3d03c0592a25abb9a469bd41697e7 (diff) | |
download | spectrum-a72481208b75d40b350b330b91a2f4d881caa2c5.tar spectrum-a72481208b75d40b350b330b91a2f4d881caa2c5.tar.gz spectrum-a72481208b75d40b350b330b91a2f4d881caa2c5.tar.bz2 spectrum-a72481208b75d40b350b330b91a2f4d881caa2c5.tar.lz spectrum-a72481208b75d40b350b330b91a2f4d881caa2c5.tar.xz spectrum-a72481208b75d40b350b330b91a2f4d881caa2c5.tar.zst spectrum-a72481208b75d40b350b330b91a2f4d881caa2c5.zip |
host/start-vm: don't poll for readiness
cloud-hypervisor supports specifying the API socket by file descriptor. By using that, we can notify readiness between creating the socket and exec-ing cloud-hypervisor. Signed-off-by: Alyssa Ross <hi@alyssa.is>
-rw-r--r-- | host/start-vm/lib.rs | 35 | ||||
-rw-r--r-- | host/start-vm/meson.build | 2 | ||||
-rw-r--r-- | host/start-vm/s6.rs | 15 | ||||
-rw-r--r-- | host/start-vm/start-vm.rs | 22 | ||||
-rw-r--r-- | host/start-vm/tests/vm_command-basic.rs | 9 | ||||
-rw-r--r-- | host/start-vm/tests/vm_command-multiple-disks.rs | 2 | ||||
-rw-r--r-- | host/start-vm/tests/vm_command-shared-dir.rs | 2 | ||||
-rw-r--r-- | host/start-vm/unix.c | 13 | ||||
-rw-r--r-- | host/start-vm/unix.rs | 9 |
9 files changed, 88 insertions, 21 deletions
diff --git a/host/start-vm/lib.rs b/host/start-vm/lib.rs index 917f584..c4afc4a 100644 --- a/host/start-vm/lib.rs +++ b/host/start-vm/lib.rs @@ -1,18 +1,24 @@ // SPDX-License-Identifier: EUPL-1.2+ -// SPDX-FileCopyrightText: 2022 Alyssa Ross <hi@alyssa.is> +// SPDX-FileCopyrightText: 2022-2023 Alyssa Ross <hi@alyssa.is> mod ch; mod net; +mod s6; +mod unix; use std::borrow::Cow; use std::env::args_os; use std::ffi::{CString, OsStr, OsString}; use std::io::{self, ErrorKind}; +use std::os::unix::net::UnixListener; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use std::process::Command; use net::{format_mac, net_setup, NetConfig}; +use unix::clear_cloexec; + +pub use s6::notify_readiness; pub fn prog_name() -> String { args_os() @@ -25,7 +31,24 @@ pub fn prog_name() -> String { .into_owned() } -pub fn vm_command(dir: PathBuf, config_root: &Path) -> Result<Command, String> { +pub fn create_api_socket() -> Result<UnixListener, String> { + let api_socket = UnixListener::bind("env/cloud-hypervisor.sock") + .map_err(|e| format!("creating API socket: {e}"))?; + + // Safe because we own api_socket. + if unsafe { clear_cloexec(api_socket.as_fd()) } == -1 { + let errno = io::Error::last_os_error(); + return Err(format!("clearing CLOEXEC on API socket fd: {}", errno)); + } + + Ok(api_socket) +} + +pub fn vm_command( + dir: PathBuf, + config_root: &Path, + api_socket_fd: RawFd, +) -> Result<Command, String> { let dir = dir.into_os_string().into_vec(); let dir = PathBuf::from(OsString::from_vec(dir)); @@ -39,10 +62,8 @@ pub fn vm_command(dir: PathBuf, config_root: &Path) -> Result<Command, String> { let config_dir = config_root.join(vm_name); - let mut command = Command::new("s6-notifyoncheck"); - command.args(["-dc", "test -S env/cloud-hypervisor.sock"]); - command.arg("cloud-hypervisor"); - command.args(["--api-socket", "env/cloud-hypervisor.sock"]); + let mut command = Command::new("cloud-hypervisor"); + command.args(["--api-socket", &format!("fd={api_socket_fd}")]); command.args(["--cmdline", "console=ttyS0 root=PARTLABEL=root"]); command.args(["--memory", "size=128M,shared=on"]); command.args(["--console", "pty"]); @@ -145,7 +166,7 @@ mod tests { #[test] fn test_vm_name_comma() { - assert!(vm_command("/v,m".into(), Path::new("/")) + assert!(vm_command("/v,m".into(), Path::new("/"), -1) .unwrap_err() .contains("comma")); } diff --git a/host/start-vm/meson.build b/host/start-vm/meson.build index 74b88f0..c5c9db2 100644 --- a/host/start-vm/meson.build +++ b/host/start-vm/meson.build @@ -7,7 +7,7 @@ project('start-vm', 'rust', 'c', add_project_arguments('-D_GNU_SOURCE', language : 'c') add_project_arguments('-C', 'panic=abort', language : 'rust') -c_lib = static_library('start-vm', 'net.c', 'net-util.c') +c_lib = static_library('start-vm', 'net.c', 'net-util.c', 'unix.c') rust_lib = static_library('start_vm', 'lib.rs', link_with : c_lib) executable('start-vm', 'start-vm.rs', link_with : rust_lib, install : true) diff --git a/host/start-vm/s6.rs b/host/start-vm/s6.rs new file mode 100644 index 0000000..eda587f --- /dev/null +++ b/host/start-vm/s6.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: EUPL-1.2+ +// SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is> + +use std::fs::File; +use std::io::Write; +use std::os::unix::prelude::*; + +/// # Safety +/// +/// Can only be called once per process, because afterwards the descriptor will be +/// closed. +pub unsafe fn notify_readiness() -> Result<(), String> { + let mut supervisor = File::from_raw_fd(3); + writeln!(supervisor).map_err(|e| format!("notifying readiness: {e}")) +} diff --git a/host/start-vm/start-vm.rs b/host/start-vm/start-vm.rs index 4790841..df1ce0a 100644 --- a/host/start-vm/start-vm.rs +++ b/host/start-vm/start-vm.rs @@ -1,28 +1,40 @@ // SPDX-License-Identifier: EUPL-1.2+ -// SPDX-FileCopyrightText: 2022 Alyssa Ross <hi@alyssa.is> +// SPDX-FileCopyrightText: 2022-2023 Alyssa Ross <hi@alyssa.is> use std::env::current_dir; use std::os::unix::prelude::*; use std::path::Path; use std::process::exit; -use start_vm::{prog_name, vm_command}; +use start_vm::{create_api_socket, notify_readiness, prog_name, vm_command}; const CONFIG_ROOT: &str = "/ext/svc/data"; -fn run() -> String { +/// # Safety +/// +/// Calls [`notify_readiness`], so can only be called once. +unsafe fn run() -> String { let dir = match current_dir().map_err(|e| format!("getting current directory: {}", e)) { Ok(dir) => dir, Err(e) => return e, }; - match vm_command(dir, Path::new(CONFIG_ROOT)) { + let api_socket = match create_api_socket() { + Ok(api_socket) => api_socket, + Err(e) => return e, + }; + + if let Err(e) = notify_readiness() { + return e; + } + + match vm_command(dir, Path::new(CONFIG_ROOT), api_socket.into_raw_fd()) { Ok(mut command) => format!("failed to exec: {}", command.exec()), Err(e) => e, } } fn main() { - eprintln!("{}: {}", prog_name(), run()); + eprintln!("{}: {}", prog_name(), unsafe { run() }); exit(1); } diff --git a/host/start-vm/tests/vm_command-basic.rs b/host/start-vm/tests/vm_command-basic.rs index 4145b94..dc12ef2 100644 --- a/host/start-vm/tests/vm_command-basic.rs +++ b/host/start-vm/tests/vm_command-basic.rs @@ -21,19 +21,16 @@ fn main() -> std::io::Result<()> { File::create(&kernel_path)?; File::create(&image_path)?; - let command = vm_command(service_dir, &tmp_dir.path().join("svc/data")).unwrap(); - assert_eq!(command.get_program(), "s6-notifyoncheck"); + let command = vm_command(service_dir, &tmp_dir.path().join("svc/data"), 4).unwrap(); + assert_eq!(command.get_program(), "cloud-hypervisor"); let mut expected_disk_arg = OsString::from("path="); expected_disk_arg.push(image_path); expected_disk_arg.push(",readonly=on"); let expected_args = vec![ - OsStr::new("-dc"), - OsStr::new("test -S env/cloud-hypervisor.sock"), - OsStr::new("cloud-hypervisor"), OsStr::new("--api-socket"), - OsStr::new("env/cloud-hypervisor.sock"), + OsStr::new("fd=4"), OsStr::new("--cmdline"), OsStr::new("console=ttyS0 root=PARTLABEL=root"), OsStr::new("--memory"), diff --git a/host/start-vm/tests/vm_command-multiple-disks.rs b/host/start-vm/tests/vm_command-multiple-disks.rs index 2aaf6f3..3b6f9aa 100644 --- a/host/start-vm/tests/vm_command-multiple-disks.rs +++ b/host/start-vm/tests/vm_command-multiple-disks.rs @@ -21,7 +21,7 @@ fn main() -> std::io::Result<()> { symlink("/dev/null", vm_config.join("blk/disk1.img"))?; symlink("/dev/null", vm_config.join("blk/disk2.img"))?; - let command = vm_command(service_dir, &tmp_dir.path().join("svc/data")).unwrap(); + let command = vm_command(service_dir, &tmp_dir.path().join("svc/data"), -1).unwrap(); let args: Box<[_]> = command.get_args().collect(); for i in 1..=2 { diff --git a/host/start-vm/tests/vm_command-shared-dir.rs b/host/start-vm/tests/vm_command-shared-dir.rs index af17a7f..583d19d 100644 --- a/host/start-vm/tests/vm_command-shared-dir.rs +++ b/host/start-vm/tests/vm_command-shared-dir.rs @@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> { create_dir(vm_config.join("shared-dirs/dir2"))?; symlink("/", vm_config.join("shared-dirs/dir2/dir"))?; - let command = vm_command(service_dir, &tmp_dir.path().join("svc/data")).unwrap(); + let command = vm_command(service_dir, &tmp_dir.path().join("svc/data"), -1).unwrap(); let args: Box<[_]> = command.get_args().collect(); for i in 1..=2 { diff --git a/host/start-vm/unix.c b/host/start-vm/unix.c new file mode 100644 index 0000000..43143fd --- /dev/null +++ b/host/start-vm/unix.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: EUPL-1.2+ +// SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is> + +#include <fcntl.h> + +[[gnu::fd_arg(1)]] +int clear_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD); + if (flags == -1) + return -1; + return fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC); +} diff --git a/host/start-vm/unix.rs b/host/start-vm/unix.rs new file mode 100644 index 0000000..8213497 --- /dev/null +++ b/host/start-vm/unix.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: EUPL-1.2+ +// SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is> + +use std::ffi::c_int; +use std::os::fd::BorrowedFd; + +extern "C" { + pub fn clear_cloexec(fd: BorrowedFd) -> c_int; +} |