diff --git a/Cargo.lock b/Cargo.lock index dd602f4e9708491c8ab37d1374da9bd06193a5f5..c70d8e7da7c504d5e84277d29798a7d11a640136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13007,7 +13007,6 @@ dependencies = [ "fs", "futures 0.3.31", "gpui", - "itertools 0.14.0", "log", "parking_lot", "paths", diff --git a/crates/remote/Cargo.toml b/crates/remote/Cargo.toml index 545eb8d43dc445732e20615d4f8aad9b927531f4..0b2bf4e2fccef3ed1ccd175eda8bde13b179f696 100644 --- a/crates/remote/Cargo.toml +++ b/crates/remote/Cargo.toml @@ -25,7 +25,6 @@ collections.workspace = true fs.workspace = true futures.workspace = true gpui.workspace = true -itertools.workspace = true log.workspace = true parking_lot.workspace = true paths.workspace = true diff --git a/crates/remote/src/remote_client.rs b/crates/remote/src/remote_client.rs index fc30767c73f94d8dc2ac647b275bc85ba3d5e760..8900d04ae56ebce93164d0840efd13c9f70d4d72 100644 --- a/crates/remote/src/remote_client.rs +++ b/crates/remote/src/remote_client.rs @@ -22,7 +22,7 @@ use futures::{ }; use gpui::{ App, AppContext as _, AsyncApp, BackgroundExecutor, BorrowAppContext, Context, Entity, - EventEmitter, Global, SemanticVersion, Task, WeakEntity, + EventEmitter, FutureExt, Global, SemanticVersion, Task, WeakEntity, }; use parking_lot::Mutex; @@ -356,26 +356,43 @@ impl RemoteClient { cx, ); - let multiplex_task = Self::monitor(this.downgrade(), io_task, cx); - - let timeout = cx.background_executor().timer(HEARTBEAT_TIMEOUT).fuse(); - futures::pin_mut!(timeout); - - select_biased! { - ready = client.wait_for_remote_started() => { - if ready.is_none() { - let error = anyhow::anyhow!("remote client exited before becoming ready"); - log::error!("failed to establish connection: {}", error); - return Err(error); + let ready = client + .wait_for_remote_started() + .with_timeout(HEARTBEAT_TIMEOUT, cx.background_executor()) + .await; + match ready { + Ok(Some(_)) => {} + Ok(None) => { + let mut error = "remote client exited before becoming ready".to_owned(); + if let Some(status) = io_task.now_or_never() { + match status { + Ok(exit_code) => { + error.push_str(&format!(", exit_code={exit_code:?}")) + } + Err(e) => error.push_str(&format!(", error={e:?}")), + } + } + let error = anyhow::anyhow!("{error}"); + log::error!("failed to establish connection: {}", error); + return Err(error); + } + Err(_) => { + let mut error = + "remote client did not become ready within the timeout".to_owned(); + if let Some(status) = io_task.now_or_never() { + match status { + Ok(exit_code) => { + error.push_str(&format!(", exit_code={exit_code:?}")) + } + Err(e) => error.push_str(&format!(", error={e:?}")), + } } - }, - _ = timeout => { - let error = anyhow::anyhow!("remote client did not become ready within the timeout"); + let error = anyhow::anyhow!("{error}"); log::error!("failed to establish connection: {}", error); return Err(error); } } - + let multiplex_task = Self::monitor(this.downgrade(), io_task, cx); if let Err(error) = client.ping(HEARTBEAT_TIMEOUT).await { log::error!("failed to establish connection: {}", error); return Err(error); diff --git a/crates/remote/src/transport.rs b/crates/remote/src/transport.rs index 9a2b13209cb2db3f36e266351b181c0f1d5397c5..62144d565348b65a5ef242124277a8325e77d1a7 100644 --- a/crates/remote/src/transport.rs +++ b/crates/remote/src/transport.rs @@ -107,7 +107,6 @@ fn handle_rpc_messages_over_child_process_stdio( result.context("stderr") } }; - let status = ssh_proxy_process.status().await?.code().unwrap_or(1); match result { Ok(_) => Ok(status), diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index 616cf3810658c4efc25cc4ae348ff73873bd664f..6e48c0d97da465af4046c514bf1d717b1e08f689 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -11,7 +11,6 @@ use futures::{ select_biased, }; use gpui::{App, AppContext as _, AsyncApp, SemanticVersion, Task}; -use itertools::Itertools; use parking_lot::Mutex; use paths::remote_server_dir_relative; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; @@ -22,7 +21,6 @@ use smol::{ process::{self, Child, Stdio}, }; use std::{ - iter, path::{Path, PathBuf}, sync::Arc, time::Instant, @@ -206,30 +204,24 @@ impl RemoteConnection for SshRemoteConnection { return Task::ready(Err(anyhow!("Remote binary path not set"))); }; - let mut start_proxy_command = shell_script!( - "exec {binary_path} proxy --identifier {identifier}", - binary_path = &remote_binary_path.display(self.path_style()), - identifier = &unique_identifier, - ); - + let mut proxy_args = vec![]; for env_var in ["RUST_LOG", "RUST_BACKTRACE", "ZED_GENERATE_MINIDUMPS"] { if let Some(value) = std::env::var(env_var).ok() { - start_proxy_command = format!( - "{}={} {} ", - env_var, - shlex::try_quote(&value).unwrap(), - start_proxy_command, - ); + proxy_args.push(format!("{}='{}'", env_var, value)); } } + proxy_args.push(remote_binary_path.display(self.path_style()).into_owned()); + proxy_args.push("proxy".to_owned()); + proxy_args.push("--identifier".to_owned()); + proxy_args.push(unique_identifier); if reconnect { - start_proxy_command.push_str(" --reconnect"); + proxy_args.push("--reconnect".to_owned()); } let ssh_proxy_process = match self .socket - .ssh_command("sh", &["-c", &start_proxy_command]) + .ssh_command("env", &proxy_args) // IMPORTANT: we kill this process when we drop the task that uses it. .kill_on_drop(true) .spawn() @@ -718,24 +710,23 @@ impl SshSocket { // Furthermore, some setups (e.g. Coder) will change directory when SSH'ing // into a machine. You must use `cd` to get back to $HOME. // You need to do it like this: $ ssh host "cd; sh -c 'ls -l /tmp'" - fn ssh_command(&self, program: &str, args: &[&str]) -> process::Command { + fn ssh_command(&self, program: &str, args: &[impl AsRef]) -> process::Command { let mut command = util::command::new_smol_command("ssh"); - let to_run = iter::once(&program) - .chain(args.iter()) - .map(|token| { - // We're trying to work with: sh, bash, zsh, fish, tcsh, ...? - debug_assert!( - !token.contains('\n'), - "multiline arguments do not work in all shells" - ); - shlex::try_quote(token).unwrap() - }) - .join(" "); + let mut to_run = shlex::try_quote(program).unwrap().into_owned(); + for arg in args { + // We're trying to work with: sh, bash, zsh, fish, tcsh, ...? + debug_assert!( + !arg.as_ref().contains('\n'), + "multiline arguments do not work in all shells" + ); + to_run.push(' '); + to_run.push_str(&shlex::try_quote(arg.as_ref()).unwrap()); + } let to_run = format!("cd; {to_run}"); - log::debug!("ssh {} {:?}", self.connection_options.ssh_url(), to_run); self.ssh_options(&mut command) .arg(self.connection_options.ssh_url()) .arg(to_run); + log::debug!("ssh {:?}", command); command } diff --git a/crates/remote/src/transport/wsl.rs b/crates/remote/src/transport/wsl.rs index 2096c25fa461df723c837314072b55fda6b3c988..3f72055cd11a9ce5b2a60670b0f66431e44e96a4 100644 --- a/crates/remote/src/transport/wsl.rs +++ b/crates/remote/src/transport/wsl.rs @@ -11,6 +11,7 @@ use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use rpc::proto::Envelope; use smol::{fs, process}; use std::{ + ffi::OsStr, fmt::Write as _, path::{Path, PathBuf}, process::Stdio, @@ -110,7 +111,7 @@ impl WslRemoteConnection { windows_path_to_wsl_path_impl(&self.connection_options, source).await } - fn wsl_command(&self, program: &str, args: &[&str]) -> process::Command { + fn wsl_command(&self, program: &str, args: &[impl AsRef]) -> process::Command { wsl_command_impl(&self.connection_options, program, args) } @@ -297,23 +298,22 @@ impl RemoteConnection for WslRemoteConnection { return Task::ready(Err(anyhow!("Remote binary path not set"))); }; - let mut proxy_command = format!( - "exec {} proxy --identifier {}", - remote_binary_path.display(PathStyle::Posix), - unique_identifier - ); - - if reconnect { - proxy_command.push_str(" --reconnect"); - } - + let mut proxy_args = vec![]; for env_var in ["RUST_LOG", "RUST_BACKTRACE", "ZED_GENERATE_MINIDUMPS"] { if let Some(value) = std::env::var(env_var).ok() { - proxy_command = format!("{}='{}' {}", env_var, value, proxy_command); + proxy_args.push(format!("{}='{}'", env_var, value)); } } + proxy_args.push(remote_binary_path.display(PathStyle::Posix).into_owned()); + proxy_args.push("proxy".to_owned()); + proxy_args.push("--identifier".to_owned()); + proxy_args.push(unique_identifier); + + if reconnect { + proxy_args.push("--reconnect".to_owned()); + } let proxy_process = match self - .wsl_command("sh", &["-lc", &proxy_command]) + .wsl_command("env", &proxy_args) .kill_on_drop(true) .spawn() { @@ -387,22 +387,22 @@ impl RemoteConnection for WslRemoteConnection { .map(|working_dir| RemotePathBuf::new(working_dir, PathStyle::Posix).to_string()) .unwrap_or("~".to_string()); - let mut script = String::new(); + let mut exec = String::from("exec env "); for (k, v) in env.iter() { - write!(&mut script, "{}='{}' ", k, v).unwrap(); + if let Some((k, v)) = shlex::try_quote(k).ok().zip(shlex::try_quote(v).ok()) { + write!(exec, "{}={} ", k, v).unwrap(); + } } if let Some(program) = program { - let command = shlex::try_quote(&program)?; - script.push_str(&command); + write!(exec, "{}", shlex::try_quote(&program)?).unwrap(); for arg in args { let arg = shlex::try_quote(&arg)?; - script.push_str(" "); - script.push_str(&arg); + write!(exec, " {}", &arg).unwrap(); } } else { - write!(&mut script, "exec {} -l", self.shell).unwrap(); + write!(&mut exec, "{} -l", self.shell).unwrap(); } let wsl_args = if let Some(user) = &self.connection_options.user { @@ -416,7 +416,7 @@ impl RemoteConnection for WslRemoteConnection { "--".to_string(), self.shell.clone(), "-c".to_string(), - script, + exec, ] } else { vec![ @@ -427,7 +427,7 @@ impl RemoteConnection for WslRemoteConnection { "--".to_string(), self.shell.clone(), "-c".to_string(), - script, + exec, ] }; @@ -476,7 +476,7 @@ async fn windows_path_to_wsl_path_impl( fn wsl_command_impl( options: &WslConnectionOptions, program: &str, - args: &[&str], + args: &[impl AsRef], ) -> process::Command { let mut command = util::command::new_smol_command("wsl.exe"); @@ -495,6 +495,7 @@ fn wsl_command_impl( .arg(program) .args(args); + log::debug!("wsl {:?}", command); command }