summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2023-06-18 13:14:14 +0000
committerAlyssa Ross <hi@alyssa.is>2023-06-24 08:12:25 +0000
commita72481208b75d40b350b330b91a2f4d881caa2c5 (patch)
treea7cb656ac734471bf0d10d7ba102dc1dc089d55e
parent35c8269325c3d03c0592a25abb9a469bd41697e7 (diff)
downloadspectrum-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.rs35
-rw-r--r--host/start-vm/meson.build2
-rw-r--r--host/start-vm/s6.rs15
-rw-r--r--host/start-vm/start-vm.rs22
-rw-r--r--host/start-vm/tests/vm_command-basic.rs9
-rw-r--r--host/start-vm/tests/vm_command-multiple-disks.rs2
-rw-r--r--host/start-vm/tests/vm_command-shared-dir.rs2
-rw-r--r--host/start-vm/unix.c13
-rw-r--r--host/start-vm/unix.rs9
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;
+}