Notify on opening WSL paths outside of wsl (#40195)

Closes #27340

Release Notes:

- N/A

---------

Co-authored-by: John Tur <john-tur@outlook.com>
This commit is contained in:
Julia Ryan
2025-10-25 01:44:32 +00:00
committed by GitHub
co-authored by John Tur
parent 1dffdea27c
commit 7433d85458
11 changed files with 250 additions and 72 deletions
+103
View File
@@ -1087,6 +1087,68 @@ pub fn compare_paths(
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WslPath {
pub distro: String,
// the reason this is an OsString and not any of the path types is that it needs to
// represent a unix path (with '/' separators) on windows. `from_path` does this by
// manually constructing it from the path components of a given windows path.
pub path: std::ffi::OsString,
}
impl WslPath {
pub fn from_path<P: AsRef<Path>>(path: P) -> Option<WslPath> {
if cfg!(not(target_os = "windows")) {
return None;
}
use std::{
ffi::OsString,
path::{Component, Prefix},
};
let mut components = path.as_ref().components();
let Some(Component::Prefix(prefix)) = components.next() else {
return None;
};
let (server, distro) = match prefix.kind() {
Prefix::UNC(server, distro) => (server, distro),
Prefix::VerbatimUNC(server, distro) => (server, distro),
_ => return None,
};
let Some(Component::RootDir) = components.next() else {
return None;
};
let server_str = server.to_string_lossy();
if server_str == "wsl.localhost" || server_str == "wsl$" {
let mut result = OsString::from("");
for c in components {
use Component::*;
match c {
Prefix(p) => unreachable!("got {p:?}, but already stripped prefix"),
RootDir => unreachable!("got root dir, but already stripped root"),
CurDir => continue,
ParentDir => result.push("/.."),
Normal(s) => {
result.push("/");
result.push(s);
}
}
}
if result.is_empty() {
result.push("/");
}
Some(WslPath {
distro: distro.to_string_lossy().to_string(),
path: result,
})
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1921,4 +1983,45 @@ mod tests {
let suffix = Path::new("app.tar.gz");
assert_eq!(strip_path_suffix(base, suffix), None);
}
#[cfg(target_os = "windows")]
#[test]
fn test_wsl_path() {
use super::WslPath;
let path = "/a/b/c";
assert_eq!(WslPath::from_path(&path), None);
let path = r"\\wsl.localhost";
assert_eq!(WslPath::from_path(&path), None);
let path = r"\\wsl.localhost\Distro";
assert_eq!(
WslPath::from_path(&path),
Some(WslPath {
distro: "Distro".to_owned(),
path: "/".into(),
})
);
let path = r"\\wsl.localhost\Distro\blue";
assert_eq!(
WslPath::from_path(&path),
Some(WslPath {
distro: "Distro".to_owned(),
path: "/blue".into()
})
);
let path = r"\\wsl$\archlinux\tomato\.\paprika\..\aubergine.txt";
assert_eq!(
WslPath::from_path(&path),
Some(WslPath {
distro: "archlinux".to_owned(),
path: "/tomato/paprika/../aubergine.txt".into()
})
);
let path = r"\\windows.localhost\Distro\foo";
assert_eq!(WslPath::from_path(&path), None);
}
}