Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5d977c1

Browse files
authoredJan 12, 2022
Rollup merge of rust-lang#91673 - ChrisDenton:path-absolute, r=Mark-Simulacrum
`std::path::absolute` Implements rust-lang#59117 by adding a `std::path::absolute` function that creates an absolute path without reading the filesystem. This is intended to be a drop-in replacement for [`std::fs::canonicalize`](https://doc.rust-lang.org/std/fs/fn.canonicalize.html) in cases where it isn't necessary to resolve symlinks. It can be used on paths that don't exist or where resolving symlinks is unwanted. It can also be used to avoid circumstances where `canonicalize` might otherwise fail. On Windows this is a wrapper around [`GetFullPathNameW`](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew). On Unix it partially implements the POSIX [pathname resolution](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13) specification, stopping just short of actually resolving symlinks.
2 parents 4c95c6e + 232f4b4 commit 5d977c1

File tree

7 files changed

+209
-4
lines changed

7 files changed

+209
-4
lines changed
 

‎library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@
225225
// std is implemented with unstable features, many of which are internal
226226
// compiler details that will never be stable
227227
// NB: the following list is sorted to minimize merge conflicts.
228+
#![feature(absolute_path)]
228229
#![feature(alloc_error_handler)]
229230
#![feature(alloc_layout_extra)]
230231
#![feature(allocator_api)]

‎library/std/src/path.rs

+79-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ use crate::str::FromStr;
8484
use crate::sync::Arc;
8585

8686
use crate::ffi::{OsStr, OsString};
87-
87+
use crate::sys;
8888
use crate::sys::path::{is_sep_byte, is_verbatim_sep, parse_prefix, MAIN_SEP_STR};
8989

9090
////////////////////////////////////////////////////////////////////////////////
@@ -3141,3 +3141,81 @@ impl Error for StripPrefixError {
31413141
"prefix not found"
31423142
}
31433143
}
3144+
3145+
/// Makes the path absolute without accessing the filesystem.
3146+
///
3147+
/// If the path is relative, the current directory is used as the base directory.
3148+
/// All intermediate components will be resolved according to platforms-specific
3149+
/// rules but unlike [`canonicalize`][crate::fs::canonicalize] this does not
3150+
/// resolve symlinks and may succeed even if the path does not exist.
3151+
///
3152+
/// If the `path` is empty or getting the
3153+
/// [current directory][crate::env::current_dir] fails then an error will be
3154+
/// returned.
3155+
///
3156+
/// # Examples
3157+
///
3158+
/// ## Posix paths
3159+
///
3160+
/// ```
3161+
/// #![feature(absolute_path)]
3162+
/// # #[cfg(unix)]
3163+
/// fn main() -> std::io::Result<()> {
3164+
/// use std::path::{self, Path};
3165+
///
3166+
/// // Relative to absolute
3167+
/// let absolute = path::absolute("foo/./bar")?;
3168+
/// assert!(absolute.ends_with("foo/bar"));
3169+
///
3170+
/// // Absolute to absolute
3171+
/// let absolute = path::absolute("/foo//test/.././bar.rs")?;
3172+
/// assert_eq!(absolute, Path::new("/foo/test/../bar.rs"));
3173+
/// Ok(())
3174+
/// }
3175+
/// # #[cfg(not(unix))]
3176+
/// # fn main() {}
3177+
/// ```
3178+
///
3179+
/// The path is resolved using [POSIX semantics][posix-semantics] except that
3180+
/// it stops short of resolving symlinks. This means it will keep `..`
3181+
/// components and trailing slashes.
3182+
///
3183+
/// ## Windows paths
3184+
///
3185+
/// ```
3186+
/// #![feature(absolute_path)]
3187+
/// # #[cfg(windows)]
3188+
/// fn main() -> std::io::Result<()> {
3189+
/// use std::path::{self, Path};
3190+
///
3191+
/// // Relative to absolute
3192+
/// let absolute = path::absolute("foo/./bar")?;
3193+
/// assert!(absolute.ends_with(r"foo\bar"));
3194+
///
3195+
/// // Absolute to absolute
3196+
/// let absolute = path::absolute(r"C:\foo//test\..\./bar.rs")?;
3197+
///
3198+
/// assert_eq!(absolute, Path::new(r"C:\foo\bar.rs"));
3199+
/// Ok(())
3200+
/// }
3201+
/// # #[cfg(not(windows))]
3202+
/// # fn main() {}
3203+
/// ```
3204+
///
3205+
/// For verbatim paths this will simply return the path as given. For other
3206+
/// paths this is currently equivalent to calling [`GetFullPathNameW`][windows-path]
3207+
/// This may change in the future.
3208+
///
3209+
/// [posix-semantics]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
3210+
/// [windows-path]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
3211+
#[unstable(feature = "absolute_path", issue = "92750")]
3212+
pub fn absolute<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
3213+
let path = path.as_ref();
3214+
if path.as_os_str().is_empty() {
3215+
return Err(io::Error::new_const(
3216+
io::ErrorKind::InvalidInput,
3217+
&"cannot make an empty path absolute",
3218+
));
3219+
}
3220+
sys::path::absolute(path)
3221+
}

‎library/std/src/path/tests.rs

+58
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,64 @@ fn test_ord() {
16651665
ord!(Equal, "foo/bar", "foo/bar//");
16661666
}
16671667

1668+
#[test]
1669+
#[cfg(unix)]
1670+
fn test_unix_absolute() {
1671+
use crate::path::absolute;
1672+
1673+
assert!(absolute("").is_err());
1674+
1675+
let relative = "a/b";
1676+
let mut expected = crate::env::current_dir().unwrap();
1677+
expected.push(relative);
1678+
assert_eq!(absolute(relative).unwrap(), expected);
1679+
1680+
// Test how components are collected.
1681+
assert_eq!(absolute("/a/b/c").unwrap(), Path::new("/a/b/c"));
1682+
assert_eq!(absolute("/a//b/c").unwrap(), Path::new("/a/b/c"));
1683+
assert_eq!(absolute("//a/b/c").unwrap(), Path::new("//a/b/c"));
1684+
assert_eq!(absolute("///a/b/c").unwrap(), Path::new("/a/b/c"));
1685+
assert_eq!(absolute("/a/b/c/").unwrap(), Path::new("/a/b/c/"));
1686+
assert_eq!(absolute("/a/./b/../c/.././..").unwrap(), Path::new("/a/b/../c/../.."));
1687+
}
1688+
1689+
#[test]
1690+
#[cfg(windows)]
1691+
fn test_windows_absolute() {
1692+
use crate::path::absolute;
1693+
// An empty path is an error.
1694+
assert!(absolute("").is_err());
1695+
1696+
let relative = r"a\b";
1697+
let mut expected = crate::env::current_dir().unwrap();
1698+
expected.push(relative);
1699+
assert_eq!(absolute(relative).unwrap(), expected);
1700+
1701+
macro_rules! unchanged(
1702+
($path:expr) => {
1703+
assert_eq!(absolute($path).unwrap(), Path::new($path));
1704+
}
1705+
);
1706+
1707+
unchanged!(r"C:\path\to\file");
1708+
unchanged!(r"C:\path\to\file\");
1709+
unchanged!(r"\\server\share\to\file");
1710+
unchanged!(r"\\server.\share.\to\file");
1711+
unchanged!(r"\\.\PIPE\name");
1712+
unchanged!(r"\\.\C:\path\to\COM1");
1713+
unchanged!(r"\\?\C:\path\to\file");
1714+
unchanged!(r"\\?\UNC\server\share\to\file");
1715+
unchanged!(r"\\?\PIPE\name");
1716+
// Verbatim paths are always unchanged, no matter what.
1717+
unchanged!(r"\\?\path.\to/file..");
1718+
1719+
assert_eq!(absolute(r"C:\path..\to.\file.").unwrap(), Path::new(r"C:\path..\to\file"));
1720+
assert_eq!(absolute(r"C:\path\to\COM1").unwrap(), Path::new(r"\\.\COM1"));
1721+
assert_eq!(absolute(r"C:\path\to\COM1.txt").unwrap(), Path::new(r"\\.\COM1"));
1722+
assert_eq!(absolute(r"C:\path\to\COM1 .txt").unwrap(), Path::new(r"\\.\COM1"));
1723+
assert_eq!(absolute(r"C:\path\to\cOnOuT$").unwrap(), Path::new(r"\\.\cOnOuT$"));
1724+
}
1725+
16681726
#[bench]
16691727
fn bench_path_cmp_fast_path_buf_sort(b: &mut test::Bencher) {
16701728
let prefix = "my/home";

‎library/std/src/sys/sgx/path.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::ffi::OsStr;
2-
use crate::path::Prefix;
2+
use crate::path::{Path, PathBuf, Prefix};
3+
use crate::sys::unsupported;
34

45
#[inline]
56
pub fn is_sep_byte(b: u8) -> bool {
@@ -17,3 +18,7 @@ pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
1718

1819
pub const MAIN_SEP_STR: &str = "/";
1920
pub const MAIN_SEP: char = '/';
21+
22+
pub(crate) fn absolute(_path: &Path) -> io::Result<PathBuf> {
23+
unsupported()
24+
}

‎library/std/src/sys/solid/path.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::ffi::OsStr;
2-
use crate::path::Prefix;
2+
use crate::path::{Path, PathBuf, Prefix};
3+
use crate::sys::unsupported;
34

45
#[inline]
56
pub fn is_sep_byte(b: u8) -> bool {
@@ -17,3 +18,7 @@ pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
1718

1819
pub const MAIN_SEP_STR: &str = "\\";
1920
pub const MAIN_SEP: char = '\\';
21+
22+
pub(crate) fn absolute(_path: &Path) -> io::Result<PathBuf> {
23+
unsupported()
24+
}

‎library/std/src/sys/unix/path.rs

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use crate::env;
12
use crate::ffi::OsStr;
2-
use crate::path::Prefix;
3+
use crate::io;
4+
use crate::path::{Path, PathBuf, Prefix};
35

46
#[inline]
57
pub fn is_sep_byte(b: u8) -> bool {
@@ -18,3 +20,43 @@ pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
1820

1921
pub const MAIN_SEP_STR: &str = "/";
2022
pub const MAIN_SEP: char = '/';
23+
24+
/// Make a POSIX path absolute without changing its semantics.
25+
pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
26+
// This is mostly a wrapper around collecting `Path::components`, with
27+
// exceptions made where this conflicts with the POSIX specification.
28+
// See 4.13 Pathname Resolution, IEEE Std 1003.1-2017
29+
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
30+
31+
let mut components = path.components();
32+
let path_os = path.as_os_str().bytes();
33+
34+
let mut normalized = if path.is_absolute() {
35+
// "If a pathname begins with two successive <slash> characters, the
36+
// first component following the leading <slash> characters may be
37+
// interpreted in an implementation-defined manner, although more than
38+
// two leading <slash> characters shall be treated as a single <slash>
39+
// character."
40+
if path_os.starts_with(b"//") && !path_os.starts_with(b"///") {
41+
components.next();
42+
PathBuf::from("//")
43+
} else {
44+
PathBuf::new()
45+
}
46+
} else {
47+
env::current_dir()?
48+
};
49+
normalized.extend(components);
50+
51+
// "Interfaces using pathname resolution may specify additional constraints
52+
// when a pathname that does not name an existing directory contains at
53+
// least one non- <slash> character and contains one or more trailing
54+
// <slash> characters".
55+
// A trailing <slash> is also meaningful if "a symbolic link is
56+
// encountered during pathname resolution".
57+
if path_os.ends_with(b"/") {
58+
normalized.push("");
59+
}
60+
61+
Ok(normalized)
62+
}

‎library/std/src/sys/windows/path.rs

+16
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,19 @@ pub(crate) fn maybe_verbatim(path: &Path) -> io::Result<Vec<u16>> {
260260
)?;
261261
Ok(path)
262262
}
263+
264+
/// Make a Windows path absolute.
265+
pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
266+
if path.as_os_str().bytes().starts_with(br"\\?\") {
267+
return Ok(path.into());
268+
}
269+
let path = to_u16s(path)?;
270+
let lpfilename = path.as_ptr();
271+
fill_utf16_buf(
272+
// SAFETY: `fill_utf16_buf` ensures the `buffer` and `size` are valid.
273+
// `lpfilename` is a pointer to a null terminated string that is not
274+
// invalidated until after `GetFullPathNameW` returns successfully.
275+
|buffer, size| unsafe { c::GetFullPathNameW(lpfilename, size, buffer, ptr::null_mut()) },
276+
super::os2path,
277+
)
278+
}

0 commit comments

Comments
 (0)
Failed to load comments.