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 <fnkl.kernel@gmail.com>
  • Loading branch information
Sasha Finkelstein authored and alyssarosenzweig committed Sep 23, 2024
1 parent 41b0e44 commit e1feb6b
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 5 deletions.
5 changes: 3 additions & 2 deletions crates/krun/src/bin/krun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
use krun::cli_options::options;
use krun::cpu::{get_fallback_cores, get_performance_cores};
use krun::env::{find_krun_exec, prepare_env_vars};
use krun::launch::{launch_or_lock, LaunchResult};
use krun::launch::{DYNAMIC_PORT_RANGE, launch_or_lock, LaunchResult};
use krun::net::{connect_to_passt, start_passt};
use krun::types::MiB;
use krun_sys::{
Expand Down Expand Up @@ -39,6 +39,7 @@ fn main() -> Result<()> {
options.command,
options.command_args,
options.env,
options.interactive,
)? {
LaunchResult::LaunchRequested => {
// There was a krun instance already running and we've requested it
Expand Down Expand Up @@ -199,7 +200,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
6 changes: 6 additions & 0 deletions crates/krun/src/cli_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct Options {
pub mem: Option<MiB>,
pub passt_socket: Option<PathBuf>,
pub server_port: u32,
pub interactive: bool,
pub command: PathBuf,
pub command_args: Vec<String>,
}
Expand Down Expand Up @@ -76,6 +77,10 @@ pub fn options() -> OptionParser<Options> {
.argument("SERVER_PORT")
.fallback(3334)
.display_fallback();
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 @@ -89,6 +94,7 @@ pub fn options() -> OptionParser<Options> {
mem,
passt_socket,
server_port,
interactive,
// positionals
command,
command_args,
Expand Down
67 changes: 64 additions & 3 deletions crates/krun/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context, Result};
use rustix::fs::{flock, FlockOperation};
use rustix::path::Arg;

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

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

pub enum LaunchResult {
LaunchRequested,
Expand Down Expand Up @@ -49,17 +53,74 @@ 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,
mut command: PathBuf,
mut command_args: Vec<String>,
env: HashMap<String, String>,
interactive: bool
) -> Result<()> {
if !interactive {
return request_launch(server_port, 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, 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("KRUN_SERVER_PORT").ok();
if let Some(port) = running_server_port {
let port: u32 = port.parse()?;
let env = prepare_env_vars(env)?;
if let Err(err) = request_launch(port, command, command_args, env) {
if let Err(err) = wrapped_launch(port, command, command_args, env, interactive) {
return Err(anyhow!("could not request launch to server: {err}"));
}
return Ok(LaunchResult::LaunchRequested);
Expand All @@ -78,7 +139,7 @@ pub fn launch_or_lock(
let env = prepare_env_vars(env)?;
let mut tries = 0;
loop {
match request_launch(port, command.clone(), command_args.clone(), env.clone()) {
match wrapped_launch(port, command.clone(), command_args.clone(), env.clone(), interactive) {
Err(err) => match err.downcast_ref::<LaunchError>() {
Some(&LaunchError::Connection(_)) => {
if tries == 3 {
Expand Down

0 comments on commit e1feb6b

Please sign in to comment.