Skip to main content

std\sys\paths/
windows.rs

1//! Implementation of `std::os` functionality for Windows.
2
3#![allow(nonstandard_style)]
4
5use crate::ffi::{OsStr, OsString};
6use crate::os::windows::ffi::EncodeWide;
7use crate::os::windows::prelude::*;
8use crate::path::{self, PathBuf};
9#[cfg(not(target_vendor = "uwp"))]
10use crate::sys::pal::api::WinError;
11use crate::sys::pal::{api, c, cvt, fill_utf16_buf, os2path};
12use crate::{fmt, io, ptr};
13
14pub struct SplitPaths<'a> {
15    data: EncodeWide<'a>,
16    must_yield: bool,
17}
18
19pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
20    SplitPaths { data: unparsed.encode_wide(), must_yield: true }
21}
22
23impl<'a> Iterator for SplitPaths<'a> {
24    type Item = PathBuf;
25    fn next(&mut self) -> Option<PathBuf> {
26        // On Windows, the PATH environment variable is semicolon separated.
27        // Double quotes are used as a way of introducing literal semicolons
28        // (since c:\some;dir is a valid Windows path). Double quotes are not
29        // themselves permitted in path names, so there is no way to escape a
30        // double quote. Quoted regions can appear in arbitrary locations, so
31        //
32        //   c:\foo;c:\som"e;di"r;c:\bar
33        //
34        // Should parse as [c:\foo, c:\some;dir, c:\bar].
35        //
36        // (The above is based on testing; there is no clear reference available
37        // for the grammar.)
38
39        let must_yield = self.must_yield;
40        self.must_yield = false;
41
42        let mut in_progress = Vec::new();
43        let mut in_quote = false;
44        for b in self.data.by_ref() {
45            if b == '"' as u16 {
46                in_quote = !in_quote;
47            } else if b == ';' as u16 && !in_quote {
48                self.must_yield = true;
49                break;
50            } else {
51                in_progress.push(b)
52            }
53        }
54
55        if !must_yield && in_progress.is_empty() { None } else { Some(os2path(&in_progress)) }
56    }
57}
58
59#[derive(Debug)]
60pub struct JoinPathsError;
61
62pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
63where
64    I: Iterator<Item = T>,
65    T: AsRef<OsStr>,
66{
67    let mut joined = Vec::new();
68    let sep = b';' as u16;
69
70    for (i, path) in paths.enumerate() {
71        let path = path.as_ref();
72        if i > 0 {
73            joined.push(sep)
74        }
75        let v = path.encode_wide().collect::<Vec<u16>>();
76        if v.contains(&(b'"' as u16)) {
77            return Err(JoinPathsError);
78        } else if v.contains(&sep) {
79            joined.push(b'"' as u16);
80            joined.extend_from_slice(&v[..]);
81            joined.push(b'"' as u16);
82        } else {
83            joined.extend_from_slice(&v[..]);
84        }
85    }
86
87    Ok(OsStringExt::from_wide(&joined[..]))
88}
89
90impl fmt::Display for JoinPathsError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        "path segment contains `\"`".fmt(f)
93    }
94}
95
96impl crate::error::Error for JoinPathsError {}
97
98pub fn current_exe() -> io::Result<PathBuf> {
99    fill_utf16_buf(|buf, sz| unsafe { c::GetModuleFileNameW(ptr::null_mut(), buf, sz) }, os2path)
100}
101
102pub fn getcwd() -> io::Result<PathBuf> {
103    fill_utf16_buf(|buf, sz| unsafe { c::GetCurrentDirectoryW(sz, buf) }, os2path)
104}
105
106pub fn chdir(p: &path::Path) -> io::Result<()> {
107    let p: &OsStr = p.as_ref();
108    let mut p = p.encode_wide().collect::<Vec<_>>();
109    p.push(0);
110
111    cvt(unsafe { c::SetCurrentDirectoryW(p.as_ptr()) }).map(drop)
112}
113
114pub fn temp_dir() -> PathBuf {
115    fill_utf16_buf(|buf, sz| unsafe { c::GetTempPath2W(sz, buf) }, os2path).unwrap()
116}
117
118#[cfg(all(not(target_vendor = "uwp"), not(target_vendor = "win7")))]
119fn home_dir_crt() -> Option<PathBuf> {
120    unsafe {
121        // Defined in processthreadsapi.h.
122        const CURRENT_PROCESS_TOKEN: usize = -4_isize as usize;
123
124        fill_utf16_buf(
125            |buf, mut sz| {
126                // GetUserProfileDirectoryW does not quite use the usual protocol for
127                // negotiating the buffer size, so we have to translate.
128                match c::GetUserProfileDirectoryW(
129                    ptr::without_provenance_mut(CURRENT_PROCESS_TOKEN),
130                    buf,
131                    &mut sz,
132                ) {
133                    0 if api::get_last_error() != WinError::INSUFFICIENT_BUFFER => 0,
134                    0 => sz,
135                    _ => sz - 1, // sz includes the null terminator
136                }
137            },
138            os2path,
139        )
140        .ok()
141    }
142}
143
144#[cfg(target_vendor = "win7")]
145fn home_dir_crt() -> Option<PathBuf> {
146    unsafe {
147        use crate::sys::handle::Handle;
148
149        let me = c::GetCurrentProcess();
150        let mut token = ptr::null_mut();
151        if c::OpenProcessToken(me, c::TOKEN_READ, &mut token) == 0 {
152            return None;
153        }
154        let _handle = Handle::from_raw_handle(token);
155        fill_utf16_buf(
156            |buf, mut sz| {
157                match c::GetUserProfileDirectoryW(token, buf, &mut sz) {
158                    0 if api::get_last_error() != WinError::INSUFFICIENT_BUFFER => 0,
159                    0 => sz,
160                    _ => sz - 1, // sz includes the null terminator
161                }
162            },
163            os2path,
164        )
165        .ok()
166    }
167}
168
169#[cfg(target_vendor = "uwp")]
170fn home_dir_crt() -> Option<PathBuf> {
171    None
172}
173
174pub fn home_dir() -> Option<PathBuf> {
175    crate::env::var_os("USERPROFILE")
176        .filter(|s| !s.is_empty())
177        .map(PathBuf::from)
178        .or_else(home_dir_crt)
179}