Skip to content

Commit

Permalink
Add interactive launch mode.
Browse files Browse the repository at this point in the history
After launching a process via the server, krun immediately closes and the
launched process' std{in,out,err} go nowhere. Add a --interactive flag that
allocates a pseudo-terminal, routes it to the host and connects to it.

Somewhat like '-it' in docker/podman exec.

Signed-off-by: Sasha Finkelstein <[email protected]>
  • Loading branch information
WhatAmISupposedToPutHere authored and slp committed Dec 3, 2024
1 parent e227614 commit a04c1ba
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 5 deletions.
5 changes: 3 additions & 2 deletions crates/muvm/src/bin/muvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use muvm::cli_options::options;
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;
use muvm::launch::{launch_or_lock, LaunchResult};
use muvm::launch::{launch_or_lock, LaunchResult, DYNAMIC_PORT_RANGE};
use muvm::monitor::spawn_monitor;
use muvm::net::{connect_to_passt, start_passt};
use muvm::types::MiB;
Expand Down Expand Up @@ -74,6 +74,7 @@ fn main() -> Result<()> {
options.command,
options.command_args,
options.env,
options.interactive,
)? {
LaunchResult::LaunchRequested => {
// There was a muvm instance already running and we've requested it
Expand Down Expand Up @@ -317,7 +318,7 @@ fn main() -> Result<()> {
let socket_dir = Path::new(&run_path).join("krun/socket");
std::fs::create_dir_all(&socket_dir)?;
// Dynamic ports: Applications may listen on these sockets as neeeded.
for port in 50000..50200 {
for port in DYNAMIC_PORT_RANGE {
let socket_path = socket_dir.join(format!("port-{}", port));
let socket_path = CString::new(
socket_path
Expand Down
7 changes: 6 additions & 1 deletion crates/muvm/src/cli_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct Options {
pub server_port: u32,
pub fex_images: Vec<String>,
pub direct_x11: bool,
pub interactive: bool,
pub command: PathBuf,
pub command_args: Vec<String>,
}
Expand Down Expand Up @@ -116,7 +117,10 @@ pub fn options() -> OptionParser<Options> {
"--direct-x11 requires the `x11bridge` crate feature",
)
.hide();

let interactive = long("interactive")
.short('i')
.help("Allocate a tty guest-side and connect it to the current stdin/out")
.switch();
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 @@ -134,6 +138,7 @@ pub fn options() -> OptionParser<Options> {
server_port,
fex_images,
direct_x11,
interactive,
// positionals
command,
command_args,
Expand Down
72 changes: 70 additions & 2 deletions crates/muvm/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ use anyhow::{anyhow, Context, Result};
use rustix::fs::{flock, FlockOperation};
use uuid::Uuid;

use super::utils::env::find_in_path;
use crate::env::prepare_env_vars;
use crate::utils::launch::Launch;
use rustix::path::Arg;
use std::ops::Range;
use std::process::{Child, Command};

pub const DYNAMIC_PORT_RANGE: Range<u32> = 50000..50200;

pub enum LaunchResult {
LaunchRequested,
Expand Down Expand Up @@ -50,18 +56,79 @@ impl Display for LaunchError {
}
}

fn start_socat() -> Result<(Child, u32)> {
let run_path = env::var("XDG_RUNTIME_DIR")
.map_err(|e| anyhow!("unable to get XDG_RUNTIME_DIR: {:?}", e))?;
let socket_dir = Path::new(&run_path).join("krun/socket");
let socat_path =
find_in_path("socat")?.ok_or_else(|| anyhow!("Unable to find socat in PATH"))?;
for port in DYNAMIC_PORT_RANGE {
let path = socket_dir.join(format!("port-{}", port));
if path.exists() {
continue;
}
let child = Command::new(&socat_path)
.arg(format!("unix-l:{}", path.as_os_str().to_string_lossy()))
.arg("-,raw,echo=0")
.spawn()?;
return Ok((child, port));
}
Err(anyhow!("Ran out of ports."))
}

fn escape_for_socat(s: String) -> String {
let mut ret = String::with_capacity(s.len());
for c in s.chars() {
match c {
':' | ',' | '!' | '"' | '\'' | '\\' | '(' | '[' | '{' => {
ret.push('\\');
},
_ => {},
}
ret.push(c);
}
ret
}

fn wrapped_launch(
server_port: u32,
cookie: Uuid,
mut command: PathBuf,
mut command_args: Vec<String>,
env: HashMap<String, String>,
interactive: bool,
) -> Result<()> {
if !interactive {
return request_launch(server_port, cookie, command, command_args, env);
}
let (mut socat, vsock_port) = start_socat()?;
command_args.insert(0, command.to_string_lossy().into_owned());
command_args = vec![
format!("vsock:2:{}", vsock_port),
format!(
"exec:{},pty,setsid,stderr",
escape_for_socat(command_args.join(" "))
),
];
command = "socat".into();
request_launch(server_port, cookie, command, command_args, env)?;
socat.wait()?;
Ok(())
}

pub fn launch_or_lock(
server_port: u32,
command: PathBuf,
command_args: Vec<String>,
env: Vec<(String, Option<String>)>,
interactive: bool,
) -> Result<LaunchResult> {
let running_server_port = env::var("MUVM_SERVER_PORT").ok();
if let Some(port) = running_server_port {
let port: u32 = port.parse()?;
let env = prepare_env_vars(env)?;
let cookie = read_cookie()?;
if let Err(err) = request_launch(port, cookie, command, command_args, env) {
if let Err(err) = wrapped_launch(port, cookie, command, command_args, env, interactive) {
return Err(anyhow!("could not request launch to server: {err}"));
}
return Ok(LaunchResult::LaunchRequested);
Expand All @@ -80,12 +147,13 @@ pub fn launch_or_lock(
let env = prepare_env_vars(env)?;
let mut tries = 0;
loop {
match request_launch(
match wrapped_launch(
server_port,
cookie,
command.clone(),
command_args.clone(),
env.clone(),
interactive,
) {
Err(err) => match err.downcast_ref::<LaunchError>() {
Some(&LaunchError::Connection(_)) => {
Expand Down

0 comments on commit a04c1ba

Please sign in to comment.