Skip to content

Commit a04c1ba

Browse files
WhatAmISupposedToPutHereslp
authored andcommitted
Add interactive launch mode.
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]>
1 parent e227614 commit a04c1ba

3 files changed

Lines changed: 79 additions & 5 deletions

File tree

crates/muvm/src/bin/muvm.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use muvm::cli_options::options;
1616
use muvm::cpu::{get_fallback_cores, get_performance_cores};
1717
use muvm::env::{find_muvm_exec, prepare_env_vars};
1818
use muvm::hidpipe_server::spawn_hidpipe_server;
19-
use muvm::launch::{launch_or_lock, LaunchResult};
19+
use muvm::launch::{launch_or_lock, LaunchResult, DYNAMIC_PORT_RANGE};
2020
use muvm::monitor::spawn_monitor;
2121
use muvm::net::{connect_to_passt, start_passt};
2222
use muvm::types::MiB;
@@ -74,6 +74,7 @@ fn main() -> Result<()> {
7474
options.command,
7575
options.command_args,
7676
options.env,
77+
options.interactive,
7778
)? {
7879
LaunchResult::LaunchRequested => {
7980
// There was a muvm instance already running and we've requested it
@@ -317,7 +318,7 @@ fn main() -> Result<()> {
317318
let socket_dir = Path::new(&run_path).join("krun/socket");
318319
std::fs::create_dir_all(&socket_dir)?;
319320
// Dynamic ports: Applications may listen on these sockets as neeeded.
320-
for port in 50000..50200 {
321+
for port in DYNAMIC_PORT_RANGE {
321322
let socket_path = socket_dir.join(format!("port-{}", port));
322323
let socket_path = CString::new(
323324
socket_path

crates/muvm/src/cli_options.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub struct Options {
1717
pub server_port: u32,
1818
pub fex_images: Vec<String>,
1919
pub direct_x11: bool,
20+
pub interactive: bool,
2021
pub command: PathBuf,
2122
pub command_args: Vec<String>,
2223
}
@@ -116,7 +117,10 @@ pub fn options() -> OptionParser<Options> {
116117
"--direct-x11 requires the `x11bridge` crate feature",
117118
)
118119
.hide();
119-
120+
let interactive = long("interactive")
121+
.short('i')
122+
.help("Allocate a tty guest-side and connect it to the current stdin/out")
123+
.switch();
120124
let command = positional("COMMAND").help("the command you want to execute in the vm");
121125
let command_args = any::<String, _, _>("COMMAND_ARGS", |arg| {
122126
(!["--help", "-h"].contains(&&*arg)).then_some(arg)
@@ -134,6 +138,7 @@ pub fn options() -> OptionParser<Options> {
134138
server_port,
135139
fex_images,
136140
direct_x11,
141+
interactive,
137142
// positionals
138143
command,
139144
command_args,

crates/muvm/src/launch.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ use anyhow::{anyhow, Context, Result};
1111
use rustix::fs::{flock, FlockOperation};
1212
use uuid::Uuid;
1313

14+
use super::utils::env::find_in_path;
1415
use crate::env::prepare_env_vars;
1516
use crate::utils::launch::Launch;
17+
use rustix::path::Arg;
18+
use std::ops::Range;
19+
use std::process::{Child, Command};
20+
21+
pub const DYNAMIC_PORT_RANGE: Range<u32> = 50000..50200;
1622

1723
pub enum LaunchResult {
1824
LaunchRequested,
@@ -50,18 +56,79 @@ impl Display for LaunchError {
5056
}
5157
}
5258

59+
fn start_socat() -> Result<(Child, u32)> {
60+
let run_path = env::var("XDG_RUNTIME_DIR")
61+
.map_err(|e| anyhow!("unable to get XDG_RUNTIME_DIR: {:?}", e))?;
62+
let socket_dir = Path::new(&run_path).join("krun/socket");
63+
let socat_path =
64+
find_in_path("socat")?.ok_or_else(|| anyhow!("Unable to find socat in PATH"))?;
65+
for port in DYNAMIC_PORT_RANGE {
66+
let path = socket_dir.join(format!("port-{}", port));
67+
if path.exists() {
68+
continue;
69+
}
70+
let child = Command::new(&socat_path)
71+
.arg(format!("unix-l:{}", path.as_os_str().to_string_lossy()))
72+
.arg("-,raw,echo=0")
73+
.spawn()?;
74+
return Ok((child, port));
75+
}
76+
Err(anyhow!("Ran out of ports."))
77+
}
78+
79+
fn escape_for_socat(s: String) -> String {
80+
let mut ret = String::with_capacity(s.len());
81+
for c in s.chars() {
82+
match c {
83+
':' | ',' | '!' | '"' | '\'' | '\\' | '(' | '[' | '{' => {
84+
ret.push('\\');
85+
},
86+
_ => {},
87+
}
88+
ret.push(c);
89+
}
90+
ret
91+
}
92+
93+
fn wrapped_launch(
94+
server_port: u32,
95+
cookie: Uuid,
96+
mut command: PathBuf,
97+
mut command_args: Vec<String>,
98+
env: HashMap<String, String>,
99+
interactive: bool,
100+
) -> Result<()> {
101+
if !interactive {
102+
return request_launch(server_port, cookie, command, command_args, env);
103+
}
104+
let (mut socat, vsock_port) = start_socat()?;
105+
command_args.insert(0, command.to_string_lossy().into_owned());
106+
command_args = vec![
107+
format!("vsock:2:{}", vsock_port),
108+
format!(
109+
"exec:{},pty,setsid,stderr",
110+
escape_for_socat(command_args.join(" "))
111+
),
112+
];
113+
command = "socat".into();
114+
request_launch(server_port, cookie, command, command_args, env)?;
115+
socat.wait()?;
116+
Ok(())
117+
}
118+
53119
pub fn launch_or_lock(
54120
server_port: u32,
55121
command: PathBuf,
56122
command_args: Vec<String>,
57123
env: Vec<(String, Option<String>)>,
124+
interactive: bool,
58125
) -> Result<LaunchResult> {
59126
let running_server_port = env::var("MUVM_SERVER_PORT").ok();
60127
if let Some(port) = running_server_port {
61128
let port: u32 = port.parse()?;
62129
let env = prepare_env_vars(env)?;
63130
let cookie = read_cookie()?;
64-
if let Err(err) = request_launch(port, cookie, command, command_args, env) {
131+
if let Err(err) = wrapped_launch(port, cookie, command, command_args, env, interactive) {
65132
return Err(anyhow!("could not request launch to server: {err}"));
66133
}
67134
return Ok(LaunchResult::LaunchRequested);
@@ -80,12 +147,13 @@ pub fn launch_or_lock(
80147
let env = prepare_env_vars(env)?;
81148
let mut tries = 0;
82149
loop {
83-
match request_launch(
150+
match wrapped_launch(
84151
server_port,
85152
cookie,
86153
command.clone(),
87154
command_args.clone(),
88155
env.clone(),
156+
interactive,
89157
) {
90158
Err(err) => match err.downcast_ref::<LaunchError>() {
91159
Some(&LaunchError::Connection(_)) => {

0 commit comments

Comments
 (0)