Skip to content

Commit

Permalink
Add try_canonicalize
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoxc committed Mar 17, 2023
1 parent f2f4496 commit 60178cb
Showing 1 changed file with 72 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/cargo/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fmt;
use std::path::{Path, PathBuf};
use std::time::Duration;

pub use self::canonical_url::CanonicalUrl;
Expand Down Expand Up @@ -133,6 +134,77 @@ pub fn truncate_with_ellipsis(s: &str, max_width: usize) -> String {
prefix
}

#[cfg(not(windows))]
#[inline]
pub fn try_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
std::fs::canonicalize(&path)
}

#[cfg(windows)]
#[inline]
pub fn try_canonicalize<P: AsRef<Path>>(path: P) -> std::io::Result<PathBuf> {
use std::ffi::OsString;
use std::io::Error;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::{io::ErrorKind, ptr};
use windows_sys::Win32::Foundation::{GetLastError, SetLastError};
use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW;

// On Windows `canonicalize` may fail, so we fall back to getting an absolute path.
std::fs::canonicalize(&path).or_else(|_| {
// Return an error if a file does not exist for better compatiblity with `canonicalize`
if !path.as_ref().try_exists()? {
return Err(Error::new(ErrorKind::NotFound, "the path was not found"));
}

let path = path.as_ref().as_os_str();
let mut path_u16 = Vec::with_capacity(path.len() + 1);
path_u16.extend(path.encode_wide());
if path_u16.iter().find(|c| **c == 0).is_some() {
return Err(Error::new(
ErrorKind::InvalidInput,
"strings passed to WinAPI cannot contain NULs",
));
}
path_u16.push(0);

unsafe {
SetLastError(0);
let len = GetFullPathNameW(path_u16.as_ptr(), 0, &mut [] as *mut u16, ptr::null_mut());
if len == 0 {
let error = GetLastError();
if error != 0 {
return Err(Error::from_raw_os_error(error as i32));
}
}
let mut result: Vec<u16> = std::iter::repeat(0).take(len as usize).collect();

let write_len = GetFullPathNameW(
path_u16.as_ptr(),
result.len().try_into().unwrap(),
result.as_mut_ptr().cast::<u16>(),
ptr::null_mut(),
);
if write_len == 0 {
let error = GetLastError();
if error != 0 {
return Err(Error::from_raw_os_error(error as i32));
}
}
assert_eq!(
write_len + 1,
len,
"mismatching requested and written lengths for path {:?}",
path
);

Ok(PathBuf::from(OsString::from_wide(
&result[0..(write_len as usize)],
)))
}
})
}

#[cfg(test)]
mod test {
use super::*;
Expand Down

0 comments on commit 60178cb

Please sign in to comment.