diff --git a/Cargo.lock b/Cargo.lock index 21646eef97e2989bfe5c9f9cac9e53eb8b8ec11d..8d86e9fa9b82978a4309ff4a77dd8388b4626d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6583,6 +6583,7 @@ dependencies = [ "async-trait", "cocoa 0.26.0", "collections", + "dunce", "fs", "futures 0.3.31", "git", diff --git a/Cargo.toml b/Cargo.toml index 754860cc43f5b841e45316a0434b37886e901a0f..e1f5b4ffeb9711e00ae698205b5e9312a49adfe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 04cae2dd2ad18f85a7c2ed663c1c3482febb22d3..371057c3f8abfd50eea34f0edfcc3e3f7d52df7b 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -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 diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 311992d20d9947d189ff5026a73620090a8579c4..51757cc5a16b301facd465c356a984f0f41af388 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -431,82 +431,43 @@ impl RealFs { #[cfg(target_os = "windows")] fn canonicalize(path: &Path) -> Result { - 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) - }?; - - 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); - } - } - } - } + // 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. - Ok(new_path) + 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) + } } } diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 9f4c391ed01cc21e6e334d37407c8206ff1b3409..4f317e79e0cfc92087250182531ae33a591b1f48 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -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