|
1 | 1 | use std::fmt;
|
| 2 | +use std::path::{Path, PathBuf}; |
2 | 3 | use std::time::Duration;
|
3 | 4 |
|
4 | 5 | pub use self::canonical_url::CanonicalUrl;
|
@@ -133,6 +134,77 @@ pub fn truncate_with_ellipsis(s: &str, max_width: usize) -> String {
|
133 | 134 | prefix
|
134 | 135 | }
|
135 | 136 |
|
| 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 | + |
136 | 208 | #[cfg(test)]
|
137 | 209 | mod test {
|
138 | 210 | use super::*;
|
|
0 commit comments