Skip to content
Merged
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
``` sh
Usage: muvm [-c=CPU_LIST]... [-e=ENV]... [--mem=MEM] [--vram=VRAM] [--passt-socket=PATH] [-f=
FEX_IMAGE]... [-m] [-i] [-t] [--privileged] [-p=<[[IP:][HOST_PORT]:]GUEST_PORT[/PROTOCOL]>]... [
--emu=EMU] COMMAND [COMMAND_ARGS]...
--emu=EMU] [-x=COMMAND]... COMMAND [COMMAND_ARGS]...

Available positional items:
COMMAND the command you want to execute in the vm
Expand Down Expand Up @@ -54,6 +54,9 @@ Available options:
Valid options are "box" and "fex". If this argument is not
present, muvm will try to use FEX, falling back to Box if it
can't be found.
-x, --execute-pre=COMMAND Command to run inside the VM before guest server starts.
Can be used for e.g. setting up additional mounts.
Can be specified multiple times.
-h, --help Prints help information
```

Expand Down
20 changes: 15 additions & 5 deletions crates/muvm/src/bin/muvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use krun_sys::{
};
use log::debug;
use muvm::cli_options::options;
use muvm::config::Configuration;
use muvm::cpu::{get_fallback_cores, get_performance_cores};
use muvm::env::{find_muvm_exec, prepare_env_vars};
use muvm::hidpipe_server::spawn_hidpipe_server;
Expand Down Expand Up @@ -73,6 +74,12 @@ fn main() -> Result<ExitCode> {
}

let options = options().fallback_to_usage().run();
let config = Configuration::parse_config_file()?;

let mut init_commands = options.init_commands;
if let Some(init_command) = config.execute_pre {
init_commands.insert(0, init_command);
}

let (_lock_file, command, command_args, env) = match launch_or_lock(
options.command,
Expand Down Expand Up @@ -346,11 +353,12 @@ fn main() -> Result<ExitCode> {
}
}

let username = env::var("USER").context("Failed to get username from environment")?;
let user = User::from_name(&username)
let uid = getuid().as_raw();
let user = User::from_uid(uid.into())
.map_err(Into::into)
.and_then(|user| user.ok_or_else(|| anyhow!("requested entry not found")))
.with_context(|| format!("Failed to get user `{username}` from user database"))?;
.with_context(|| format!("Failed to get user `{uid}` from user database"))?;

let workdir_path = CString::new(
user.dir
.to_str()
Expand Down Expand Up @@ -379,6 +387,7 @@ fn main() -> Result<ExitCode> {

let muvm_guest_path = find_muvm_exec("muvm-guest")?;

let cwd = env::current_dir()?;
let display = env::var("DISPLAY").ok();
let guest_config = GuestConfiguration {
command: Launch {
Expand All @@ -389,12 +398,13 @@ fn main() -> Result<ExitCode> {
tty: false,
privileged: false,
},
username,
uid: getuid().as_raw(),
uid,
gid: getgid().as_raw(),
host_display: display,
merged_rootfs: options.merged_rootfs,
emulator: options.emulator,
cwd,
init_commands,
};
let mut muvm_config_file = NamedTempFile::new()
.context("Failed to create a temporary file to store the muvm guest config")?;
Expand Down
11 changes: 11 additions & 0 deletions crates/muvm/src/cli_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct Options {
pub privileged: bool,
pub publish_ports: Vec<String>,
pub emulator: Option<Emulator>,
pub init_commands: Vec<PathBuf>,
pub command: PathBuf,
pub command_args: Vec<String>,
}
Expand Down Expand Up @@ -132,6 +133,15 @@ pub fn options() -> OptionParser<Options> {
)
.argument::<String>("[[IP:][HOST_PORT]:]GUEST_PORT[/PROTOCOL]")
.many();
let init_commands = long("execute-pre")
.short('x')
.help(
"Command to run inside the VM before guest server starts.
Can be used for e.g. setting up additional mounts.
Can be specified multiple times.",
)
.argument("COMMAND")
.many();
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 @@ -152,6 +162,7 @@ pub fn options() -> OptionParser<Options> {
privileged,
publish_ports,
emulator,
init_commands,
// positionals
command,
command_args,
Expand Down
73 changes: 73 additions & 0 deletions crates/muvm/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::{
env::{self, VarError},
io,
path::{Path, PathBuf},
};

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Default)]
pub struct Configuration {
pub execute_pre: Option<PathBuf>,
}

fn get_var_if_exists(name: &str) -> Option<Result<String>> {
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<PathBuf> {
let mut path = get_var_if_exists("XDG_CONFIG_DIR").map_or_else(
|| {
let base = env::var("HOME").context("Failed to get HOME")?;
let mut base = PathBuf::from(base);
base.push(".config");
Ok(base)
},
|p| p.map(PathBuf::from),
)?;
path.push("muvm");
path.push("config.json");
Ok(path)
}

fn get_global_config_path() -> Result<PathBuf> {
Ok(PathBuf::from("/etc/muvm/config.json"))
}

fn get_system_config_path() -> Result<PathBuf> {
Ok(PathBuf::from("/usr/lib/muvm/config.json"))
}

const CONFIG_PATHS: &[fn() -> Result<PathBuf>] = &[
get_user_config_path,
get_global_config_path,
get_system_config_path,
];

fn read_to_string_if_exists(p: &Path) -> Option<Result<String>> {
match std::fs::read_to_string(p) {
Ok(content) => Some(Ok(content)),
Err(err) if err.kind() == io::ErrorKind::NotFound => None,
Err(e) => Some(Err(e.into())),
}
}

impl Configuration {
pub fn parse_config_file() -> Result<Self> {
let Some(content) = CONFIG_PATHS.iter().find_map(|get_config_path| {
let config_path = match get_config_path() {
Ok(path) => path,
Err(e) => return Some(Err(e)),
};
read_to_string_if_exists(&config_path)
}) else {
return Ok(Default::default());
};
Ok(serde_json::from_str(&content?)?)
}
}
18 changes: 12 additions & 6 deletions crates/muvm/src/guest/bin/muvm-guest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::panic::catch_unwind;
use std::process::Command;
use std::{cmp, env, fs, thread};

use anyhow::{Context, Result};
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;
Expand Down Expand Up @@ -94,13 +94,19 @@ fn main() -> Result<()> {
}
}

for init_command in options.init_commands {
let code = Command::new(&init_command)
.current_dir(&options.cwd)
.spawn()?
.wait()?;
if !code.success() {
return Err(anyhow!("Executing `{}` failed", init_command.display()));
}
}

configure_network()?;

let run_path = match setup_user(
options.username,
Uid::from(options.uid),
Gid::from(options.gid),
) {
let run_path = match setup_user(Uid::from(options.uid), Gid::from(options.gid)) {
Ok(p) => p,
Err(err) => return Err(err).context("Failed to set up user, bailing out"),
};
Expand Down
6 changes: 3 additions & 3 deletions crates/muvm/src/guest/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use anyhow::{anyhow, Context, Result};
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::{fork, setresgid, setresuid, ForkResult, Gid, Uid, User};

pub fn setup_user(username: String, uid: Uid, gid: Gid) -> Result<PathBuf> {
pub fn setup_user(uid: Uid, gid: Gid) -> Result<PathBuf> {
setup_directories(uid, gid)?;

setresgid(gid, gid, Gid::from(0)).context("Failed to setgid")?;
Expand All @@ -24,10 +24,10 @@ pub fn setup_user(username: String, uid: Uid, gid: Gid) -> Result<PathBuf> {
// See https://doc.rust-lang.org/std/env/fn.set_var.html#safety
env::set_var("XDG_RUNTIME_DIR", &path);

let user = User::from_name(&username)
let user = User::from_uid(uid)
.map_err(Into::into)
.and_then(|user| user.ok_or_else(|| anyhow!("requested entry not found")))
.with_context(|| format!("Failed to get user `{username}` from user database"))?;
.with_context(|| format!("Failed to get user `{uid}` from user database"))?;

{
// SAFETY: Safe if and only if `muvm-guest` program is not multithreaded.
Expand Down
1 change: 1 addition & 0 deletions crates/muvm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod cli_options;
pub mod config;
pub mod cpu;
pub mod env;
pub mod hidpipe_common;
Expand Down
3 changes: 2 additions & 1 deletion crates/muvm/src/utils/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ impl FromStr for Emulator {
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct GuestConfiguration {
pub command: Launch,
pub username: String,
pub uid: u32,
pub gid: u32,
pub host_display: Option<String>,
pub merged_rootfs: bool,
pub emulator: Option<Emulator>,
pub cwd: PathBuf,
pub init_commands: Vec<PathBuf>,
}

pub const PULSE_SOCKET: u32 = 3333;
Expand Down
Loading