Skip to content

Commit 60178cb

Browse files
committed
Add try_canonicalize
1 parent f2f4496 commit 60178cb

File tree

1 file changed

+72
-0
lines changed

1 file changed

+72
-0
lines changed

src/cargo/util/mod.rs

Lines changed: 72 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,77 @@ 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+
let path = path.as_ref().as_os_str();
161+
let mut path_u16 = Vec::with_capacity(path.len() + 1);
162+
path_u16.extend(path.encode_wide());
163+
if path_u16.iter().find(|c| **c == 0).is_some() {
164+
return Err(Error::new(
165+
ErrorKind::InvalidInput,
166+
"strings passed to WinAPI cannot contain NULs",
167+
));
168+
}
169+
path_u16.push(0);
170+
171+
unsafe {
172+
SetLastError(0);
173+
let len = GetFullPathNameW(path_u16.as_ptr(), 0, &mut [] as *mut u16, ptr::null_mut());
174+
if len == 0 {
175+
let error = GetLastError();
176+
if error != 0 {
177+
return Err(Error::from_raw_os_error(error as i32));
178+
}
179+
}
180+
let mut result: Vec<u16> = std::iter::repeat(0).take(len as usize).collect();
181+
182+
let write_len = GetFullPathNameW(
183+
path_u16.as_ptr(),
184+
result.len().try_into().unwrap(),
185+
result.as_mut_ptr().cast::<u16>(),
186+
ptr::null_mut(),
187+
);
188+
if write_len == 0 {
189+
let error = GetLastError();
190+
if error != 0 {
191+
return Err(Error::from_raw_os_error(error as i32));
192+
}
193+
}
194+
assert_eq!(
195+
write_len + 1,
196+
len,
197+
"mismatching requested and written lengths for path {:?}",
198+
path
199+
);
200+
201+
Ok(PathBuf::from(OsString::from_wide(
202+
&result[0..(write_len as usize)],
203+
)))
204+
}
205+
})
206+
}
207+
136208
#[cfg(test)]
137209
mod test {
138210
use super::*;

0 commit comments

Comments
 (0)