Skip to content

Commit dfea730

Browse files
committed
Handle win32 separator & prefixes for cygwin paths
1 parent 99426c5 commit dfea730

File tree

11 files changed

+789
-187
lines changed

11 files changed

+789
-187
lines changed

library/std/src/path.rs

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,8 @@ where
299299
////////////////////////////////////////////////////////////////////////////////
300300

301301
/// Says whether the first byte after the prefix is a separator.
302-
fn has_physical_root(s: &[u8], prefix: Option<Prefix<'_>>) -> bool {
303-
let path = if let Some(p) = prefix { &s[p.len()..] } else { s };
302+
fn has_physical_root(s: &[u8], prefix: Option<Prefix<'_>>, pre_prefix_len: usize) -> bool {
303+
let path = if let Some(p) = prefix { &s[p.len() + pre_prefix_len..] } else { s };
304304
!path.is_empty() && is_sep_byte(path[0])
305305
}
306306

@@ -600,6 +600,9 @@ pub struct Components<'a> {
600600
// The prefix as it was originally parsed, if any
601601
prefix: Option<Prefix<'a>>,
602602

603+
// The length of `./` in `//./` on Cygwin
604+
pre_prefix_len: usize,
605+
603606
// true if path *physically* has a root separator; for most Windows
604607
// prefixes, it may have a "logical" root separator for the purposes of
605608
// normalization, e.g., \\server\share == \\server\share\.
@@ -643,7 +646,7 @@ impl<'a> Components<'a> {
643646
// how long is the prefix, if any?
644647
#[inline]
645648
fn prefix_len(&self) -> usize {
646-
self.prefix.as_ref().map(Prefix::len).unwrap_or(0)
649+
self.prefix.as_ref().map(Prefix::len).unwrap_or(0) + self.pre_prefix_len
647650
}
648651

649652
#[inline]
@@ -989,7 +992,14 @@ impl FusedIterator for Components<'_> {}
989992
impl<'a> PartialEq for Components<'a> {
990993
#[inline]
991994
fn eq(&self, other: &Components<'a>) -> bool {
992-
let Components { path: _, front: _, back: _, has_physical_root: _, prefix: _ } = self;
995+
let Components {
996+
path: _,
997+
front: _,
998+
back: _,
999+
has_physical_root: _,
1000+
prefix: _,
1001+
pre_prefix_len: _,
1002+
} = self;
9931003

9941004
// Fast path for exact matches, e.g. for hashmap lookups.
9951005
// Don't explicitly compare the prefix or has_physical_root fields since they'll
@@ -999,6 +1009,7 @@ impl<'a> PartialEq for Components<'a> {
9991009
&& self.back == State::Body
10001010
&& other.back == State::Body
10011011
&& self.prefix_verbatim() == other.prefix_verbatim()
1012+
&& self.pre_prefix_len == other.pre_prefix_len
10021013
{
10031014
// possible future improvement: this could bail out earlier if there were a
10041015
// reverse memcmp/bcmp comparing back to front
@@ -1315,8 +1326,17 @@ impl PathBuf {
13151326
need_sep = false
13161327
}
13171328

1329+
let need_clear = if cfg!(target_os = "cygwin") {
1330+
// If path is absolute and its prefix is none, it is like `/foo`,
1331+
// and will be handled below.
1332+
path.prefix().is_some()
1333+
} else {
1334+
// On Unix: prefix is always None.
1335+
path.is_absolute() || path.prefix().is_some()
1336+
};
1337+
13181338
// absolute `path` replaces `self`
1319-
if path.is_absolute() || path.prefix().is_some() {
1339+
if need_clear {
13201340
self.inner.truncate(0);
13211341

13221342
// verbatim paths need . and .. removed
@@ -2862,11 +2882,15 @@ impl Path {
28622882
/// [`CurDir`]: Component::CurDir
28632883
#[stable(feature = "rust1", since = "1.0.0")]
28642884
pub fn components(&self) -> Components<'_> {
2865-
let prefix = parse_prefix(self.as_os_str());
2885+
let (pre_prefix_len, prefix) = match parse_prefix(self.as_os_str()) {
2886+
Some((pre_prefix_len, prefix)) => (pre_prefix_len, Some(prefix)),
2887+
None => (0, None),
2888+
};
28662889
Components {
28672890
path: self.as_u8_slice(),
28682891
prefix,
2869-
has_physical_root: has_physical_root(self.as_u8_slice(), prefix),
2892+
pre_prefix_len,
2893+
has_physical_root: has_physical_root(self.as_u8_slice(), prefix, pre_prefix_len),
28702894
front: State::Prefix,
28712895
back: State::Body,
28722896
}
@@ -3330,9 +3354,9 @@ impl Hash for Path {
33303354
fn hash<H: Hasher>(&self, h: &mut H) {
33313355
let bytes = self.as_u8_slice();
33323356
let (prefix_len, verbatim) = match parse_prefix(&self.inner) {
3333-
Some(prefix) => {
3357+
Some((pre_prefix_len, prefix)) => {
33343358
prefix.hash(h);
3335-
(prefix.len(), prefix.is_verbatim())
3359+
(prefix.len() + pre_prefix_len, prefix.is_verbatim())
33363360
}
33373361
None => (0, false),
33383362
};
@@ -3615,6 +3639,9 @@ impl Error for NormalizeError {}
36153639
/// paths, this is currently equivalent to calling
36163640
/// [`GetFullPathNameW`][windows-path].
36173641
///
3642+
/// On Cygwin, this is currently equivalent to calling [`cygwin_conv_path`][cygwin-path]
3643+
/// with mode `CCP_WIN_A_TO_POSIX`.
3644+
///
36183645
/// Note that these [may change in the future][changes].
36193646
///
36203647
/// # Errors
@@ -3667,11 +3694,36 @@ impl Error for NormalizeError {}
36673694
/// # fn main() {}
36683695
/// ```
36693696
///
3697+
/// ## Cygwin paths
3698+
///
3699+
/// ```
3700+
/// # #[cfg(target_os = "cygwin")]
3701+
/// fn main() -> std::io::Result<()> {
3702+
/// use std::path::{self, Path};
3703+
///
3704+
/// // Relative to absolute
3705+
/// let absolute = path::absolute("foo/./bar")?;
3706+
/// assert!(absolute.ends_with(r"foo/bar"));
3707+
///
3708+
/// // Windows absolute to absolute
3709+
/// let absolute = path::absolute(r"C:\foo//test\..\./bar.rs")?;
3710+
/// assert!(absolute.ends_with("/c/foo/bar.rs"));
3711+
///
3712+
/// // POSIX absolute to absolute
3713+
/// let absolute = path::absolute("/foo//test/.././bar.rs")?;
3714+
/// assert_eq!(absolute, Path::new("/foo//test/.././bar.rs"));
3715+
/// Ok(())
3716+
/// }
3717+
/// # #[cfg(not(target_os = "cygwin"))]
3718+
/// # fn main() {}
3719+
/// ```
3720+
///
36703721
/// Note that this [may change in the future][changes].
36713722
///
36723723
/// [changes]: io#platform-specific-behavior
36733724
/// [posix-semantics]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
36743725
/// [windows-path]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
3726+
/// [cygwin-path]: https://cygwin.com/cygwin-api/func-cygwin-conv-path.html
36753727
#[stable(feature = "absolute_path", since = "1.79.0")]
36763728
pub fn absolute<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
36773729
let path = path.as_ref();

library/std/src/sys/path/cygwin.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use crate::ffi::OsString;
2+
use crate::os::unix::ffi::OsStringExt;
3+
use crate::path::{Path, PathBuf};
4+
use crate::sys::common::small_c_string::run_path_with_cstr;
5+
use crate::sys::cvt;
6+
use crate::{io, ptr};
7+
8+
#[inline]
9+
pub fn is_sep_byte(b: u8) -> bool {
10+
b == b'/' || b == b'\\'
11+
}
12+
13+
/// Cygwin allways prefers `/` over `\`, and it always converts all `/` to `\`
14+
/// internally when calling Win32 APIs. Therefore, the server component of path
15+
/// `\\?\UNC\localhost/share` is `localhost/share` on Win32, but `localhost`
16+
/// on Cygwin.
17+
#[inline]
18+
pub fn is_verbatim_sep(b: u8) -> bool {
19+
b == b'/' || b == b'\\'
20+
}
21+
22+
pub use super::windows_prefix::parse_prefix;
23+
24+
pub const MAIN_SEP_STR: &str = "/";
25+
pub const MAIN_SEP: char = '/';
26+
27+
unsafe extern "C" {
28+
// Doc: https://cygwin.com/cygwin-api/func-cygwin-conv-path.html
29+
// Src: https://github.com/cygwin/cygwin/blob/718a15ba50e0d01c79800bd658c2477f9a603540/winsup/cygwin/path.cc#L3902
30+
// Safety:
31+
// * `what` should be `CCP_WIN_A_TO_POSIX` here
32+
// * `from` is null-terminated UTF-8 path
33+
// * `to` is buffer, the buffer size is `size`.
34+
//
35+
// Converts a path to an absolute POSIX path, no matter the input is Win32 path or POSIX path.
36+
fn cygwin_conv_path(
37+
what: libc::c_uint,
38+
from: *const libc::c_char,
39+
to: *mut u8,
40+
size: libc::size_t,
41+
) -> libc::ssize_t;
42+
}
43+
44+
const CCP_WIN_A_TO_POSIX: libc::c_uint = 2;
45+
46+
/// Make a POSIX path absolute.
47+
pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
48+
run_path_with_cstr(path, &|path| {
49+
let conv = CCP_WIN_A_TO_POSIX;
50+
let size = cvt(unsafe { cygwin_conv_path(conv, path.as_ptr(), ptr::null_mut(), 0) })?;
51+
// If success, size should not be 0.
52+
debug_assert!(size >= 1);
53+
let size = size as usize;
54+
let mut buffer = Vec::with_capacity(size);
55+
cvt(unsafe { cygwin_conv_path(conv, path.as_ptr(), buffer.as_mut_ptr(), size) })?;
56+
unsafe {
57+
buffer.set_len(size - 1);
58+
}
59+
Ok(PathBuf::from(OsString::from_vec(buffer)))
60+
})
61+
}
62+
63+
pub(crate) fn is_absolute(path: &Path) -> bool {
64+
path.has_root()
65+
}

library/std/src/sys/path/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
cfg_if::cfg_if! {
22
if #[cfg(target_os = "windows")] {
33
mod windows;
4+
mod windows_prefix;
45
pub use windows::*;
56
} else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
67
mod sgx;
@@ -11,6 +12,10 @@ cfg_if::cfg_if! {
1112
} else if #[cfg(target_os = "uefi")] {
1213
mod uefi;
1314
pub use uefi::*;
15+
} else if #[cfg(target_os = "cygwin")] {
16+
mod cygwin;
17+
mod windows_prefix;
18+
pub use cygwin::*;
1419
} else {
1520
mod unix;
1621
pub use unix::*;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub fn is_verbatim_sep(b: u8) -> bool {
1313
b == b'/'
1414
}
1515

16-
pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
16+
pub fn parse_prefix(_: &OsStr) -> Option<(usize, Prefix<'_>)> {
1717
None
1818
}
1919

library/std/src/sys/path/uefi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub fn is_verbatim_sep(b: u8) -> bool {
1717
b == b'\\'
1818
}
1919

20-
pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
20+
pub fn parse_prefix(_: &OsStr) -> Option<(usize, Prefix<'_>)> {
2121
None
2222
}
2323

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub fn is_verbatim_sep(b: u8) -> bool {
1313
}
1414

1515
#[inline]
16-
pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
16+
pub fn parse_prefix(_: &OsStr) -> Option<(usize, Prefix<'_>)> {
1717
None
1818
}
1919

library/std/src/sys/path/unsupported_backslash.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub fn is_verbatim_sep(b: u8) -> bool {
1414
b == b'\\'
1515
}
1616

17-
pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
17+
pub fn parse_prefix(_: &OsStr) -> Option<(usize, Prefix<'_>)> {
1818
None
1919
}
2020

0 commit comments

Comments
 (0)