Skip to content

Commit d59d32c

Browse files
committed
std::path::absolute
1 parent 775e480 commit d59d32c

File tree

7 files changed

+208
-4
lines changed

7 files changed

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

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)