diff --git a/Cargo.lock b/Cargo.lock index 262cefba..ac3d7d0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -494,6 +494,17 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "listenfd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87bc54a4629b4294d0b3ef041b64c40c611097a677d9dc07b2c67739fe39dba" +dependencies = [ + "libc", + "uuid", + "winapi", +] + [[package]] name = "lock_api" version = "0.4.12" @@ -563,6 +574,7 @@ dependencies = [ "input-linux", "input-linux-sys", "krun-sys", + "listenfd", "log", "neli", "nix 0.30.1", @@ -1044,6 +1056,28 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/crates/muvm/Cargo.toml b/crates/muvm/Cargo.toml index df87995d..583b47ae 100644 --- a/crates/muvm/Cargo.toml +++ b/crates/muvm/Cargo.toml @@ -29,6 +29,7 @@ tokio = { version = "1.38.0", default-features = false, features = ["io-util", " tokio-stream = { version = "0.1.15", default-features = false, features = ["net", "sync"] } udev = { version = "0.9.0", default-features = false, features = [] } uuid = { version = "1.10.0", default-features = false, features = ["serde", "std", "v7"] } +listenfd = "1.0.2" [[bin]] name = "muvm" diff --git a/crates/muvm/src/bin/muvm.rs b/crates/muvm/src/bin/muvm.rs index 0827e8db..d839c639 100644 --- a/crates/muvm/src/bin/muvm.rs +++ b/crates/muvm/src/bin/muvm.rs @@ -429,13 +429,21 @@ fn main() -> Result { .to_str() .context("Temporary directory path contains invalid UTF-8")? .to_owned(); - let muvm_guest_args = vec![ - muvm_guest_path - .to_str() - .context("Failed to process `muvm-guest` path as it contains invalid UTF-8")? - .to_owned(), - muvm_config_path, - ]; + let custom_init = options.custom_init_cmdline.is_some(); + let muvm_guest_args = if let Some(cmdline) = options.custom_init_cmdline { + cmdline + .split_whitespace() + .map(|a| a.to_owned()) + .collect::>() + } else { + vec![ + muvm_guest_path + .to_str() + .context("Failed to process `muvm-guest` path as it contains invalid UTF-8")? + .to_owned(), + muvm_config_path, + ] + }; // And forward XAUTHORITY. This will be modified to fix the // display name in muvm-guest. @@ -470,7 +478,21 @@ fn main() -> Result { let krun_config_env = CString::new(format!("KRUN_CONFIG={}", config_file.path().display())) .context("Failed to process config_file var as it contains NUL character")?; - let env: Vec<*const c_char> = vec![krun_config_env.as_ptr(), std::ptr::null()]; + #[allow(unused_assignments)] // wat? + let mut muvm_config_env = None; // keep in this scope + let mut env: Vec<*const c_char> = vec![krun_config_env.as_ptr()]; + if custom_init { + env.push(c"KRUN_INIT_PID1=1".as_ptr()); + muvm_config_env = Some( + CString::new(format!( + "MUVM_REMOTE_CONFIG={}", + muvm_config_file.path().display() + )) + .context("Failed to process internal config path as it contains NUL character")?, + ); + env.push(muvm_config_env.as_ref().unwrap().as_ptr()); + } + env.push(std::ptr::null()); { // Sets environment variables to be configured in the context of the executable. diff --git a/crates/muvm/src/cli_options.rs b/crates/muvm/src/cli_options.rs index 5080c911..69326da0 100644 --- a/crates/muvm/src/cli_options.rs +++ b/crates/muvm/src/cli_options.rs @@ -47,6 +47,7 @@ pub struct Options { pub emulator: Option, pub init_commands: Vec, pub user_init_commands: Vec, + pub custom_init_cmdline: Option, pub command: PathBuf, pub command_args: Vec, } @@ -179,6 +180,13 @@ pub fn options() -> OptionParser { ) .argument("COMMAND") .many(); + let custom_init_cmdline = long("custom-init-cmdline") + .help( + "Command and arguments to run as PID 1, replacing muvm's own init. + (Warning: this will break many muvm features, unless your init reimplements them.)", + ) + .argument("CMDLINE") + .optional(); let command = positional("COMMAND").help("the command you want to execute in the vm"); let command_args = any::("COMMAND_ARGS", |arg| { (!["--help", "-h"].contains(&&*arg)).then_some(arg) @@ -202,6 +210,7 @@ pub fn options() -> OptionParser { emulator, init_commands, user_init_commands, + custom_init_cmdline, // positionals command, command_args, diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index 6bac1838..757780d9 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -7,8 +7,9 @@ use std::{cmp, env, fs, thread}; use anyhow::{anyhow, Context, Result}; use muvm::guest::box64::setup_box; -use muvm::guest::bridge::pipewire::start_pwbridge; -use muvm::guest::bridge::x11::start_x11bridge; +use muvm::guest::bridge::common::{bridge_loop, bridge_loop_with_listenfd}; +use muvm::guest::bridge::pipewire::{pipewire_sock_path, PipeWireProtocolHandler}; +use muvm::guest::bridge::x11::{start_x11bridge, X11ProtocolHandler}; use muvm::guest::fex::setup_fex; use muvm::guest::hidpipe::start_hidpipe; use muvm::guest::mount::mount_filesystems; @@ -24,17 +25,7 @@ use rustix::process::{getrlimit, setrlimit, Resource}; const KRUN_CONFIG: &str = "KRUN_CONFIG"; -fn main() -> Result { - env_logger::init(); - - if let Ok(val) = env::var("__X11BRIDGE_DEBUG") { - start_x11bridge(val.parse()?); - return Ok(ExitCode::SUCCESS); - } - - let config_path = env::args() - .nth(1) - .context("expected configuration file path")?; +fn parse_config(config_path: String) -> Result { let mut config_file = File::open(&config_path)?; let mut config_buf = Vec::new(); config_file.read_to_end(&mut config_buf)?; @@ -47,7 +38,54 @@ fn main() -> Result { } // SAFETY: We are single-threaded at this point env::remove_var("KRUN_WORKDIR"); - let options = serde_json::from_slice::(&config_buf)?; + Ok(serde_json::from_slice::(&config_buf)?) +} + +fn main() -> Result { + env_logger::init(); + + let binary_path = env::args().next().context("arg0")?; + let bb = binary_path.split('/').next_back().context("arg0 split")?; + match bb { + "muvm-configure-network" => return configure_network().map(|()| ExitCode::SUCCESS), + "muvm-pwbridge" => { + bridge_loop_with_listenfd::(pipewire_sock_path); + return Ok(ExitCode::SUCCESS); + }, + "muvm-x11bridge" => { + bridge_loop_with_listenfd::(|| "/tmp/.X11-unix/X1".to_owned()); + return Ok(ExitCode::SUCCESS); + }, + "muvm-hidpipe" => { + let config_path = + env::var("MUVM_REMOTE_CONFIG").context("expected MUVM_REMOTE_CONFIG to be set")?; + let options = parse_config(config_path)?; + let uid = options.uid; + start_hidpipe(uid); + return Ok(ExitCode::SUCCESS); + }, + "muvm-remote" => { + let rt = tokio::runtime::Runtime::new().unwrap(); + let config_path = + env::var("MUVM_REMOTE_CONFIG").context("expected MUVM_REMOTE_CONFIG to be set")?; + let options = parse_config(config_path)?; + return rt.block_on(server_main( + options.command.command, + options.command.command_args, + )); + }, + _ => { /* continue with all-in-one mode */ }, + } + + if let Ok(val) = env::var("__X11BRIDGE_DEBUG") { + start_x11bridge(val.parse()?); + return Ok(ExitCode::SUCCESS); + } + + let config_path = env::args() + .nth(1) + .context("expected configuration file path")?; + let options = parse_config(config_path)?; { const ESYNC_RLIMIT_NOFILE: u64 = 524288; @@ -155,7 +193,7 @@ fn main() -> Result { }); thread::spawn(|| { - if catch_unwind(start_pwbridge).is_err() { + if catch_unwind(|| bridge_loop::(&pipewire_sock_path())).is_err() { eprintln!("pwbridge thread crashed, pipewire passthrough will no longer function"); } }); diff --git a/crates/muvm/src/guest/bridge/common.rs b/crates/muvm/src/guest/bridge/common.rs index 7cc54a8c..e96b2abd 100644 --- a/crates/muvm/src/guest/bridge/common.rs +++ b/crates/muvm/src/guest/bridge/common.rs @@ -960,10 +960,24 @@ impl<'a, T: ProtocolHandler> SubPoll<'a, T> { } } +pub fn bridge_loop_with_listenfd(fallback_sock_path: impl Fn() -> String) { + if let Some(listen_sock) = listenfd::ListenFd::from_env() + .take_unix_listener(0) + .unwrap() + { + bridge_loop_sock::(listen_sock) + } else { + bridge_loop::(&fallback_sock_path()) + } +} + pub fn bridge_loop(sock_path: &str) { - let epoll = Epoll::new(EpollCreateFlags::empty()).unwrap(); _ = fs::remove_file(sock_path); - let listen_sock = UnixListener::bind(sock_path).unwrap(); + bridge_loop_sock::(UnixListener::bind(sock_path).unwrap()); +} + +pub fn bridge_loop_sock(listen_sock: UnixListener) { + let epoll = Epoll::new(EpollCreateFlags::empty()).unwrap(); epoll .add( &listen_sock, diff --git a/crates/muvm/src/guest/bridge/pipewire.rs b/crates/muvm/src/guest/bridge/pipewire.rs index 343dd211..005f8a25 100644 --- a/crates/muvm/src/guest/bridge/pipewire.rs +++ b/crates/muvm/src/guest/bridge/pipewire.rs @@ -9,7 +9,6 @@ use nix::errno::Errno; use nix::sys::epoll::EpollFlags; use nix::sys::eventfd::{EfdFlags, EventFd}; -use crate::guest::bridge::common; use crate::guest::bridge::common::{ Client, CrossDomainHeader, CrossDomainResource, MessageResourceFinalizer, ProtocolHandler, StreamRecvResult, StreamSendResult, @@ -165,7 +164,7 @@ impl PipeWireHeader { } } -struct PipeWireResourceFinalizer; +pub struct PipeWireResourceFinalizer; impl MessageResourceFinalizer for PipeWireResourceFinalizer { type Handler = PipeWireProtocolHandler; @@ -194,7 +193,7 @@ impl ClientNodeData { } } -struct PipeWireProtocolHandler { +pub struct PipeWireProtocolHandler { client_nodes: HashMap, guest_to_host_eventfds: HashMap, host_to_guest_eventfds: HashMap, @@ -405,8 +404,6 @@ impl ProtocolHandler for PipeWireProtocolHandler { } } -pub fn start_pwbridge() { - let sock_path = format!("{}/pipewire-0", env::var("XDG_RUNTIME_DIR").unwrap()); - - common::bridge_loop::(&sock_path) +pub fn pipewire_sock_path() -> String { + format!("{}/pipewire-0", env::var("XDG_RUNTIME_DIR").unwrap()) } diff --git a/crates/muvm/src/guest/bridge/x11.rs b/crates/muvm/src/guest/bridge/x11.rs index 0bc4616b..62cc64e5 100644 --- a/crates/muvm/src/guest/bridge/x11.rs +++ b/crates/muvm/src/guest/bridge/x11.rs @@ -4,7 +4,6 @@ use std::ffi::{c_long, c_void, CString}; use std::fs::{read_to_string, remove_file, File}; use std::io::{IoSlice, Write}; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; -use std::process::exit; use std::ptr::NonNull; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, OnceLock}; @@ -156,7 +155,7 @@ struct CrossDomainFutexDestroy { pad: u32, } -enum X11ResourceFinalizer { +pub enum X11ResourceFinalizer { Gem(GemHandleFinalizer), Futex(u32), } @@ -187,7 +186,7 @@ impl MessageResourceFinalizer for X11ResourceFinalizer { } } -struct X11ProtocolHandler { +pub struct X11ProtocolHandler { // futex_watchers gets dropped first futex_watchers: HashMap, got_first_req: bool, @@ -704,7 +703,7 @@ impl RemoteCaller { // Find the vDSO and the address of a syscall instruction within it let (vdso_start, _) = find_vdso(Some(pid))?; - let syscall_addr = vdso_start + SYSCALL_OFFSET.get().unwrap(); + let syscall_addr = vdso_start + SYSCALL_OFFSET.get_or_init(find_syscall_offset); let mut regs = old_regs; arch::set_syscall_addr(&mut regs, syscall_addr); @@ -847,9 +846,7 @@ fn find_vdso(pid: Option) -> Result<(usize, usize), Errno> { Err(Errno::EINVAL) } -pub fn start_x11bridge(display: u32) { - let sock_path = format!("/tmp/.X11-unix/X{display}"); - +fn find_syscall_offset() -> usize { // Look for a syscall instruction in the vDSO. We assume all processes map // the same vDSO (which should be true if they are running under the same // kernel!) @@ -858,14 +855,13 @@ pub fn start_x11bridge(display: u32) { let addr = vdso_start + off; let val = unsafe { std::ptr::read(addr as *const arch::SyscallInstr) }; if val == arch::SYSCALL_INSTR { - SYSCALL_OFFSET.set(off).unwrap(); - break; + return off; } } - if SYSCALL_OFFSET.get().is_none() { - eprintln!("Failed to find syscall instruction in vDSO"); - exit(1); - } + panic!("Failed to find syscall instruction in vDSO"); +} +pub fn start_x11bridge(display: u32) { + let sock_path = format!("/tmp/.X11-unix/X{display}"); common::bridge_loop::(&sock_path) }