Skip to content

Commit 1b2de21

Browse files
committed
Auto merge of #11866 - Zoxc:fs-non-canon, r=ehuss
Add `try_canonicalize` and use it over `std::fs::canonicalize` This adds a `try_canonicalize` function that calls `std::fs::canonicalize` and on Windows falls back to getting an absolute path. Uses of `canonicalize` have been replaced with `std::fs::canonicalize`. On Windows `std::fs::canonicalize` may fail due to incomplete drivers. In particular `ImDisk` does not support it. Combined with rust-lang/rust#109231 this allows compiling crates on an `ImDisk` RAM disk and I've tested that it works with various configuration using [rcb](https://github.com/Zoxc/rcb).
2 parents 39684ff + 5430956 commit 1b2de21

File tree

5 files changed

+90
-17
lines changed

5 files changed

+90
-17
lines changed

src/cargo/core/compiler/compile_kind.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::core::Target;
44
use crate::util::errors::CargoResult;
55
use crate::util::interning::InternedString;
6-
use crate::util::{Config, StableHasher};
6+
use crate::util::{try_canonicalize, Config, StableHasher};
77
use anyhow::Context as _;
88
use serde::Serialize;
99
use std::collections::BTreeSet;
@@ -138,8 +138,7 @@ impl CompileTarget {
138138
// If `name` ends in `.json` then it's likely a custom target
139139
// specification. Canonicalize the path to ensure that different builds
140140
// with different paths always produce the same result.
141-
let path = Path::new(name)
142-
.canonicalize()
141+
let path = try_canonicalize(Path::new(name))
143142
.with_context(|| format!("target path {:?} is not a valid file", name))?;
144143

145144
let name = path

src/cargo/core/compiler/fingerprint/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,9 @@ use serde::{Deserialize, Serialize};
367367

368368
use crate::core::compiler::unit_graph::UnitDep;
369369
use crate::core::Package;
370-
use crate::util;
371370
use crate::util::errors::CargoResult;
372371
use crate::util::interning::InternedString;
372+
use crate::util::{self, try_canonicalize};
373373
use crate::util::{internal, path_args, profile, StableHasher};
374374
use crate::{Config, CARGO_ENV};
375375

@@ -1951,8 +1951,8 @@ pub fn translate_dep_info(
19511951
) -> CargoResult<()> {
19521952
let depinfo = parse_rustc_dep_info(rustc_dep_info)?;
19531953

1954-
let target_root = target_root.canonicalize()?;
1955-
let pkg_root = pkg_root.canonicalize()?;
1954+
let target_root = try_canonicalize(target_root)?;
1955+
let pkg_root = try_canonicalize(pkg_root)?;
19561956
let mut on_disk_info = EncodedDepInfo::default();
19571957
on_disk_info.env = depinfo.env;
19581958

@@ -1995,7 +1995,7 @@ pub fn translate_dep_info(
19951995
// If canonicalization fails, just use the abs path. There is currently
19961996
// a bug where --remap-path-prefix is affecting .d files, causing them
19971997
// to point to non-existent paths.
1998-
let canon_file = abs_file.canonicalize().unwrap_or_else(|_| abs_file.clone());
1998+
let canon_file = try_canonicalize(&abs_file).unwrap_or_else(|_| abs_file.clone());
19991999

20002000
let (ty, path) = if let Ok(stripped) = canon_file.strip_prefix(&target_root) {
20012001
(DepInfoPathType::TargetRootRelative, stripped)

src/cargo/ops/vendor.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::core::{GitReference, Package, Workspace};
44
use crate::ops;
55
use crate::sources::path::PathSource;
66
use crate::sources::CRATES_IO_REGISTRY;
7-
use crate::util::{CargoResult, Config};
7+
use crate::util::{try_canonicalize, CargoResult, Config};
88
use anyhow::{bail, Context as _};
99
use cargo_util::{paths, Sha256};
1010
use serde::Serialize;
@@ -83,7 +83,7 @@ fn sync(
8383
workspaces: &[&Workspace<'_>],
8484
opts: &VendorOptions<'_>,
8585
) -> CargoResult<VendorConfig> {
86-
let canonical_destination = opts.destination.canonicalize();
86+
let canonical_destination = try_canonicalize(opts.destination);
8787
let canonical_destination = canonical_destination.as_deref().unwrap_or(opts.destination);
8888
let dest_dir_already_exists = canonical_destination.exists();
8989

@@ -125,7 +125,7 @@ fn sync(
125125
// Don't delete actual source code!
126126
if pkg.source_id().is_path() {
127127
if let Ok(path) = pkg.source_id().url().to_file_path() {
128-
if let Ok(path) = path.canonicalize() {
128+
if let Ok(path) = try_canonicalize(path) {
129129
to_remove.remove(&path);
130130
}
131131
}

src/cargo/util/config/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ use crate::core::{features, CliUnstable, Shell, SourceId, Workspace, WorkspaceRo
7272
use crate::ops::{self, RegistryCredentialConfig};
7373
use crate::util::auth::Secret;
7474
use crate::util::errors::CargoResult;
75-
use crate::util::validate_package_name;
7675
use crate::util::CanonicalUrl;
7776
use crate::util::{internal, toml as cargo_toml};
77+
use crate::util::{try_canonicalize, validate_package_name};
7878
use crate::util::{FileLock, Filesystem, IntoUrl, IntoUrlWithBase, Rustc};
7979
use anyhow::{anyhow, bail, format_err, Context as _};
8080
use cargo_util::paths;
@@ -433,11 +433,11 @@ impl Config {
433433
// commands that use Cargo as a library to inherit (via `cargo <subcommand>`)
434434
// or set (by setting `$CARGO`) a correct path to `cargo` when the current exe
435435
// is not actually cargo (e.g., `cargo-*` binaries, Valgrind, `ld.so`, etc.).
436-
let exe = self
437-
.get_env_os(crate::CARGO_ENV)
438-
.map(PathBuf::from)
439-
.ok_or_else(|| anyhow!("$CARGO not set"))?
440-
.canonicalize()?;
436+
let exe = try_canonicalize(
437+
self.get_env_os(crate::CARGO_ENV)
438+
.map(PathBuf::from)
439+
.ok_or_else(|| anyhow!("$CARGO not set"))?,
440+
)?;
441441
Ok(exe)
442442
};
443443

@@ -446,7 +446,7 @@ impl Config {
446446
// The method varies per operating system and might fail; in particular,
447447
// it depends on `/proc` being mounted on Linux, and some environments
448448
// (like containers or chroots) may not have that available.
449-
let exe = env::current_exe()?.canonicalize()?;
449+
let exe = try_canonicalize(env::current_exe()?)?;
450450
Ok(exe)
451451
}
452452

src/cargo/util/mod.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fmt;
2+
use std::path::{Path, PathBuf};
23
use std::time::Duration;
34

45
pub use self::canonical_url::CanonicalUrl;
@@ -133,6 +134,79 @@ pub fn truncate_with_ellipsis(s: &str, max_width: usize) -> String {
133134
prefix
134135
}
135136

137+
#[cfg(not(windows))]
138+
#[inline]
139+
pub fn try_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
140+
std::fs::canonicalize(&path)
141+
}
142+
143+
#[cfg(windows)]
144+
#[inline]
145+
pub fn try_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
146+
use std::ffi::OsString;
147+
use std::io::Error;
148+
use std::os::windows::ffi::{OsStrExt, OsStringExt};
149+
use std::{io::ErrorKind, ptr};
150+
use windows_sys::Win32::Foundation::{GetLastError, SetLastError};
151+
use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW;
152+
153+
// On Windows `canonicalize` may fail, so we fall back to getting an absolute path.
154+
std::fs::canonicalize(&path).or_else(|_| {
155+
// Return an error if a file does not exist for better compatiblity with `canonicalize`
156+
if !path.as_ref().try_exists()? {
157+
return Err(Error::new(ErrorKind::NotFound, "the path was not found"));
158+
}
159+
160+
// This code is based on the unstable `std::path::absolute` and could be replaced with it
161+
// if it's stabilized.
162+
163+
let path = path.as_ref().as_os_str();
164+
let mut path_u16 = Vec::with_capacity(path.len() + 1);
165+
path_u16.extend(path.encode_wide());
166+
if path_u16.iter().find(|c| **c == 0).is_some() {
167+
return Err(Error::new(
168+
ErrorKind::InvalidInput,
169+
"strings passed to WinAPI cannot contain NULs",
170+
));
171+
}
172+
path_u16.push(0);
173+
174+
loop {
175+
unsafe {
176+
SetLastError(0);
177+
let len =
178+
GetFullPathNameW(path_u16.as_ptr(), 0, &mut [] as *mut u16, ptr::null_mut());
179+
if len == 0 {
180+
let error = GetLastError();
181+
if error != 0 {
182+
return Err(Error::from_raw_os_error(error as i32));
183+
}
184+
}
185+
let mut result = vec![0u16; len as usize];
186+
187+
let write_len = GetFullPathNameW(
188+
path_u16.as_ptr(),
189+
result.len().try_into().unwrap(),
190+
result.as_mut_ptr().cast::<u16>(),
191+
ptr::null_mut(),
192+
);
193+
if write_len == 0 {
194+
let error = GetLastError();
195+
if error != 0 {
196+
return Err(Error::from_raw_os_error(error as i32));
197+
}
198+
}
199+
200+
if write_len <= len {
201+
return Ok(PathBuf::from(OsString::from_wide(
202+
&result[0..(write_len as usize)],
203+
)));
204+
}
205+
}
206+
}
207+
})
208+
}
209+
136210
#[cfg(test)]
137211
mod test {
138212
use super::*;

0 commit comments

Comments
 (0)