From 81ba91c9c200fb165b73f0287119aae90c597e2c Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 5 Sep 2025 05:23:47 -0300 Subject: [PATCH 1/8] Allow overriding external binary paths via compile-time env vars This is particularly useful for Nix, to avoid patching code when building. Signed-off-by: Val Packett --- crates/muvm/src/guest/bin/muvm-guest.rs | 4 +++- crates/muvm/src/monitor.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index 4cb5f39c..a671cde5 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -77,7 +77,9 @@ fn main() -> Result { rustix::stdio::dup2_stdout(console.as_fd())?; rustix::stdio::dup2_stderr(console.as_fd())?; - Command::new("/usr/lib/systemd/systemd-udevd").spawn()?; + Command::new(std::option_env!("MUVM_UDEVD_PATH").unwrap_or("/usr/lib/systemd/systemd-udevd")) + .spawn() + .context("Running systemd-udevd")?; if let Some(emulator) = options.emulator { match emulator { diff --git a/crates/muvm/src/monitor.rs b/crates/muvm/src/monitor.rs index 316ecb20..533cc430 100644 --- a/crates/muvm/src/monitor.rs +++ b/crates/muvm/src/monitor.rs @@ -49,7 +49,7 @@ fn set_guest_pressure(pressure: GuestPressure) -> Result<()> { let wsf: u32 = pressure.into(); debug!("setting watermark_scale_factor to {wsf}"); - let command = PathBuf::from("/sbin/sysctl"); + let command = PathBuf::from(std::option_env!("MUVM_SYSCTL_PATH").unwrap_or("/sbin/sysctl")); let command_args = vec![format!("vm.watermark_scale_factor={}", wsf)]; let env = HashMap::new(); request_launch(command, command_args, env, 0, false, true) From 0a65332213a4364d98b48d5b060dff6406b9caf7 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 19 Sep 2025 01:23:09 -0300 Subject: [PATCH 2/8] Allow overriding udevd binary path at runtime Signed-off-by: Val Packett --- crates/muvm/src/guest/bin/muvm-guest.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index a671cde5..9f863571 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -77,9 +77,17 @@ fn main() -> Result { rustix::stdio::dup2_stdout(console.as_fd())?; rustix::stdio::dup2_stderr(console.as_fd())?; - Command::new(std::option_env!("MUVM_UDEVD_PATH").unwrap_or("/usr/lib/systemd/systemd-udevd")) - .spawn() - .context("Running systemd-udevd")?; + const DEFAULT_UDEVD_PATH: &str = match std::option_env!("MUVM_UDEVD_PATH") { + Some(path) => path, + None => "/usr/lib/systemd/systemd-udevd", + }; + Command::new( + env::var("MUVM_UDEVD_PATH").unwrap_or_else(|_| DEFAULT_UDEVD_PATH.parse().unwrap()), + ) + .spawn() + .context("Failed to execute `systemd-udevd` as a child process")?; + // SAFETY: We are single-threaded at this point + env::remove_var("MUVM_UDEVD_PATH"); if let Some(emulator) = options.emulator { match emulator { From b38e9c01ba6b8a126db32106ffb50f90c2cbc9ca Mon Sep 17 00:00:00 2001 From: Val Packett Date: Tue, 18 Nov 2025 01:57:31 -0300 Subject: [PATCH 3/8] Extract get_var_if_exists into common code And use it for MUVM_UDEVD_PATH Signed-off-by: Val Packett --- crates/muvm/src/config.rs | 13 +++---------- crates/muvm/src/guest/bin/muvm-guest.rs | 4 +++- crates/muvm/src/utils/env.rs | 8 ++++++++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/muvm/src/config.rs b/crates/muvm/src/config.rs index d2906011..a561d7d2 100644 --- a/crates/muvm/src/config.rs +++ b/crates/muvm/src/config.rs @@ -1,25 +1,18 @@ use std::{ - env::{self, VarError}, - io, + env, io, path::{Path, PathBuf}, }; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; +use crate::utils::env::get_var_if_exists; + #[derive(Deserialize, Serialize, Default)] pub struct Configuration { pub execute_pre: Option, } -fn get_var_if_exists(name: &str) -> Option> { - match env::var(name) { - Ok(val) => Some(Ok(val)), - Err(VarError::NotPresent) => None, - Err(e) => Some(Err(e.into())), - } -} - fn get_user_config_path() -> Result { let mut path = get_var_if_exists("XDG_CONFIG_DIR").map_or_else( || { diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index 9f863571..17b0161f 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -17,6 +17,7 @@ use muvm::guest::server::server_main; use muvm::guest::socket::setup_socket_proxy; use muvm::guest::user::setup_user; use muvm::guest::x11::setup_x11_forwarding; +use muvm::utils::env::get_var_if_exists; use muvm::utils::launch::{Emulator, GuestConfiguration, PULSE_SOCKET}; use nix::unistd::{Gid, Uid}; use rustix::process::{getrlimit, setrlimit, Resource}; @@ -82,7 +83,8 @@ fn main() -> Result { None => "/usr/lib/systemd/systemd-udevd", }; Command::new( - env::var("MUVM_UDEVD_PATH").unwrap_or_else(|_| DEFAULT_UDEVD_PATH.parse().unwrap()), + get_var_if_exists("MUVM_UDEVD_PATH") + .unwrap_or_else(|| Ok(DEFAULT_UDEVD_PATH.to_owned()))?, ) .spawn() .context("Failed to execute `systemd-udevd` as a child process")?; diff --git a/crates/muvm/src/utils/env.rs b/crates/muvm/src/utils/env.rs index f12372b9..2c6bd6ae 100644 --- a/crates/muvm/src/utils/env.rs +++ b/crates/muvm/src/utils/env.rs @@ -32,3 +32,11 @@ where Ok(None) } + +pub fn get_var_if_exists(name: &str) -> Option> { + match env::var(name) { + Ok(val) => Some(Ok(val)), + Err(env::VarError::NotPresent) => None, + Err(e) => Some(Err(e.into())), + } +} From a7b50855942addd170f2740c7c5a04888fe18df4 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 19 Sep 2025 00:20:28 -0300 Subject: [PATCH 4/8] muvm-guest: add virtual command for memory watermark sysctl To remove the need for a sysctl binary. Signed-off-by: Val Packett --- crates/muvm/src/guest/server_worker.rs | 80 +++++++++++++++----------- crates/muvm/src/monitor.rs | 4 +- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/crates/muvm/src/guest/server_worker.rs b/crates/muvm/src/guest/server_worker.rs index c7c5459d..368a570b 100644 --- a/crates/muvm/src/guest/server_worker.rs +++ b/crates/muvm/src/guest/server_worker.rs @@ -34,7 +34,7 @@ use crate::utils::stdio::make_stdout_stderr; use crate::utils::tty::*; pub enum ConnRequest { - DropCaches, + HandledByBuiltin, ExecuteCommand { command: PathBuf, child: Child, @@ -83,7 +83,7 @@ impl Worker { match handle_connection(stream).await { Ok(request) => match request { - ConnRequest::DropCaches => {}, + ConnRequest::HandledByBuiltin => {}, ConnRequest::ExecuteCommand {command, mut child, stop_pipe } => { self.child_set.spawn(async move { (command, child.wait().await, stop_pipe) }); self.set_child_processes(self.child_set.len()); @@ -190,6 +190,41 @@ async fn read_request(stream: &mut BufStream) -> Result { } } +async fn write_to( + mut stream: BufStream, + path: &'static std::ffi::CStr, + data: &[u8], +) -> Result { + // SAFETY: `open` and `write` are async signal safe + let code = unsafe { + user::run_as_root(|| { + let fd = nix::libc::open(path.as_ptr(), nix::libc::O_WRONLY); + if fd < 0 { + return 1; + } + let written = nix::libc::write(fd, data.as_ptr() as *const _, data.len()) as usize; + if written == data.len() { + 0 + } else { + 2 + } + }) + .with_context(|| format!("Failed to write to {path:?}"))? + }; + match code { + 0 => { + stream.write_all(b"OK").await.ok(); + stream.flush().await.ok(); + Ok(ConnRequest::HandledByBuiltin) + }, + 1 => Err(anyhow!("Failed to open {path:?} for writing")), + 2 => Err(anyhow!("Failed to write to {path:?}")), + err => Err(anyhow!( + "Unexpected return status when attempting to write to {path:?}: {err}" + )), + } +} + async fn handle_connection(mut stream: BufStream) -> Result { let mut envs: HashMap = env::vars().collect(); @@ -204,38 +239,17 @@ async fn handle_connection(mut stream: BufStream) -> Result { - stream.write_all(b"OK").await.ok(); - stream.flush().await.ok(); - Ok(ConnRequest::DropCaches) - }, - 1 => Err(anyhow!( - "Failed to open /proc/sys/vm/drop_caches for writing" - )), - 2 => Err(anyhow!("Failed to write to /proc/sys/vm/drop_caches")), - e => Err(anyhow!( - "Unexpected return status when attempting to drop caches: {}", - e - )), + return write_to(stream, c"/proc/sys/vm/drop_caches", b"1").await; + } else if command == Path::new("/muvmwatermarkscalefactor") { + let Some(data) = command_args.first() else { + return Err(anyhow!("muvmwatermarkscalefactor: missing arg")); }; + return write_to( + stream, + c"/proc/sys/vm/watermark_scale_factor", + data.as_bytes(), + ) + .await; } envs.extend(env); diff --git a/crates/muvm/src/monitor.rs b/crates/muvm/src/monitor.rs index 533cc430..ed54bcf5 100644 --- a/crates/muvm/src/monitor.rs +++ b/crates/muvm/src/monitor.rs @@ -49,8 +49,8 @@ fn set_guest_pressure(pressure: GuestPressure) -> Result<()> { let wsf: u32 = pressure.into(); debug!("setting watermark_scale_factor to {wsf}"); - let command = PathBuf::from(std::option_env!("MUVM_SYSCTL_PATH").unwrap_or("/sbin/sysctl")); - let command_args = vec![format!("vm.watermark_scale_factor={}", wsf)]; + let command = PathBuf::from("/muvmwatermarkscalefactor"); + let command_args = vec![format!("{wsf}")]; let env = HashMap::new(); request_launch(command, command_args, env, 0, false, true) } From 2936c0a95dca1cb6bd0831c1ba6fac58d369e9a8 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Tue, 16 Sep 2025 21:13:04 -0300 Subject: [PATCH 5/8] x11: add error context for opening XAUTHORITY Signed-off-by: Val Packett --- crates/muvm/src/guest/x11.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/muvm/src/guest/x11.rs b/crates/muvm/src/guest/x11.rs index bb74d452..45d1777e 100644 --- a/crates/muvm/src/guest/x11.rs +++ b/crates/muvm/src/guest/x11.rs @@ -25,7 +25,7 @@ where if let Ok(xauthority) = std::env::var("XAUTHORITY") { let src_path = format!("/run/muvm-host/{xauthority}"); - let mut rdr = File::open(src_path)?; + let mut rdr = File::open(src_path).context("Failed to open XAUTHORITY")?; let dst_path = run_path.as_ref().join("xauth"); let mut wtr = File::options() From 5b7e9fde0f2cee6067b8a99407ff00e1d1e079d0 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Fri, 19 Sep 2025 01:35:55 -0300 Subject: [PATCH 6/8] x11: add x86_64 support Signed-off-by: Val Packett --- crates/muvm/src/guest/bridge/x11.rs | 85 ++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/crates/muvm/src/guest/bridge/x11.rs b/crates/muvm/src/guest/bridge/x11.rs index c0b01e44..0bc4616b 100644 --- a/crates/muvm/src/guest/bridge/x11.rs +++ b/crates/muvm/src/guest/bridge/x11.rs @@ -51,7 +51,6 @@ const DRI3_OPCODE_PIXMAP_FROM_BUFFERS: u8 = 7; const PRESENT_OPCODE_PRESENT_PIXMAP: u8 = 1; pub const SHM_TEMPLATE: &str = "/dev/shm/krshm-XXXXXX"; pub const SHM_DIR: &str = "/dev/shm/"; -const SYSCALL_INSTR: u32 = 0xd4000001; static SYSCALL_OFFSET: OnceLock = OnceLock::new(); const CROSS_DOMAIN_CHANNEL_TYPE_X11: u32 = 0x11; const CROSS_DOMAIN_ID_TYPE_SHM: u32 = 5; @@ -59,6 +58,64 @@ const CROSS_DOMAIN_CMD_FUTEX_NEW: u8 = 8; const CROSS_DOMAIN_CMD_FUTEX_SIGNAL: u8 = 9; const CROSS_DOMAIN_CMD_FUTEX_DESTROY: u8 = 10; +#[cfg(target_arch = "aarch64")] +mod arch { + use nix::libc::{c_long, c_ulonglong, user_regs_struct}; + pub type SyscallInstr = u32; + pub const SYSCALL_INSTR: SyscallInstr = 0xd4000001; + + pub fn set_syscall_addr(regs: &mut user_regs_struct, syscall_addr: usize) { + regs.pc = syscall_addr as u64; + } + + pub fn fill_syscall_args( + regs: &mut user_regs_struct, + syscall_no: c_long, + args: &[c_ulonglong; 6], + ) { + regs.regs[..6].copy_from_slice(args); + regs.regs[8] = syscall_no as c_ulonglong; + } + + pub fn get_syscall_result(regs: &user_regs_struct) -> c_ulonglong { + regs.regs[0] + } +} + +#[cfg(target_arch = "x86_64")] +mod arch { + use nix::libc::{c_long, c_ulonglong, user_regs_struct}; + pub type SyscallInstr = u16; + pub const SYSCALL_INSTR: SyscallInstr = 0x05_0f; + + pub fn set_syscall_addr(regs: &mut user_regs_struct, syscall_addr: usize) { + regs.rip = syscall_addr as u64; + } + + pub fn fill_syscall_args( + regs: &mut user_regs_struct, + syscall_no: c_long, + args: &[c_ulonglong; 6], + ) { + regs.rdi = args[0]; + regs.rsi = args[1]; + regs.rdx = args[2]; + regs.r10 = args[3]; + regs.r8 = args[4]; + regs.r9 = args[5]; + regs.rax = syscall_no as c_ulonglong; + } + + pub fn get_syscall_result(regs: &user_regs_struct) -> c_ulonglong { + regs.rax + } +} + +#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] +mod arch { + pub const SYSCALL_INSTR: u32 = 0xffffffff; +} + #[repr(C)] #[derive(Debug, Default)] struct ExportedHandle { @@ -458,7 +515,7 @@ impl X11ProtocolHandler { // Allow everything in /dev/shm (including paths with trailing '(deleted)') let shmem_file = if filename.starts_with(SHM_DIR) { File::from(memfd) - } else if cfg!(not(target_arch = "aarch64")) { + } else if cfg!(not(any(target_arch = "aarch64", target_arch = "x86_64"))) { return Err(Errno::EOPNOTSUPP.into()); } else { let (fd, shmem_path) = mkstemp(SHM_TEMPLATE)?; @@ -638,8 +695,7 @@ struct RemoteCaller { } impl RemoteCaller { - // This is arch-specific, so gate it off of x86_64 builds done for CI purposes - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] fn with(pid: Pid, f: F) -> Result where F: FnOnce(&RemoteCaller) -> Result, @@ -651,7 +707,7 @@ impl RemoteCaller { let syscall_addr = vdso_start + SYSCALL_OFFSET.get().unwrap(); let mut regs = old_regs; - regs.pc = syscall_addr as u64; + arch::set_syscall_addr(&mut regs, syscall_addr); ptrace::setregs(pid, regs)?; let res = f(&RemoteCaller { regs, pid })?; ptrace::setregs(pid, old_regs)?; @@ -711,12 +767,10 @@ impl RemoteCaller { .map(|x| x as i32) } - // This is arch-specific, so gate it off of x86_64 builds done for CI purposes - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] fn syscall(&self, syscall_no: c_long, args: [c_ulonglong; 6]) -> Result { let mut regs = self.regs; - regs.regs[..6].copy_from_slice(&args); - regs.regs[8] = syscall_no as c_ulonglong; + arch::fill_syscall_args(&mut regs, syscall_no, &args); ptrace::setregs(self.pid, regs)?; ptrace::step(self.pid, None)?; let evt = waitpid(self.pid, Some(WaitPidFlag::__WALL))?; @@ -724,17 +778,18 @@ impl RemoteCaller { unimplemented!(); } regs = ptrace::getregs(self.pid)?; - Ok(regs.regs[0]) + Ok(arch::get_syscall_result(®s)) } - #[cfg(not(target_arch = "aarch64"))] + #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] fn with(_pid: Pid, _f: F) -> Result where F: FnOnce(&RemoteCaller) -> Result, { Err(Errno::EOPNOTSUPP.into()) } - #[cfg(not(target_arch = "aarch64"))] + + #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] fn syscall(&self, _syscall_no: c_long, _args: [c_ulonglong; 6]) -> Result { Err(Errno::EOPNOTSUPP.into()) } @@ -799,10 +854,10 @@ pub fn start_x11bridge(display: u32) { // the same vDSO (which should be true if they are running under the same // kernel!) let (vdso_start, vdso_end) = find_vdso(None).unwrap(); - for off in (0..(vdso_end - vdso_start)).step_by(4) { + for off in (0..(vdso_end - vdso_start)).step_by(mem::size_of::()) { let addr = vdso_start + off; - let val = unsafe { std::ptr::read(addr as *const u32) }; - if val == SYSCALL_INSTR { + let val = unsafe { std::ptr::read(addr as *const arch::SyscallInstr) }; + if val == arch::SYSCALL_INSTR { SYSCALL_OFFSET.set(off).unwrap(); break; } From 9c176a34957027968dec9c4a0484580f7373ab60 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Wed, 17 Sep 2025 02:25:52 -0300 Subject: [PATCH 7/8] muvm-guest: add error context to init script launches Signed-off-by: Val Packett --- crates/muvm/src/guest/bin/muvm-guest.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index 17b0161f..bef62ecf 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -109,7 +109,8 @@ fn main() -> Result { for init_command in options.init_commands { let code = Command::new(&init_command) .current_dir(&options.cwd) - .spawn()? + .spawn() + .with_context(|| format!("Failed to execute init command {init_command:?}"))? .wait()?; if !code.success() { return Err(anyhow!("Executing `{}` failed", init_command.display())); From 4ac27cbc387fa794a7cfbcb3d228741ca3f37161 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Tue, 16 Sep 2025 21:13:43 -0300 Subject: [PATCH 8/8] muvm-guest: only auto-initialize emulation on aarch64 Running x86_64 emulators on x86_64 is not typically desired, so only try initializing them without flags on aarch64. While here, let's call Box64 Box64 and not Box. Signed-off-by: Val Packett --- crates/muvm/src/guest/bin/muvm-guest.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index bef62ecf..6bac1838 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -96,13 +96,16 @@ fn main() -> Result { Emulator::Box => setup_box()?, Emulator::Fex => setup_fex()?, }; - } else if let Err(err) = setup_fex() { - eprintln!("Error setting up FEX in binfmt_misc: {err}"); - eprintln!("Failed to find or configure FEX, falling back to Box"); - - if let Err(err) = setup_box() { - eprintln!("Error setting up Box in binfmt_misc: {err}"); - eprintln!("No emulators were configured, x86 emulation may not work"); + } else { + #[cfg(target_arch = "aarch64")] + if let Err(err) = setup_fex() { + eprintln!("Error setting up FEX in binfmt_misc: {err}"); + eprintln!("Failed to find or configure FEX, falling back to Box64"); + + if let Err(err) = setup_box() { + eprintln!("Error setting up Box64 in binfmt_misc: {err}"); + eprintln!("No emulators were configured, x86 emulation may not work"); + } } }