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 7485a7f

Browse files
authoredJan 11, 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 2e2c86e + 0660732 commit 7485a7f

File tree

7 files changed

+210
-4
lines changed

7 files changed

+210
-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 paths 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 = "none")]
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

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
use crate::env;
12
use crate::ffi::OsStr;
2-
use crate::path::Prefix;
3+
use crate::io;
4+
use crate::os::unix::ffi::OsStrExt;
5+
use crate::path::{Path, PathBuf, Prefix};
36

47
#[inline]
58
pub fn is_sep_byte(b: u8) -> bool {
@@ -18,3 +21,43 @@ pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
1821

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

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