about summary refs log tree commit diff
path: root/library/std/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-04-09 14:48:05 +0000
committerbors <bors@rust-lang.org>2025-04-09 14:48:05 +0000
commit48f89e7659678f91a68c0c2d868180a0036ab32d (patch)
treefd5e5fb7e10f18ac69d39d81da5ecb3240b14baf /library/std/src
parentf06e5c1e35bc5bc6131c6f8a0eb782097e3f28c3 (diff)
parent1aa6f70e3e9bf24cb45ae6f87e3b11fc51b1a9e7 (diff)
downloadrust-48f89e7659678f91a68c0c2d868180a0036ab32d.tar.gz
rust-48f89e7659678f91a68c0c2d868180a0036ab32d.zip
Auto merge of #139581 - matthiaskrgr:rollup-d6hph16, r=matthiaskrgr
Rollup of 7 pull requests

Successful merges:

 - #138869 (Try not to use verbatim paths in `Command::current_dir`)
 - #138993 (Make `cfg_match!` a semitransparent macro)
 - #139099 (Promise `array::from_fn` is generated in order of increasing indices)
 - #139364 (Make the compiler suggest actual paths instead of visible paths if the visible paths are through any doc hidden path.)
 - #139468 (Don't call `Span::with_parent` on the good path in `has_stashed_diagnostic`)
 - #139481 (Add job summary links to post-merge report)
 - #139573 (Miri subtree update)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'library/std/src')
-rw-r--r--library/std/src/sys/path/windows.rs43
-rw-r--r--library/std/src/sys/path/windows/tests.rs12
-rw-r--r--library/std/src/sys/process/windows.rs32
3 files changed, 83 insertions, 4 deletions
diff --git a/library/std/src/sys/path/windows.rs b/library/std/src/sys/path/windows.rs
index 1c534721916..6547ed9aa5f 100644
--- a/library/std/src/sys/path/windows.rs
+++ b/library/std/src/sys/path/windows.rs
@@ -350,3 +350,46 @@ pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
 pub(crate) fn is_absolute(path: &Path) -> bool {
     path.has_root() && path.prefix().is_some()
 }
+
+/// Test that the path is absolute, fully qualified and unchanged when processed by the Windows API.
+///
+/// For example:
+///
+/// - `C:\path\to\file` will return true.
+/// - `C:\path\to\nul` returns false because the Windows API will convert it to \\.\NUL
+/// - `C:\path\to\..\file` returns false because it will be resolved to `C:\path\file`.
+///
+/// This is a useful property because it means the path can be converted from and to and verbatim
+/// path just by changing the prefix.
+pub(crate) fn is_absolute_exact(path: &[u16]) -> bool {
+    // This is implemented by checking that passing the path through
+    // GetFullPathNameW does not change the path in any way.
+
+    // Windows paths are limited to i16::MAX length
+    // though the API here accepts a u32 for the length.
+    if path.is_empty() || path.len() > u32::MAX as usize || path.last() != Some(&0) {
+        return false;
+    }
+    // The path returned by `GetFullPathNameW` must be the same length as the
+    // given path, otherwise they're not equal.
+    let buffer_len = path.len();
+    let mut new_path = Vec::with_capacity(buffer_len);
+    let result = unsafe {
+        c::GetFullPathNameW(
+            path.as_ptr(),
+            new_path.capacity() as u32,
+            new_path.as_mut_ptr(),
+            crate::ptr::null_mut(),
+        )
+    };
+    // Note: if non-zero, the returned result is the length of the buffer without the null termination
+    if result == 0 || result as usize != buffer_len - 1 {
+        false
+    } else {
+        // SAFETY: `GetFullPathNameW` initialized `result` bytes and does not exceed `nBufferLength - 1` (capacity).
+        unsafe {
+            new_path.set_len((result as usize) + 1);
+        }
+        path == &new_path
+    }
+}
diff --git a/library/std/src/sys/path/windows/tests.rs b/library/std/src/sys/path/windows/tests.rs
index f2a60e30bc6..9eb79203dca 100644
--- a/library/std/src/sys/path/windows/tests.rs
+++ b/library/std/src/sys/path/windows/tests.rs
@@ -135,3 +135,15 @@ fn broken_unc_path() {
     assert_eq!(components.next(), Some(Component::Normal("foo".as_ref())));
     assert_eq!(components.next(), Some(Component::Normal("bar".as_ref())));
 }
+
+#[test]
+fn test_is_absolute_exact() {
+    use crate::sys::pal::api::wide_str;
+    // These paths can be made verbatim by only changing their prefix.
+    assert!(is_absolute_exact(wide_str!(r"C:\path\to\file")));
+    assert!(is_absolute_exact(wide_str!(r"\\server\share\path\to\file")));
+    // These paths change more substantially
+    assert!(!is_absolute_exact(wide_str!(r"C:\path\to\..\file")));
+    assert!(!is_absolute_exact(wide_str!(r"\\server\share\path\to\..\file")));
+    assert!(!is_absolute_exact(wide_str!(r"C:\path\to\NUL"))); // Converts to \\.\NUL
+}
diff --git a/library/std/src/sys/process/windows.rs b/library/std/src/sys/process/windows.rs
index 06c15e08f3f..4cfdf908c58 100644
--- a/library/std/src/sys/process/windows.rs
+++ b/library/std/src/sys/process/windows.rs
@@ -19,7 +19,7 @@ use crate::sys::args::{self, Arg};
 use crate::sys::c::{self, EXIT_FAILURE, EXIT_SUCCESS};
 use crate::sys::fs::{File, OpenOptions};
 use crate::sys::handle::Handle;
-use crate::sys::pal::api::{self, WinError};
+use crate::sys::pal::api::{self, WinError, utf16};
 use crate::sys::pal::{ensure_no_nuls, fill_utf16_buf};
 use crate::sys::pipe::{self, AnonPipe};
 use crate::sys::{cvt, path, stdio};
@@ -880,9 +880,33 @@ fn make_envp(maybe_env: Option<BTreeMap<EnvKey, OsString>>) -> io::Result<(*mut
 fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> {
     match d {
         Some(dir) => {
-            let mut dir_str: Vec<u16> = ensure_no_nuls(dir)?.encode_wide().collect();
-            dir_str.push(0);
-            Ok((dir_str.as_ptr(), dir_str))
+            let mut dir_str: Vec<u16> = ensure_no_nuls(dir)?.encode_wide().chain([0]).collect();
+            // Try to remove the `\\?\` prefix, if any.
+            // This is necessary because the current directory does not support verbatim paths.
+            // However. this can only be done if it doesn't change how the path will be resolved.
+            let ptr = if dir_str.starts_with(utf16!(r"\\?\UNC")) {
+                // Turn the `C` in `UNC` into a `\` so we can then use `\\rest\of\path`.
+                let start = r"\\?\UN".len();
+                dir_str[start] = b'\\' as u16;
+                if path::is_absolute_exact(&dir_str[start..]) {
+                    dir_str[start..].as_ptr()
+                } else {
+                    // Revert the above change.
+                    dir_str[start] = b'C' as u16;
+                    dir_str.as_ptr()
+                }
+            } else if dir_str.starts_with(utf16!(r"\\?\")) {
+                // Strip the leading `\\?\`
+                let start = r"\\?\".len();
+                if path::is_absolute_exact(&dir_str[start..]) {
+                    dir_str[start..].as_ptr()
+                } else {
+                    dir_str.as_ptr()
+                }
+            } else {
+                dir_str.as_ptr()
+            };
+            Ok((ptr, dir_str))
         }
         None => Ok((ptr::null(), Vec::new())),
     }