Skip to content

Commit 0660732

Browse files
committed
std::path::absolute
1 parent f9e77f2 commit 0660732

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)