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 1f4681a

Browse files
committedFeb 13, 2022
Auto 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 c26fbf8 + 81cc3af commit 1f4681a

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
@@ -222,6 +222,7 @@
222222
// std is implemented with unstable features, many of which are internal
223223
// compiler details that will never be stable
224224
// NB: the following list is sorted to minimize merge conflicts.
225+
#![feature(absolute_path)]
225226
#![feature(alloc_error_handler)]
226227
#![feature(alloc_layout_extra)]
227228
#![feature(allocator_api)]

‎library/std/src/path.rs

+77-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ use crate::str::FromStr;
8585
use crate::sync::Arc;
8686

8787
use crate::ffi::{OsStr, OsString};
88-
88+
use crate::sys;
8989
use crate::sys::path::{is_sep_byte, is_verbatim_sep, parse_prefix, MAIN_SEP_STR};
9090

9191
////////////////////////////////////////////////////////////////////////////////
@@ -3172,3 +3172,79 @@ impl Error for StripPrefixError {
31723172
"prefix not found"
31733173
}
31743174
}
3175+
3176+
/// Makes the path absolute without accessing the filesystem.
3177+
///
3178+
/// If the path is relative, the current directory is used as the base directory.
3179+
/// All intermediate components will be resolved according to platforms-specific
3180+
/// rules but unlike [`canonicalize`][crate::fs::canonicalize] this does not
3181+
/// resolve symlinks and may succeed even if the path does not exist.
3182+
///
3183+
/// If the `path` is empty or getting the
3184+
/// [current directory][crate::env::current_dir] fails then an error will be
3185+
/// returned.
3186+
///
3187+
/// # Examples
3188+
///
3189+
/// ## Posix paths
3190+
///
3191+
/// ```
3192+
/// #![feature(absolute_path)]
3193+
/// # #[cfg(unix)]
3194+
/// fn main() -> std::io::Result<()> {
3195+
/// use std::path::{self, Path};
3196+
///
3197+
/// // Relative to absolute
3198+
/// let absolute = path::absolute("foo/./bar")?;
3199+
/// assert!(absolute.ends_with("foo/bar"));
3200+
///
3201+
/// // Absolute to absolute
3202+
/// let absolute = path::absolute("/foo//test/.././bar.rs")?;
3203+
/// assert_eq!(absolute, Path::new("/foo/test/../bar.rs"));
3204+
/// Ok(())
3205+
/// }
3206+
/// # #[cfg(not(unix))]
3207+
/// # fn main() {}
3208+
/// ```
3209+
///
3210+
/// The path is resolved using [POSIX semantics][posix-semantics] except that
3211+
/// it stops short of resolving symlinks. This means it will keep `..`
3212+
/// components and trailing slashes.
3213+
///
3214+
/// ## Windows paths
3215+
///
3216+
/// ```
3217+
/// #![feature(absolute_path)]
3218+
/// # #[cfg(windows)]
3219+
/// fn main() -> std::io::Result<()> {
3220+
/// use std::path::{self, Path};
3221+
///
3222+
/// // Relative to absolute
3223+
/// let absolute = path::absolute("foo/./bar")?;
3224+
/// assert!(absolute.ends_with(r"foo\bar"));
3225+
///
3226+
/// // Absolute to absolute
3227+
/// let absolute = path::absolute(r"C:\foo//test\..\./bar.rs")?;
3228+
///
3229+
/// assert_eq!(absolute, Path::new(r"C:\foo\bar.rs"));
3230+
/// Ok(())
3231+
/// }
3232+
/// # #[cfg(not(windows))]
3233+
/// # fn main() {}
3234+
/// ```
3235+
///
3236+
/// For verbatim paths this will simply return the path as given. For other
3237+
/// paths this is currently equivalent to calling [`GetFullPathNameW`][windows-path]
3238+
/// This may change in the future.
3239+
///
3240+
/// [posix-semantics]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
3241+
/// [windows-path]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
3242+
#[unstable(feature = "absolute_path", issue = "92750")]
3243+
pub fn absolute<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
3244+
let path = path.as_ref();
3245+
if path.as_os_str().is_empty() {
3246+
Err(io::const_io_error!(io::ErrorKind::InvalidInput, "cannot make an empty path absolute",))
3247+
} else {
3248+
sys::path::absolute(path)
3249+
}
3250+
}

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

+58
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,64 @@ fn test_ord() {
17001700
ord!(Equal, "foo/bar", "foo/bar//");
17011701
}
17021702

1703+
#[test]
1704+
#[cfg(unix)]
1705+
fn test_unix_absolute() {
1706+
use crate::path::absolute;
1707+
1708+
assert!(absolute("").is_err());
1709+
1710+
let relative = "a/b";
1711+
let mut expected = crate::env::current_dir().unwrap();
1712+
expected.push(relative);
1713+
assert_eq!(absolute(relative).unwrap(), expected);
1714+
1715+
// Test how components are collected.
1716+
assert_eq!(absolute("/a/b/c").unwrap(), Path::new("/a/b/c"));
1717+
assert_eq!(absolute("/a//b/c").unwrap(), Path::new("/a/b/c"));
1718+
assert_eq!(absolute("//a/b/c").unwrap(), Path::new("//a/b/c"));
1719+
assert_eq!(absolute("///a/b/c").unwrap(), Path::new("/a/b/c"));
1720+
assert_eq!(absolute("/a/b/c/").unwrap(), Path::new("/a/b/c/"));
1721+
assert_eq!(absolute("/a/./b/../c/.././..").unwrap(), Path::new("/a/b/../c/../.."));
1722+
}
1723+
1724+
#[test]
1725+
#[cfg(windows)]
1726+
fn test_windows_absolute() {
1727+
use crate::path::absolute;
1728+
// An empty path is an error.
1729+
assert!(absolute("").is_err());
1730+
1731+
let relative = r"a\b";
1732+
let mut expected = crate::env::current_dir().unwrap();
1733+
expected.push(relative);
1734+
assert_eq!(absolute(relative).unwrap(), expected);
1735+
1736+
macro_rules! unchanged(
1737+
($path:expr) => {
1738+
assert_eq!(absolute($path).unwrap(), Path::new($path));
1739+
}
1740+
);
1741+
1742+
unchanged!(r"C:\path\to\file");
1743+
unchanged!(r"C:\path\to\file\");
1744+
unchanged!(r"\\server\share\to\file");
1745+
unchanged!(r"\\server.\share.\to\file");
1746+
unchanged!(r"\\.\PIPE\name");
1747+
unchanged!(r"\\.\C:\path\to\COM1");
1748+
unchanged!(r"\\?\C:\path\to\file");
1749+
unchanged!(r"\\?\UNC\server\share\to\file");
1750+
unchanged!(r"\\?\PIPE\name");
1751+
// Verbatim paths are always unchanged, no matter what.
1752+
unchanged!(r"\\?\path.\to/file..");
1753+
1754+
assert_eq!(absolute(r"C:\path..\to.\file.").unwrap(), Path::new(r"C:\path..\to\file"));
1755+
assert_eq!(absolute(r"C:\path\to\COM1").unwrap(), Path::new(r"\\.\COM1"));
1756+
assert_eq!(absolute(r"C:\path\to\COM1.txt").unwrap(), Path::new(r"\\.\COM1"));
1757+
assert_eq!(absolute(r"C:\path\to\COM1 .txt").unwrap(), Path::new(r"\\.\COM1"));
1758+
assert_eq!(absolute(r"C:\path\to\cOnOuT$").unwrap(), Path::new(r"\\.\cOnOuT$"));
1759+
}
1760+
17031761
#[bench]
17041762
fn bench_path_cmp_fast_path_buf_sort(b: &mut test::Bencher) {
17051763
let prefix = "my/home";

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

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

46
#[inline]
57
pub fn is_sep_byte(b: u8) -> bool {
@@ -17,3 +19,7 @@ pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
1719

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

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

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

46
#[inline]
57
pub fn is_sep_byte(b: u8) -> bool {
@@ -17,3 +19,7 @@ pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
1719

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

‎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.