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