// SPDX-License-Identifier: EUPL-1.2+ // SPDX-FileCopyrightText: 2022-2024 Alyssa Ross // SPDX-FileCopyrightText: 2025 Yureka Lilian use std::ffi::OsStr; use std::fs::File; use std::io::Write; use std::mem::take; use std::num::NonZeroI32; use std::os::unix::prelude::*; use std::path::Path; use std::process::{Command, Stdio}; use miniserde::{Serialize, json}; use crate::net::MacAddress; use crate::s6::notify_readiness; // Trivially safe. const EPERM: NonZeroI32 = NonZeroI32::new(1).unwrap(); const EPROTO: NonZeroI32 = NonZeroI32::new(71).unwrap(); #[derive(Serialize)] pub struct ConsoleConfig { pub mode: &'static str, pub file: Option, } #[derive(Serialize)] pub struct DiskConfig { pub path: String, pub readonly: bool, } #[derive(Serialize)] pub struct FsConfig { pub socket: String, pub tag: &'static str, } #[derive(Serialize)] pub struct GpuConfig { pub socket: String, } #[derive(Serialize)] pub struct NetConfig { pub vhost_user_sock: String, pub id: String, pub mac: MacAddress, } #[derive(Serialize)] pub struct MemoryConfig { pub size: i64, pub shared: bool, } #[derive(Serialize)] pub struct PayloadConfig { pub kernel: String, pub cmdline: &'static str, } #[derive(Serialize)] pub struct VsockConfig { pub cid: u32, pub socket: String, } #[derive(Serialize)] pub struct LandlockConfig { pub path: &'static str, pub access: &'static str, } #[derive(Serialize)] pub struct VmConfig { pub console: ConsoleConfig, pub disks: Vec, pub fs: [FsConfig; 1], pub gpu: Vec, pub memory: MemoryConfig, pub net: Vec, pub payload: PayloadConfig, pub serial: ConsoleConfig, pub vsock: VsockConfig, pub landlock_enable: bool, pub landlock_rules: Vec, } fn command(vm_dir: &Path, s: impl AsRef) -> Command { let mut command = Command::new("ch-remote"); command.stdin(Stdio::null()); command.arg("--api-socket"); command.arg(vm_dir.join("vmm")); command.arg(s); command } pub fn create_vm(vm_dir: &Path, ready_fd: File, mut config: VmConfig) -> Result<(), String> { // Net devices can't be created from file descriptors in vm.create. // https://github.com/cloud-hypervisor/cloud-hypervisor/issues/5523 let nets = take(&mut config.net); let mut ch_remote = command(vm_dir, "create") .args(["--", "-"]) .stdin(Stdio::piped()) .spawn() .map_err(|e| format!("failed to start ch-remote: {e}"))?; let json = json::to_string(&config); write!(ch_remote.stdin.as_ref().unwrap(), "{json}") .map_err(|e| format!("writing to ch-remote's stdin: {e}"))?; let status = ch_remote .wait() .map_err(|e| format!("waiting for ch-remote: {e}"))?; if !status.success() { if let Some(code) = status.code() { return Err(format!("ch-remote exited {code}")); } else { let signal = status.signal().unwrap(); return Err(format!("ch-remote killed by signal {signal}")); } } notify_readiness(ready_fd)?; for net in nets { add_net(vm_dir, &net).map_err(|e| format!("failed to add net: {e}"))?; } Ok(()) } pub fn add_net(vm_dir: &Path, net: &NetConfig) -> Result<(), NonZeroI32> { let mut ch_remote = command(vm_dir, "add-net") .arg(format!( "vhost_user=on,socket={},id={},mac={}", net.vhost_user_sock, net.id, net.mac )) .stdout(Stdio::piped()) .spawn() .or(Err(EPERM))?; if let Ok(ch_remote_status) = ch_remote.wait() && ch_remote_status.success() { return Ok(()); } Err(EPROTO) }