Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/muvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
38 changes: 30 additions & 8 deletions crates/muvm/src/bin/muvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,21 @@ fn main() -> Result<ExitCode> {
.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::<Vec<String>>()
} 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.
Expand Down Expand Up @@ -470,7 +478,21 @@ fn main() -> Result<ExitCode> {

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.
Expand Down
9 changes: 9 additions & 0 deletions crates/muvm/src/cli_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub struct Options {
pub emulator: Option<Emulator>,
pub init_commands: Vec<PathBuf>,
pub user_init_commands: Vec<PathBuf>,
pub custom_init_cmdline: Option<String>,
pub command: PathBuf,
pub command_args: Vec<String>,
}
Expand Down Expand Up @@ -179,6 +180,13 @@ pub fn options() -> OptionParser<Options> {
)
.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::<String, _, _>("COMMAND_ARGS", |arg| {
(!["--help", "-h"].contains(&&*arg)).then_some(arg)
Expand All @@ -202,6 +210,7 @@ pub fn options() -> OptionParser<Options> {
emulator,
init_commands,
user_init_commands,
custom_init_cmdline,
// positionals
command,
command_args,
Expand Down
68 changes: 53 additions & 15 deletions crates/muvm/src/guest/bin/muvm-guest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,17 +25,7 @@ use rustix::process::{getrlimit, setrlimit, Resource};

const KRUN_CONFIG: &str = "KRUN_CONFIG";

fn main() -> Result<ExitCode> {
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<GuestConfiguration> {
let mut config_file = File::open(&config_path)?;
let mut config_buf = Vec::new();
config_file.read_to_end(&mut config_buf)?;
Expand All @@ -47,7 +38,54 @@ fn main() -> Result<ExitCode> {
}
// SAFETY: We are single-threaded at this point
env::remove_var("KRUN_WORKDIR");
let options = serde_json::from_slice::<GuestConfiguration>(&config_buf)?;
Ok(serde_json::from_slice::<GuestConfiguration>(&config_buf)?)
}

fn main() -> Result<ExitCode> {
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::<PipeWireProtocolHandler>(pipewire_sock_path);
return Ok(ExitCode::SUCCESS);
},
"muvm-x11bridge" => {
bridge_loop_with_listenfd::<X11ProtocolHandler>(|| "/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;
Expand Down Expand Up @@ -155,7 +193,7 @@ fn main() -> Result<ExitCode> {
});

thread::spawn(|| {
if catch_unwind(start_pwbridge).is_err() {
if catch_unwind(|| bridge_loop::<PipeWireProtocolHandler>(&pipewire_sock_path())).is_err() {
eprintln!("pwbridge thread crashed, pipewire passthrough will no longer function");
}
});
Expand Down
18 changes: 16 additions & 2 deletions crates/muvm/src/guest/bridge/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -960,10 +960,24 @@ impl<'a, T: ProtocolHandler> SubPoll<'a, T> {
}
}

pub fn bridge_loop_with_listenfd<T: ProtocolHandler>(fallback_sock_path: impl Fn() -> String) {
if let Some(listen_sock) = listenfd::ListenFd::from_env()
.take_unix_listener(0)
.unwrap()
{
bridge_loop_sock::<T>(listen_sock)
} else {
bridge_loop::<T>(&fallback_sock_path())
}
}

pub fn bridge_loop<T: ProtocolHandler>(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::<T>(UnixListener::bind(sock_path).unwrap());
}

pub fn bridge_loop_sock<T: ProtocolHandler>(listen_sock: UnixListener) {
let epoll = Epoll::new(EpollCreateFlags::empty()).unwrap();
epoll
.add(
&listen_sock,
Expand Down
11 changes: 4 additions & 7 deletions crates/muvm/src/guest/bridge/pipewire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -165,7 +164,7 @@ impl PipeWireHeader {
}
}

struct PipeWireResourceFinalizer;
pub struct PipeWireResourceFinalizer;

impl MessageResourceFinalizer for PipeWireResourceFinalizer {
type Handler = PipeWireProtocolHandler;
Expand Down Expand Up @@ -194,7 +193,7 @@ impl ClientNodeData {
}
}

struct PipeWireProtocolHandler {
pub struct PipeWireProtocolHandler {
client_nodes: HashMap<u32, ClientNodeData>,
guest_to_host_eventfds: HashMap<u64, CrossDomainEventFd>,
host_to_guest_eventfds: HashMap<u32, CrossDomainEventFd>,
Expand Down Expand Up @@ -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::<PipeWireProtocolHandler>(&sock_path)
pub fn pipewire_sock_path() -> String {
format!("{}/pipewire-0", env::var("XDG_RUNTIME_DIR").unwrap())
}
22 changes: 9 additions & 13 deletions crates/muvm/src/guest/bridge/x11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -156,7 +155,7 @@ struct CrossDomainFutexDestroy {
pad: u32,
}

enum X11ResourceFinalizer {
pub enum X11ResourceFinalizer {
Gem(GemHandleFinalizer),
Futex(u32),
}
Expand Down Expand Up @@ -187,7 +186,7 @@ impl MessageResourceFinalizer for X11ResourceFinalizer {
}
}

struct X11ProtocolHandler {
pub struct X11ProtocolHandler {
// futex_watchers gets dropped first
futex_watchers: HashMap<u32, FutexWatcherThread>,
got_first_req: bool,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -847,9 +846,7 @@ fn find_vdso(pid: Option<Pid>) -> 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!)
Expand All @@ -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::<X11ProtocolHandler>(&sock_path)
}
Loading