remote: Check if remote can --exec, fall back to spawning shell otherwise (#40112)

Jakub Konka created

Bonus: fix passing env vars to the proxy server in WSL setting.

Closes #39710
Supersedes #39893

Release Notes:

- N/A

Change summary

crates/remote/src/transport/wsl.rs | 93 ++++++++++++++++++++++++-------
1 file changed, 71 insertions(+), 22 deletions(-)

Detailed changes

crates/remote/src/transport/wsl.rs 🔗

@@ -38,12 +38,14 @@ impl From<settings::WslConnection> for WslConnectionOptions {
     }
 }
 
+#[derive(Debug)]
 pub(crate) struct WslRemoteConnection {
     remote_binary_path: Option<Arc<RelPath>>,
     platform: RemotePlatform,
     shell: String,
     default_system_shell: String,
     connection_options: WslConnectionOptions,
+    can_exec: bool,
 }
 
 impl WslRemoteConnection {
@@ -71,18 +73,48 @@ impl WslRemoteConnection {
             platform: RemotePlatform { os: "", arch: "" },
             shell: String::new(),
             default_system_shell: String::from("/bin/sh"),
+            can_exec: true,
         };
         delegate.set_status(Some("Detecting WSL environment"), cx);
+        this.can_exec = this.detect_can_exec().await?;
         this.platform = this.detect_platform().await?;
         this.shell = this.detect_shell().await?;
         this.remote_binary_path = Some(
             this.ensure_server_binary(&delegate, release_channel, version, commit, cx)
                 .await?,
         );
+        log::debug!("Detected WSL environment: {this:#?}");
 
         Ok(this)
     }
 
+    async fn detect_can_exec(&self) -> Result<bool> {
+        let options = &self.connection_options;
+        let program = "uname";
+        let args = &["-m"];
+        let output = wsl_command_impl(options, program, args, true)
+            .output()
+            .await?;
+
+        if !output.status.success() {
+            let output = wsl_command_impl(options, program, args, false)
+                .output()
+                .await?;
+
+            if !output.status.success() {
+                return Err(anyhow!(
+                    "Command '{}' failed: {}",
+                    program,
+                    String::from_utf8_lossy(&output.stderr).trim()
+                ));
+            }
+
+            Ok(false)
+        } else {
+            Ok(true)
+        }
+    }
+
     async fn detect_platform(&self) -> Result<RemotePlatform> {
         let arch_str = self.run_wsl_command("uname", &["-m"]).await?;
         let arch_str = arch_str.trim().to_string();
@@ -103,15 +135,15 @@ impl WslRemoteConnection {
     }
 
     async fn windows_path_to_wsl_path(&self, source: &Path) -> Result<String> {
-        windows_path_to_wsl_path_impl(&self.connection_options, source).await
+        windows_path_to_wsl_path_impl(&self.connection_options, source, self.can_exec).await
     }
 
     fn wsl_command(&self, program: &str, args: &[impl AsRef<OsStr>]) -> process::Command {
-        wsl_command_impl(&self.connection_options, program, args)
+        wsl_command_impl(&self.connection_options, program, args, self.can_exec)
     }
 
     async fn run_wsl_command(&self, program: &str, args: &[&str]) -> Result<String> {
-        run_wsl_command_impl(&self.connection_options, program, args).await
+        run_wsl_command_impl(&self.connection_options, program, args, self.can_exec).await
     }
 
     async fn ensure_server_binary(
@@ -296,7 +328,10 @@ impl RemoteConnection for WslRemoteConnection {
         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_args.push(format!("{}='{}'", env_var, value));
+                // We don't quote the value here as it seems excessive and may result in invalid envs for the
+                // proxy server. For example, `RUST_LOG='debug'` will result in a warning "invalid logging spec 'debug'', ignoring it"
+                // in the proxy server. Therefore, we pass the env vars as is.
+                proxy_args.push(format!("{}={}", env_var, value));
             }
         }
         proxy_args.push(remote_binary_path.display(PathStyle::Posix).into_owned());
@@ -335,19 +370,25 @@ impl RemoteConnection for WslRemoteConnection {
     ) -> Task<Result<()>> {
         cx.background_spawn({
             let options = self.connection_options.clone();
+            let can_exec = self.can_exec;
             async move {
-                let wsl_src = windows_path_to_wsl_path_impl(&options, &src_path).await?;
-
-                run_wsl_command_impl(&options, "cp", &["-r", &wsl_src, &dest_path.to_string()])
-                    .await
-                    .map_err(|e| {
-                        anyhow!(
-                            "failed to upload directory {} -> {}: {}",
-                            src_path.display(),
-                            dest_path.to_string(),
-                            e
-                        )
-                    })?;
+                let wsl_src = windows_path_to_wsl_path_impl(&options, &src_path, can_exec).await?;
+
+                run_wsl_command_impl(
+                    &options,
+                    "cp",
+                    &["-r", &wsl_src, &dest_path.to_string()],
+                    can_exec,
+                )
+                .await
+                .map_err(|e| {
+                    anyhow!(
+                        "failed to upload directory {} -> {}: {}",
+                        src_path.display(),
+                        dest_path.to_string(),
+                        e
+                    )
+                })?;
 
                 Ok(())
             }
@@ -472,17 +513,21 @@ async fn sanitize_path(path: &Path) -> Result<String> {
 async fn windows_path_to_wsl_path_impl(
     options: &WslConnectionOptions,
     source: &Path,
+    exec: bool,
 ) -> Result<String> {
     let source = sanitize_path(source).await?;
-    run_wsl_command_impl(options, "wslpath", &["-u", &source]).await
+    run_wsl_command_impl(options, "wslpath", &["-u", &source], exec).await
 }
 
 async fn run_wsl_command_impl(
     options: &WslConnectionOptions,
     program: &str,
     args: &[&str],
+    exec: bool,
 ) -> Result<String> {
-    let output = wsl_command_impl(options, program, args).output().await?;
+    let output = wsl_command_impl(options, program, args, exec)
+        .output()
+        .await?;
 
     if !output.status.success() {
         return Err(anyhow!(
@@ -502,6 +547,7 @@ fn wsl_command_impl(
     options: &WslConnectionOptions,
     program: &str,
     args: &[impl AsRef<OsStr>],
+    exec: bool,
 ) -> process::Command {
     let mut command = util::command::new_smol_command("wsl.exe");
 
@@ -516,10 +562,13 @@ fn wsl_command_impl(
         .arg("--distribution")
         .arg(&options.distro_name)
         .arg("--cd")
-        .arg("~")
-        .arg("--exec")
-        .arg(program)
-        .args(args);
+        .arg("~");
+
+    if exec {
+        command.arg("--exec");
+    }
+
+    command.arg(program).args(args);
 
     log::debug!("wsl {:?}", command);
     command