diff --git a/Cargo.lock b/Cargo.lock index 61153de..7c3c671 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -944,8 +944,9 @@ checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "libssh2-sys" -version = "0.2.21" -source = "git+https://github.com/wez/ssh2-rs.git?branch=win32ssl#c65067040c97a0cf7f96c69d6fc87764a32c34ae" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" dependencies = [ "cc", "libc", @@ -1677,8 +1678,9 @@ dependencies = [ [[package]] name = "ssh2" -version = "0.9.1" -source = "git+https://github.com/wez/ssh2-rs.git?branch=win32ssl#c65067040c97a0cf7f96c69d6fc87764a32c34ae" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269343e64430067a14937ae0e3c4ec604c178fb896dde0964b1acd22b3e2eeb1" dependencies = [ "bitflags", "libc", diff --git a/Cargo.toml b/Cargo.toml index 4c06dde..689cbaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,6 @@ opt-level = 'z' lto = true codegen-units = 1 -[patch.crates-io] -ssh2 = { git = "https://github.com/wez/ssh2-rs.git", branch="win32ssl" } - [features] default = ["ssh2"] ssh2 = ["distant-ssh2"] diff --git a/distant-core/src/client/lsp/mod.rs b/distant-core/src/client/lsp/mod.rs index ee62a12..32cad16 100644 --- a/distant-core/src/client/lsp/mod.rs +++ b/distant-core/src/client/lsp/mod.rs @@ -31,8 +31,9 @@ impl RemoteLspProcess { channel: SessionChannel, cmd: impl Into, args: Vec, + detached: bool, ) -> Result { - let mut inner = RemoteProcess::spawn(tenant, channel, cmd, args).await?; + let mut inner = RemoteProcess::spawn(tenant, channel, cmd, args, detached).await?; let stdin = inner.stdin.take().map(RemoteLspStdin::new); let stdout = inner.stdout.take().map(RemoteLspStdout::new); let stderr = inner.stderr.take().map(RemoteLspStderr::new); @@ -324,6 +325,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); diff --git a/distant-core/src/client/process.rs b/distant-core/src/client/process.rs index f089d92..fbb83b1 100644 --- a/distant-core/src/client/process.rs +++ b/distant-core/src/client/process.rs @@ -67,6 +67,7 @@ impl RemoteProcess { mut channel: SessionChannel, cmd: impl Into, args: Vec, + detached: bool, ) -> Result { let tenant = tenant.into(); let cmd = cmd.into(); @@ -75,7 +76,11 @@ impl RemoteProcess { let mut mailbox = channel .mail(Request::new( tenant.as_str(), - vec![RequestData::ProcRun { cmd, args }], + vec![RequestData::ProcRun { + cmd, + args, + detached, + }], )) .await?; @@ -365,6 +370,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -403,6 +409,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -448,6 +455,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -493,6 +501,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -549,6 +558,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -604,6 +614,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -653,6 +664,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -702,6 +714,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -744,6 +757,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); @@ -793,6 +807,7 @@ mod tests { session.clone_channel(), String::from("cmd"), vec![String::from("arg")], + false, ) .await }); diff --git a/distant-core/src/client/session/ext.rs b/distant-core/src/client/session/ext.rs index fb2701d..482602e 100644 --- a/distant-core/src/client/session/ext.rs +++ b/distant-core/src/client/session/ext.rs @@ -121,6 +121,7 @@ pub trait SessionChannelExt { tenant: impl Into, cmd: impl Into, args: Vec>, + detached: bool, ) -> AsyncReturn<'_, RemoteProcess, RemoteProcessError>; /// Spawns an LSP process on the remote machine @@ -129,6 +130,7 @@ pub trait SessionChannelExt { tenant: impl Into, cmd: impl Into, args: Vec>, + detached: bool, ) -> AsyncReturn<'_, RemoteLspProcess, RemoteProcessError>; /// Retrieves information about the remote system @@ -367,11 +369,14 @@ impl SessionChannelExt for SessionChannel { tenant: impl Into, cmd: impl Into, args: Vec>, + detached: bool, ) -> AsyncReturn<'_, RemoteProcess, RemoteProcessError> { let tenant = tenant.into(); let cmd = cmd.into(); let args = args.into_iter().map(Into::into).collect(); - Box::pin(async move { RemoteProcess::spawn(tenant, self.clone(), cmd, args).await }) + Box::pin( + async move { RemoteProcess::spawn(tenant, self.clone(), cmd, args, detached).await }, + ) } fn spawn_lsp( @@ -379,11 +384,14 @@ impl SessionChannelExt for SessionChannel { tenant: impl Into, cmd: impl Into, args: Vec>, + detached: bool, ) -> AsyncReturn<'_, RemoteLspProcess, RemoteProcessError> { let tenant = tenant.into(); let cmd = cmd.into(); let args = args.into_iter().map(Into::into).collect(); - Box::pin(async move { RemoteLspProcess::spawn(tenant, self.clone(), cmd, args).await }) + Box::pin( + async move { RemoteLspProcess::spawn(tenant, self.clone(), cmd, args, detached).await }, + ) } fn system_info(&mut self, tenant: impl Into) -> AsyncReturn<'_, SystemInfo> { diff --git a/distant-core/src/data.rs b/distant-core/src/data.rs index fd65282..2869815 100644 --- a/distant-core/src/data.rs +++ b/distant-core/src/data.rs @@ -222,6 +222,11 @@ pub enum RequestData { /// Arguments for the command args: Vec, + + /// Whether or not the process should be detached, meaning that the process will not be + /// killed when the associated client disconnects + #[cfg_attr(feature = "structopt", structopt(long))] + detached: bool, }, /// Kills a process running on the remote machine @@ -465,6 +470,9 @@ pub struct RunningProcess { /// Arguments for the command pub args: Vec, + /// Whether or not the process was run in detached mode + pub detached: bool, + /// Arbitrary id associated with running process /// /// Not the same as the process' pid! diff --git a/distant-core/src/server/distant/handler.rs b/distant-core/src/server/distant/handler.rs index 83e91c1..031104c 100644 --- a/distant-core/src/server/distant/handler.rs +++ b/distant-core/src/server/distant/handler.rs @@ -98,7 +98,11 @@ pub(super) async fn process( canonicalize, resolve_file_type, } => metadata(path, canonicalize, resolve_file_type).await, - RequestData::ProcRun { cmd, args } => proc_run(conn_id, state, reply, cmd, args).await, + RequestData::ProcRun { + cmd, + args, + detached, + } => proc_run(conn_id, state, reply, cmd, args, detached).await, RequestData::ProcKill { id } => proc_kill(state, id).await, RequestData::ProcStdin { id, data } => proc_stdin(state, id, data).await, RequestData::ProcList {} => proc_list(state).await, @@ -424,6 +428,7 @@ async fn proc_run( reply: F, cmd: String, args: Vec, + detached: bool, ) -> Result where F: FnMut(Vec) -> ReplyRet + Clone + Send + 'static, @@ -439,7 +444,7 @@ where state .lock() .await - .push_process(conn_id, Process::new(id, cmd, args)); + .push_process(conn_id, Process::new(id, cmd, args, detached)); let post_hook = Box::new(move |mut state_lock: MutexGuard<'_, State>| { // Spawn a task that sends stdout as a response @@ -656,6 +661,7 @@ async fn proc_list(state: HState) -> Result { .map(|p| RunningProcess { cmd: p.cmd.to_string(), args: p.args.clone(), + detached: p.detached, id: p.id, }) .collect(), @@ -2096,6 +2102,7 @@ mod tests { vec![RequestData::ProcRun { cmd: DOES_NOT_EXIST_BIN.to_str().unwrap().to_string(), args: Vec::new(), + detached: false, }], ); @@ -2121,6 +2128,7 @@ mod tests { vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()], + detached: false, }], ); @@ -2153,6 +2161,7 @@ mod tests { ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(), String::from("some stdout"), ], + detached: false, }], ); @@ -2218,6 +2227,7 @@ mod tests { ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(), String::from("some stderr"), ], + detached: false, }], ); @@ -2280,6 +2290,7 @@ mod tests { vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0.1")], + detached: false, }], ); @@ -2322,6 +2333,7 @@ mod tests { vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")], + detached: false, }], ); @@ -2396,6 +2408,7 @@ mod tests { vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")], + detached: false, }], ); @@ -2491,6 +2504,7 @@ mod tests { vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap().to_string()], + detached: false, }], ); @@ -2563,6 +2577,7 @@ mod tests { RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")], + detached: false, }, RequestData::ProcList {}, ], @@ -2586,6 +2601,7 @@ mod tests { entries: vec![RunningProcess { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")], + detached: false, id, }], }, diff --git a/distant-core/src/server/distant/state.rs b/distant-core/src/server/distant/state.rs index 774429d..b8a8e83 100644 --- a/distant-core/src/server/distant/state.rs +++ b/distant-core/src/server/distant/state.rs @@ -68,14 +68,22 @@ impl State { if let Some(ids) = self.client_processes.remove(&conn_id) { for id in ids { if let Some(process) = self.processes.remove(&id) { - trace!( - " Requesting proc {} be killed", - conn_id, - process.id - ); - let pid = process.id; - if !process.kill() { - error!("Conn {} failed to send process {} kill signal", id, pid); + if !process.detached { + trace!( + " Requesting proc {} be killed", + conn_id, + process.id + ); + let pid = process.id; + if !process.kill() { + error!("Conn {} failed to send process {} kill signal", id, pid); + } + } else { + trace!( + " Proc {} is detached and will not be killed", + conn_id, + process.id + ); } } } @@ -94,6 +102,9 @@ pub struct Process { /// Arguments associated with the process pub args: Vec, + /// Whether or not this process was run detached + pub detached: bool, + /// Transport channel to send new input to the stdin of the process, /// one line at a time stdin_tx: Option>, @@ -106,11 +117,12 @@ pub struct Process { } impl Process { - pub fn new(id: usize, cmd: String, args: Vec) -> Self { + pub fn new(id: usize, cmd: String, args: Vec, detached: bool) -> Self { Self { id, cmd, args, + detached, stdin_tx: None, kill_tx: None, wait_task: None, diff --git a/distant-lua/src/session.rs b/distant-lua/src/session.rs index 5320bdf..d060359 100644 --- a/distant-lua/src/session.rs +++ b/distant-lua/src/session.rs @@ -34,9 +34,7 @@ pub fn make_session_tbl(lua: &Lua) -> LuaResult { "launch", lua.create_function(|lua, opts: LuaValue| { let opts = LaunchOpts::from_lua(opts, lua)?; - let x = runtime::block_on(Session::launch(opts))?; - trace!("launch: {:?}", x); - Ok(x) + runtime::block_on(Session::launch(opts)) })?, )?; @@ -159,9 +157,8 @@ impl Session { mode, handler, ssh, + distant, timeout, - distant_bin, - distant_args, } = opts; // First, establish a connection to an SSH server @@ -177,8 +174,9 @@ impl Session { let session = match mode { Mode::Distant => ssh_session .into_distant_session(IntoDistantSessionOpts { - binary: distant_bin, - args: distant_args, + binary: distant.bin, + args: distant.args, + use_login_shell: distant.use_login_shell, timeout, }) .await diff --git a/distant-lua/src/session/api.rs b/distant-lua/src/session/api.rs index 95d9936..11e0747 100644 --- a/distant-lua/src/session/api.rs +++ b/distant-lua/src/session/api.rs @@ -164,16 +164,23 @@ make_api!( make_api!( spawn, RemoteProcess, - { cmd: String, #[serde(default)] args: Vec }, - |channel, tenant, params| { channel.spawn(tenant, params.cmd, params.args).await } + { cmd: String, #[serde(default)] args: Vec, #[serde(default)] detached: bool }, + |channel, tenant, params| { + channel.spawn(tenant, params.cmd, params.args, params.detached).await + } ); make_api!( spawn_wait, Output, - { cmd: String, #[serde(default)] args: Vec }, + { cmd: String, #[serde(default)] args: Vec, #[serde(default)] detached: bool }, |channel, tenant, params| { - let proc = channel.spawn(tenant, params.cmd, params.args).await.to_lua_err()?; + let proc = channel.spawn( + tenant, + params.cmd, + params.args, + params.detached, + ).await.to_lua_err()?; let id = LuaRemoteProcess::from_distant_async(proc).await?.id; LuaRemoteProcess::output_async(id).await } @@ -182,8 +189,10 @@ make_api!( make_api!( spawn_lsp, RemoteLspProcess, - { cmd: String, #[serde(default)] args: Vec }, - |channel, tenant, params| { channel.spawn_lsp(tenant, params.cmd, params.args).await } + { cmd: String, #[serde(default)] args: Vec, #[serde(default)] detached: bool }, + |channel, tenant, params| { + channel.spawn_lsp(tenant, params.cmd, params.args, params.detached).await + } ); make_api!(system_info, SystemInfo, |channel, tenant, _params| { diff --git a/distant-lua/src/session/opts.rs b/distant-lua/src/session/opts.rs index b7e953a..74e717c 100644 --- a/distant-lua/src/session/opts.rs +++ b/distant-lua/src/session/opts.rs @@ -92,14 +92,11 @@ pub struct LaunchOpts<'a> { /// Miscellaneous ssh configuration options pub ssh: Ssh2SessionOpts, + /// Options specific to launching the distant binary on the remote machine + pub distant: LaunchDistantOpts, + /// Maximum time to wait for launch to complete pub timeout: Duration, - - /// Binary representing the distant server on the remote machine - pub distant_bin: String, - - /// Additional CLI options to pass to the distant server when starting - pub distant_args: String, } impl fmt::Debug for LaunchOpts<'_> { @@ -109,6 +106,7 @@ impl fmt::Debug for LaunchOpts<'_> { .field("mode", &self.mode) .field("handler", &"...") .field("ssh", &self.ssh) + .field("distant", &self.distant) .field("timeout", &self.timeout) .finish() } @@ -182,25 +180,17 @@ impl<'lua> FromLua<'lua> for LaunchOpts<'lua> { None => Default::default(), } }, + distant: { + let distant_tbl: Option = tbl.get("distant")?; + match distant_tbl { + Some(value) => LaunchDistantOpts::from_lua(value, lua)?, + None => Default::default(), + } + }, timeout: { let milliseconds: Option = tbl.get("timeout")?; Duration::from_millis(milliseconds.unwrap_or(TIMEOUT_MILLIS)) }, - distant_bin: { - let distant_bin: Option = tbl.get("distant_bin")?; - distant_bin.unwrap_or_else(|| String::from("distant")) - }, - distant_args: { - let value: LuaValue = tbl.get("distant_args")?; - match value { - LuaValue::Nil => String::new(), - LuaValue::String(args) => args.to_str()?.to_string(), - x => { - let args: Vec = lua.from_value(x)?; - args.join(" ") - } - } - }, }), LuaValue::Nil => Err(LuaError::FromLuaConversionError { from: "Nil", @@ -256,6 +246,120 @@ impl<'lua> FromLua<'lua> for LaunchOpts<'lua> { } } +#[derive(Debug)] +pub struct LaunchDistantOpts { + /// Binary representing the distant server on the remote machine + pub bin: String, + + /// Additional CLI options to pass to the distant server when starting + pub args: String, + + /// If true, will run distant via `echo | $SHELL -l`, which will spawn a login shell to + /// execute distant + pub use_login_shell: bool, +} + +impl Default for LaunchDistantOpts { + /// Create default options + /// + /// * bin = "distant" + /// * args = "" + /// * use_login_shell = false + fn default() -> Self { + Self { + bin: String::from("distant"), + args: String::new(), + use_login_shell: false, + } + } +} + +impl<'lua> FromLua<'lua> for LaunchDistantOpts { + fn from_lua(lua_value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult { + let LaunchDistantOpts { + bin: default_bin, + args: default_args, + use_login_shell: default_use_login_shell, + } = Default::default(); + + match lua_value { + LuaValue::Table(tbl) => Ok(Self { + bin: { + let bin: Option = tbl.get("bin")?; + bin.unwrap_or(default_bin) + }, + + // Allows "--some --args" or {"--some", "--args"} + args: { + let value: LuaValue = tbl.get("args")?; + match value { + LuaValue::Nil => default_args, + LuaValue::String(args) => args.to_str()?.to_string(), + x => { + let args: Vec = lua.from_value(x)?; + args.join(" ") + } + } + }, + + use_login_shell: tbl + .get::<_, Option>("use_login_shell")? + .unwrap_or(default_use_login_shell), + }), + LuaValue::Nil => Err(LuaError::FromLuaConversionError { + from: "Nil", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::Boolean(_) => Err(LuaError::FromLuaConversionError { + from: "Boolean", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::LightUserData(_) => Err(LuaError::FromLuaConversionError { + from: "LightUserData", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::Integer(_) => Err(LuaError::FromLuaConversionError { + from: "Integer", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::Number(_) => Err(LuaError::FromLuaConversionError { + from: "Number", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::String(_) => Err(LuaError::FromLuaConversionError { + from: "String", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::Function(_) => Err(LuaError::FromLuaConversionError { + from: "Function", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::Thread(_) => Err(LuaError::FromLuaConversionError { + from: "Thread", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::UserData(_) => Err(LuaError::FromLuaConversionError { + from: "UserData", + to: "LaunchDistantOpts", + message: None, + }), + LuaValue::Error(_) => Err(LuaError::FromLuaConversionError { + from: "Error", + to: "LaunchDistantOpts", + message: None, + }), + } + } +} + #[derive(Copy, Clone, Debug, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Mode { diff --git a/distant-ssh2/src/handler.rs b/distant-ssh2/src/handler.rs index 2426c41..045a2a4 100644 --- a/distant-ssh2/src/handler.rs +++ b/distant-ssh2/src/handler.rs @@ -35,6 +35,7 @@ struct Process { id: usize, cmd: String, args: Vec, + detached: bool, stdin_tx: mpsc::Sender, kill_tx: mpsc::Sender<()>, } @@ -96,7 +97,11 @@ pub(super) async fn process( canonicalize, resolve_file_type, } => metadata(session, path, canonicalize, resolve_file_type).await, - RequestData::ProcRun { cmd, args } => proc_run(session, state, reply, cmd, args).await, + RequestData::ProcRun { + cmd, + args, + detached, + } => proc_run(session, state, reply, cmd, args, detached).await, RequestData::ProcKill { id } => proc_kill(session, state, id).await, RequestData::ProcStdin { id, data } => proc_stdin(session, state, id, data).await, RequestData::ProcList {} => proc_list(session, state).await, @@ -598,6 +603,7 @@ async fn proc_run( reply: F, cmd: String, args: Vec, + detached: bool, ) -> io::Result where F: FnMut(Vec) -> ReplyRet + Clone + Send + 'static, @@ -644,6 +650,7 @@ where id, cmd, args, + detached, stdin_tx, kill_tx, }, @@ -865,6 +872,7 @@ async fn proc_list(_session: WezSession, state: Arc>) -> io::Result .map(|p| RunningProcess { cmd: p.cmd.to_string(), args: p.args.clone(), + detached: p.detached, id: p.id, }) .collect(), diff --git a/distant-ssh2/src/lib.rs b/distant-ssh2/src/lib.rs index f3218ff..0794616 100644 --- a/distant-ssh2/src/lib.rs +++ b/distant-ssh2/src/lib.rs @@ -91,6 +91,10 @@ pub struct IntoDistantSessionOpts { /// Arguments to supply to the distant server when starting it pub args: String, + /// If true, launches via `echo distant listen ... | $SHELL -l`, otherwise attempts to launch + /// by directly invoking distant + pub use_login_shell: bool, + /// Timeout to use when connecting to the distant server pub timeout: Duration, } @@ -100,6 +104,7 @@ impl Default for IntoDistantSessionOpts { Self { binary: String::from("distant"), args: String::new(), + use_login_shell: false, timeout: Duration::from_secs(15), } } @@ -332,12 +337,33 @@ impl Ssh2Session { .map_err(|x| io::Error::new(io::ErrorKind::InvalidInput, x))?, ); - // Spawn distant server + // If we are using a login shell, we need to make the binary be sh + // so we can appropriately pipe into the login shell + let (bin, args) = if opts.use_login_shell { + ( + String::from("sh"), + vec![ + String::from("-c"), + shell_words::quote(&format!( + "echo {} {} | $SHELL -l", + opts.binary, + args.join(" ") + )) + .to_string(), + ], + ) + } else { + (opts.binary, args) + }; + + // Spawn distant server and detach it so that we don't kill it when the + // ssh session is closed let mut proc = session - .spawn("", opts.binary, args) + .spawn("", bin, args, true) .await .map_err(|x| io::Error::new(io::ErrorKind::Other, x))?; let mut stdout = proc.stdout.take().unwrap(); + let mut stderr = proc.stderr.take().unwrap(); let (success, code) = proc .wait() .await @@ -370,12 +396,18 @@ impl Ssh2Session { )), } } else { + let mut err = String::new(); + while let Ok(data) = stderr.read().await { + err.push_str(&data); + } + Err(io::Error::new( io::ErrorKind::Other, format!( - "Spawning distant failed: {}", + "Spawning distant failed [{}]: {}", code.map(|x| x.to_string()) - .unwrap_or_else(|| String::from("???")) + .unwrap_or_else(|| String::from("???")), + err ), )) } diff --git a/distant-ssh2/tests/ssh2/session.rs b/distant-ssh2/tests/ssh2/session.rs index 7b646dd..5ddbea2 100644 --- a/distant-ssh2/tests/ssh2/session.rs +++ b/distant-ssh2/tests/ssh2/session.rs @@ -1355,6 +1355,7 @@ async fn proc_run_should_send_error_over_stderr_on_failure(#[future] session: Se vec![RequestData::ProcRun { cmd: DOES_NOT_EXIST_BIN.to_str().unwrap().to_string(), args: Vec::new(), + detached: false, }], ); @@ -1400,6 +1401,7 @@ async fn proc_run_should_send_back_proc_start_on_success(#[future] session: Sess vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()], + detached: false, }], ); @@ -1428,6 +1430,7 @@ async fn proc_run_should_send_back_stdout_periodically_when_available(#[future] ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(), String::from("'some stdout'"), ], + detached: false, }], ); @@ -1491,6 +1494,7 @@ async fn proc_run_should_send_back_stderr_periodically_when_available(#[future] ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(), String::from("'some stderr'"), ], + detached: false, }], ); @@ -1551,6 +1555,7 @@ async fn proc_run_should_clear_process_from_state_when_done(#[future] session: S vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("0.1")], + detached: false, }], ); let mut mailbox = session.mail(req).await.unwrap(); @@ -1598,6 +1603,7 @@ async fn proc_run_should_clear_process_from_state_when_killed(#[future] session: vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")], + detached: false, }], ); @@ -1671,6 +1677,7 @@ async fn proc_kill_should_send_ok_and_done_responses_on_success(#[future] sessio vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("1")], + detached: false, }], ); @@ -1745,6 +1752,7 @@ async fn proc_stdin_should_send_ok_on_success_and_properly_send_stdin_to_process vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap().to_string()], + detached: false, }], ); let mut mailbox = session.mail(req).await.unwrap(); @@ -1793,6 +1801,7 @@ async fn proc_list_should_send_proc_entry_list(#[future] session: Session) { vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("10")], + detached: false, }], ); @@ -1817,6 +1826,7 @@ async fn proc_list_should_send_proc_entry_list(#[future] session: Session) { entries: vec![RunningProcess { cmd: SCRIPT_RUNNER.to_string(), args: vec![SLEEP_SH.to_str().unwrap().to_string(), String::from("10")], + detached: false, id, }], }, diff --git a/src/opt.rs b/src/opt.rs index f6a3f6b..6df74a7 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -659,6 +659,11 @@ pub struct LspSubcommand { #[structopt(flatten)] pub ssh_connection: SshConnectionOpts, + /// If provided, will run in detached mode, meaning that the process will not be killed if the + /// client disconnects from the server + #[structopt(long)] + pub detached: bool, + /// Command to run on the remote machine that represents an LSP server pub cmd: String, diff --git a/src/subcommand/action.rs b/src/subcommand/action.rs index a22d9c9..9d9102a 100644 --- a/src/subcommand/action.rs +++ b/src/subcommand/action.rs @@ -86,10 +86,22 @@ async fn start( match (cmd.interactive, cmd.operation) { // ProcRun request w/ shell format is specially handled and we ignore interactive as // the stdin will be used for sending ProcStdin to remote process - (_, Some(RequestData::ProcRun { cmd, args })) if is_shell_format => { - let mut proc = - RemoteProcess::spawn(utils::new_tenant(), session.clone_channel(), cmd, args) - .await?; + ( + _, + Some(RequestData::ProcRun { + cmd, + args, + detached, + }), + ) if is_shell_format => { + let mut proc = RemoteProcess::spawn( + utils::new_tenant(), + session.clone_channel(), + cmd, + args, + detached, + ) + .await?; // If we also parsed an LSP's initialize request for its session, we want to forward // it along in the case of a process call diff --git a/src/subcommand/lsp.rs b/src/subcommand/lsp.rs index 37a46d9..4601e32 100644 --- a/src/subcommand/lsp.rs +++ b/src/subcommand/lsp.rs @@ -73,6 +73,7 @@ async fn start( session.clone_channel(), cmd.cmd, cmd.args, + cmd.detached, ) .await?; diff --git a/tests/cli/action/proc_run.rs b/tests/cli/action/proc_run.rs index c55b445..284a627 100644 --- a/tests/cli/action/proc_run.rs +++ b/tests/cli/action/proc_run.rs @@ -171,6 +171,7 @@ fn should_support_json_to_execute_program_and_return_exit_status(mut action_cmd: payload: vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string()], + detached: false, }], }; @@ -203,6 +204,7 @@ fn should_support_json_to_capture_and_print_stdout(ctx: &'_ DistantServerCtx) { ECHO_ARGS_TO_STDOUT_SH.to_str().unwrap().to_string(), output.to_string(), ], + detached: false, }], }; @@ -271,6 +273,7 @@ fn should_support_json_to_capture_and_print_stderr(ctx: &'_ DistantServerCtx) { ECHO_ARGS_TO_STDERR_SH.to_str().unwrap().to_string(), output.to_string(), ], + detached: false, }], }; @@ -335,6 +338,7 @@ fn should_support_json_to_forward_stdin_to_remote_process(ctx: &'_ DistantServer payload: vec![RequestData::ProcRun { cmd: SCRIPT_RUNNER.to_string(), args: vec![ECHO_STDIN_TO_STDOUT_SH.to_str().unwrap().to_string()], + detached: false, }], }; @@ -428,6 +432,7 @@ fn should_support_json_output_for_error(mut action_cmd: Command) { payload: vec![RequestData::ProcRun { cmd: DOES_NOT_EXIST_BIN.to_str().unwrap().to_string(), args: Vec::new(), + detached: false, }], };