Remove std::fs::read_link in fs (#50974)

Closes #46307

Before you mark this PR as ready for review, make sure that you have:
- [ ] Added a solid test coverage and/or screenshots from doing manual
testing
- [X] Done a self-review taking into account security and performance
aspects
- [ ] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- Improved compatibility with mounted VHDs on Windows.

---------

Co-authored-by: John Tur <john-tur@outlook.com>
This commit is contained in:
Alex MihaiucandJohn Tur authored and GitHub committed 2026-03-14 20:02:34 -04:00
1 parent 99a5915576
commit 061e583223
5 files changed
+38 -74

No files matched your search

Generated
+1
View File
@@ -6583,6 +6583,7 @@ dependencies = [
"async-trait",
"cocoa 0.26.0",
"collections",
"dunce",
"fs",
"futures 0.3.31",
"git",
+1
View File
@@ -548,6 +548,7 @@ derive_more = { version = "2.1.1", features = [
dirs = "4.0"
documented = "0.9.1"
dotenvy = "0.15.0"
dunce = "1.0"
ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
+1
View File
@@ -48,6 +48,7 @@ cocoa = "0.26"
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true
dunce.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
ashpd.workspace = true
+34 -73
View File
@@ -431,82 +431,43 @@ impl RealFs {
#[cfg(target_os = "windows")]
fn canonicalize(path: &Path) -> Result<PathBuf> {
let mut strip_prefix = None;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use windows::Win32::Storage::FileSystem::GetVolumePathNameW;
use windows::core::HSTRING;
let mut new_path = PathBuf::new();
for component in path.components() {
match component {
std::path::Component::Prefix(_) => {
let component = component.as_os_str();
let canonicalized = if component
.to_str()
.map(|e| e.ends_with("\\"))
.unwrap_or(false)
{
std::fs::canonicalize(component)
} else {
let mut component = component.to_os_string();
component.push("\\");
std::fs::canonicalize(component)
}?;
// std::fs::canonicalize resolves mapped network paths to UNC paths, which can
// confuse some software. To mitigate this, we canonicalize the input, then rebase
// the result onto the input's original volume root if both paths are on the same
// volume. This keeps the same drive letter or mount point the caller used.
let mut strip = PathBuf::new();
for component in canonicalized.components() {
match component {
Component::Prefix(prefix_component) => {
match prefix_component.kind() {
std::path::Prefix::Verbatim(os_str) => {
strip.push(os_str);
}
std::path::Prefix::VerbatimUNC(host, share) => {
strip.push("\\\\");
strip.push(host);
strip.push(share);
}
std::path::Prefix::VerbatimDisk(disk) => {
strip.push(format!("{}:", disk as char));
}
_ => strip.push(component),
};
}
_ => strip.push(component),
}
}
strip_prefix = Some(strip);
new_path.push(component);
}
std::path::Component::RootDir => {
new_path.push(component);
}
std::path::Component::CurDir => {
if strip_prefix.is_none() {
// unrooted path
new_path.push(component);
}
}
std::path::Component::ParentDir => {
if strip_prefix.is_some() {
// rooted path
new_path.pop();
} else {
new_path.push(component);
}
}
std::path::Component::Normal(_) => {
if let Ok(link) = std::fs::read_link(new_path.join(component)) {
let link = match &strip_prefix {
Some(e) => link.strip_prefix(e).unwrap_or(&link),
None => &link,
};
new_path.extend(link);
} else {
new_path.push(component);
}
}
}
let abs_path = if path.is_relative() {
std::env::current_dir()?.join(path)
} else {
path.to_path_buf()
};
let path_hstring = HSTRING::from(abs_path.as_os_str());
let mut vol_buf = vec![0u16; abs_path.as_os_str().len() + 2];
unsafe { GetVolumePathNameW(&path_hstring, &mut vol_buf)? };
let volume_root = {
let len = vol_buf
.iter()
.position(|&c| c == 0)
.unwrap_or(vol_buf.len());
PathBuf::from(OsString::from_wide(&vol_buf[..len]))
};
let resolved_path = dunce::canonicalize(&abs_path)?;
let resolved_root = dunce::canonicalize(&volume_root)?;
if let Ok(relative) = resolved_path.strip_prefix(&resolved_root) {
let mut result = volume_root;
result.push(relative);
Ok(result)
} else {
Ok(resolved_path)
}
Ok(new_path)
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ test-support = ["git2", "rand", "util_macros"]
anyhow.workspace = true
async_zip.workspace = true
collections.workspace = true
dunce = "1.0"
dunce.workspace = true
futures-lite.workspace = true
futures.workspace = true
globset.workspace = true