std\sys\pal\windows/
os.rs

1//! Implementation of `std::os` functionality for Windows.
2
3#![allow(nonstandard_style)]
4
5#[cfg(test)]
6mod tests;
7
8use super::api;
9#[cfg(not(target_vendor = "uwp"))]
10use super::api::WinError;
11use crate::error::Error as StdError;
12use crate::ffi::{OsStr, OsString};
13use crate::os::windows::ffi::EncodeWide;
14use crate::os::windows::prelude::*;
15use crate::path::{self, PathBuf};
16use crate::sys::pal::{c, cvt};
17use crate::{fmt, io, ptr};
18
19pub fn errno() -> i32 {
20    api::get_last_error().code as i32
21}
22
23/// Gets a detailed string description for the given error number.
24pub fn error_string(mut errnum: i32) -> String {
25    let mut buf = [0 as c::WCHAR; 2048];
26
27    unsafe {
28        let mut module = ptr::null_mut();
29        let mut flags = 0;
30
31        // NTSTATUS errors may be encoded as HRESULT, which may returned from
32        // GetLastError. For more information about Windows error codes, see
33        // `[MS-ERREF]`: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a
34        if (errnum & c::FACILITY_NT_BIT as i32) != 0 {
35            // format according to https://support.microsoft.com/en-us/help/259693
36            const NTDLL_DLL: &[u16] = &[
37                'N' as _, 'T' as _, 'D' as _, 'L' as _, 'L' as _, '.' as _, 'D' as _, 'L' as _,
38                'L' as _, 0,
39            ];
40            module = c::GetModuleHandleW(NTDLL_DLL.as_ptr());
41
42            if !module.is_null() {
43                errnum ^= c::FACILITY_NT_BIT as i32;
44                flags = c::FORMAT_MESSAGE_FROM_HMODULE;
45            }
46        }
47
48        let res = c::FormatMessageW(
49            flags | c::FORMAT_MESSAGE_FROM_SYSTEM | c::FORMAT_MESSAGE_IGNORE_INSERTS,
50            module,
51            errnum as u32,
52            0,
53            buf.as_mut_ptr(),
54            buf.len() as u32,
55            ptr::null(),
56        ) as usize;
57        if res == 0 {
58            // Sometimes FormatMessageW can fail e.g., system doesn't like 0 as langId,
59            let fm_err = errno();
60            return format!("OS Error {errnum} (FormatMessageW() returned error {fm_err})");
61        }
62
63        match String::from_utf16(&buf[..res]) {
64            Ok(mut msg) => {
65                // Trim trailing CRLF inserted by FormatMessageW
66                let len = msg.trim_end().len();
67                msg.truncate(len);
68                msg
69            }
70            Err(..) => format!(
71                "OS Error {} (FormatMessageW() returned \
72                 invalid UTF-16)",
73                errnum
74            ),
75        }
76    }
77}
78
79pub struct SplitPaths<'a> {
80    data: EncodeWide<'a>,
81    must_yield: bool,
82}
83
84pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
85    SplitPaths { data: unparsed.encode_wide(), must_yield: true }
86}
87
88impl<'a> Iterator for SplitPaths<'a> {
89    type Item = PathBuf;
90    fn next(&mut self) -> Option<PathBuf> {
91        // On Windows, the PATH environment variable is semicolon separated.
92        // Double quotes are used as a way of introducing literal semicolons
93        // (since c:\some;dir is a valid Windows path). Double quotes are not
94        // themselves permitted in path names, so there is no way to escape a
95        // double quote. Quoted regions can appear in arbitrary locations, so
96        //
97        //   c:\foo;c:\som"e;di"r;c:\bar
98        //
99        // Should parse as [c:\foo, c:\some;dir, c:\bar].
100        //
101        // (The above is based on testing; there is no clear reference available
102        // for the grammar.)
103
104        let must_yield = self.must_yield;
105        self.must_yield = false;
106
107        let mut in_progress = Vec::new();
108        let mut in_quote = false;
109        for b in self.data.by_ref() {
110            if b == '"' as u16 {
111                in_quote = !in_quote;
112            } else if b == ';' as u16 && !in_quote {
113                self.must_yield = true;
114                break;
115            } else {
116                in_progress.push(b)
117            }
118        }
119
120        if !must_yield && in_progress.is_empty() {
121            None
122        } else {
123            Some(super::os2path(&in_progress))
124        }
125    }
126}
127
128#[derive(Debug)]
129pub struct JoinPathsError;
130
131pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
132where
133    I: Iterator<Item = T>,
134    T: AsRef<OsStr>,
135{
136    let mut joined = Vec::new();
137    let sep = b';' as u16;
138
139    for (i, path) in paths.enumerate() {
140        let path = path.as_ref();
141        if i > 0 {
142            joined.push(sep)
143        }
144        let v = path.encode_wide().collect::<Vec<u16>>();
145        if v.contains(&(b'"' as u16)) {
146            return Err(JoinPathsError);
147        } else if v.contains(&sep) {
148            joined.push(b'"' as u16);
149            joined.extend_from_slice(&v[..]);
150            joined.push(b'"' as u16);
151        } else {
152            joined.extend_from_slice(&v[..]);
153        }
154    }
155
156    Ok(OsStringExt::from_wide(&joined[..]))
157}
158
159impl fmt::Display for JoinPathsError {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        "path segment contains `\"`".fmt(f)
162    }
163}
164
165impl StdError for JoinPathsError {
166    #[allow(deprecated)]
167    fn description(&self) -> &str {
168        "failed to join paths"
169    }
170}
171
172pub fn current_exe() -> io::Result<PathBuf> {
173    super::fill_utf16_buf(
174        |buf, sz| unsafe { c::GetModuleFileNameW(ptr::null_mut(), buf, sz) },
175        super::os2path,
176    )
177}
178
179pub fn getcwd() -> io::Result<PathBuf> {
180    super::fill_utf16_buf(|buf, sz| unsafe { c::GetCurrentDirectoryW(sz, buf) }, super::os2path)
181}
182
183pub fn chdir(p: &path::Path) -> io::Result<()> {
184    let p: &OsStr = p.as_ref();
185    let mut p = p.encode_wide().collect::<Vec<_>>();
186    p.push(0);
187
188    cvt(unsafe { c::SetCurrentDirectoryW(p.as_ptr()) }).map(drop)
189}
190
191pub fn temp_dir() -> PathBuf {
192    super::fill_utf16_buf(|buf, sz| unsafe { c::GetTempPath2W(sz, buf) }, super::os2path).unwrap()
193}
194
195#[cfg(all(not(target_vendor = "uwp"), not(target_vendor = "win7")))]
196fn home_dir_crt() -> Option<PathBuf> {
197    unsafe {
198        // Defined in processthreadsapi.h.
199        const CURRENT_PROCESS_TOKEN: usize = -4_isize as usize;
200
201        super::fill_utf16_buf(
202            |buf, mut sz| {
203                // GetUserProfileDirectoryW does not quite use the usual protocol for
204                // negotiating the buffer size, so we have to translate.
205                match c::GetUserProfileDirectoryW(
206                    ptr::without_provenance_mut(CURRENT_PROCESS_TOKEN),
207                    buf,
208                    &mut sz,
209                ) {
210                    0 if api::get_last_error() != WinError::INSUFFICIENT_BUFFER => 0,
211                    0 => sz,
212                    _ => sz - 1, // sz includes the null terminator
213                }
214            },
215            super::os2path,
216        )
217        .ok()
218    }
219}
220
221#[cfg(target_vendor = "win7")]
222fn home_dir_crt() -> Option<PathBuf> {
223    unsafe {
224        use crate::sys::handle::Handle;
225
226        let me = c::GetCurrentProcess();
227        let mut token = ptr::null_mut();
228        if c::OpenProcessToken(me, c::TOKEN_READ, &mut token) == 0 {
229            return None;
230        }
231        let _handle = Handle::from_raw_handle(token);
232        super::fill_utf16_buf(
233            |buf, mut sz| {
234                match c::GetUserProfileDirectoryW(token, buf, &mut sz) {
235                    0 if api::get_last_error() != WinError::INSUFFICIENT_BUFFER => 0,
236                    0 => sz,
237                    _ => sz - 1, // sz includes the null terminator
238                }
239            },
240            super::os2path,
241        )
242        .ok()
243    }
244}
245
246#[cfg(target_vendor = "uwp")]
247fn home_dir_crt() -> Option<PathBuf> {
248    None
249}
250
251pub fn home_dir() -> Option<PathBuf> {
252    crate::env::var_os("USERPROFILE")
253        .filter(|s| !s.is_empty())
254        .map(PathBuf::from)
255        .or_else(home_dir_crt)
256}
257
258pub fn exit(code: i32) -> ! {
259    unsafe { c::ExitProcess(code as u32) }
260}
261
262pub fn getpid() -> u32 {
263    unsafe { c::GetCurrentProcessId() }
264}