mirror of
https://github.com/zed-industries/zed.git
synced 2026-08-21 06:54:45 +00:00
Remote: Stream remote server into WSL on /mnt copy failure (#57624)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #47375 The current `crates/remote/src/transport/wsl.rs` remote transport code tries to copy the remote-server from /mnt/c/* from within WSL. This fails if you have automount disabled, or otherwise have your drive unmounted. How to reproduce this error: Prerequisites: Windows with WSL2 and a WSL distro installed. 1. Inside the WSL distro, disable Windows automount — edit `/etc/wsl.conf`: ```ini [automount] enabled = false ``` 2. From a Windows shell: `wsl --shutdown` 3. Open the distro shell again and confirm `mount | grep /mnt/c` returns nothing. 4. From Zed (any version before this PR), open a remote WSL project on that distro for the first time (no cached `~/.zed_server/zed-remote-server-*` inside the distro — `rm` it if present). The solution was to pipe the server in over stdin if the copy fails. This is a fallback path similar to how ssh.rs has a fallback path if the download fails there. With this PR the logs look like this: ``` 2026-05-24T18:51:37-07:00 INFO [remote::transport::wsl] uploading remote server to WSL ".zed_server/download-72808-remote_server" (441187kb) 2026-05-24T18:51:37-07:00 WARN [remote::transport::wsl] failed to upload remote server via /mnt, falling back to wsl.exe stdin: Command 'Command("wsl.exe" "--distribution" "Ubuntu" "--cd" "~" "--exec" "wslpath" "-u" "C:/Users/<user>/Documents/GitHub/zed/target/remote_server/x86_64-unknown-linux-musl/debug/remote_server")' failed: wslpath: C:/Users/<user>/Documents/GitHub/zed/target/remote_server/x86_64-unknown-linux-musl/debug/remote_server: Command 'Command("wsl.exe" "--distribution" "Ubuntu" "--cd" "~" "wslpath" "-u" "C:/Users/<user>/Documents/GitHub/zed/target/remote_server/x86_64-unknown-linux-musl/debug/remote_server")' failed: wslpath: C:/Users/<user>/Documents/GitHub/zed/target/remote_server/x86_64-unknown-linux-musl/debug/remote_server 2026-05-24T18:51:38-07:00 INFO [remote::transport::wsl] uploaded remote server in 945.899ms ``` I considered making wsl just use curl/wget to download as a fallback, as docker/ssh do, but that would lose the 'cache' effect of downloading once on windows and sending to multiple WSL distros. Which seems to have been the intent of the copy method in the first place. I also considered using the network-drive style UNC mount to copy from Windows to the WSL distro, but that could run into similar issues where the user may have that disabled. It also seems to copy as WSL's default user, rather than the --user specified to Zed for the distro. Currently --user is not passed by default to wsl.exe, so that ends up being the default user. However, it is settable through the settings.json, so some rare users may still have an issue with ownership of the copied files. I chose to make the new stdin option the fallback instead of the default because the current code is working for most people, and it seemed like a much larger change to replace the default. For the tests there don't seem to be any tests for wsl.rs currently that I can find, so it seemed like a lot of extra code needed to satisfy that PR checkbox. wsl.rs and docker.rs have zero inline tests, and the ones that are in ssh.rs don't cover the ensure/upload/download portion. The integration tests seem to use RemoteClient::fake_server and bypass the real transports. For manual testing I tested these scenarios: 1. WSL distro with automount **on** AND existing binary: **still works** 2. WSL distro with automount **on** AND **no** existing binary: **still works** 3. WSL distro with automount **off** AND existing binary: **still works** 4. WSL distro with automount **off** AND **no** existing binary: **now works** I'm not all that proficient with rust so I used a coding agent to help, but I reviewed every line and it all makes sense to me. Please let me know if something looks off, I'm trying to learn. Release Notes: - Fixed remote server installation failing in WSL when automount is disabled. --------- Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
parent
4aad57fd1f
commit
e717010c82
1 changed files with 73 additions and 2 deletions
|
|
@ -11,7 +11,10 @@ use gpui::{App, AppContext as _, AsyncApp, Task};
|
|||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use rpc::proto::Envelope;
|
||||
use semver::Version;
|
||||
use smol::fs;
|
||||
use smol::{
|
||||
fs,
|
||||
io::{self, AsyncWriteExt as _},
|
||||
};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fmt::Write as _,
|
||||
|
|
@ -297,6 +300,26 @@ impl WslRemoteConnection {
|
|||
size / 1024
|
||||
);
|
||||
|
||||
match self.copy_via_wslpath_and_cp(src_path, dst_path).await {
|
||||
Ok(()) => {}
|
||||
Err(cp_err) => {
|
||||
log::warn!(
|
||||
"failed to upload remote server via /mnt, falling back to wsl.exe stdin: {cp_err:#}"
|
||||
);
|
||||
delegate.set_status(Some("Streaming remote server into WSL"), cx);
|
||||
self.stream_file_into_wsl(src_path, dst_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to stream file into WSL after /mnt copy failed: {cp_err:#}")
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("uploaded remote server in {:?}", t0.elapsed());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn copy_via_wslpath_and_cp(&self, src_path: &Path, dst_path: &RelPath) -> Result<()> {
|
||||
let src_path_in_wsl = self.windows_path_to_wsl_path(src_path).await?;
|
||||
let cp = self.shell_kind.prepend_command_prefix("cp");
|
||||
self.run_wsl_command(
|
||||
|
|
@ -312,9 +335,57 @@ impl WslRemoteConnection {
|
|||
dst_path,
|
||||
e
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async fn stream_file_into_wsl(&self, src_path: &Path, dst_path: &RelPath) -> Result<()> {
|
||||
let mut file = fs::File::open(src_path).await.with_context(|| {
|
||||
format!(
|
||||
"failed to open {} for streaming into WSL",
|
||||
src_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
log::info!("uploaded remote server in {:?}", t0.elapsed());
|
||||
let dst_posix = dst_path.display(PathStyle::Unix);
|
||||
let mut command = wsl_command_impl(
|
||||
&self.connection_options,
|
||||
"sh",
|
||||
&["-c", "cat > \"$1\"", "zed-upload", dst_posix.as_ref()],
|
||||
true,
|
||||
);
|
||||
command.kill_on_drop(true);
|
||||
|
||||
let mut child = command
|
||||
.spawn()
|
||||
.context("failed to spawn wsl.exe for stdin upload")?;
|
||||
let mut stdin = child
|
||||
.stdin
|
||||
.take()
|
||||
.context("wsl.exe child did not expose stdin")?;
|
||||
|
||||
let copy_result = io::copy(&mut file, &mut stdin).await;
|
||||
let flush_result = stdin.flush().await;
|
||||
drop(stdin);
|
||||
|
||||
let output = child
|
||||
.output()
|
||||
.await
|
||||
.context("failed to await wsl.exe stdin-upload child")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!(
|
||||
"wsl.exe stdin upload failed (status {:?}): {}",
|
||||
output.status.code(),
|
||||
stderr.trim()
|
||||
);
|
||||
}
|
||||
|
||||
copy_result.with_context(|| {
|
||||
format!("failed to write {} into wsl.exe stdin", src_path.display())
|
||||
})?;
|
||||
flush_result.context("failed to flush wsl.exe stdin")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue