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
8#[cfg(not(target_vendor = "uwp"))]
9use super::api::WinError;
10use super::{api, to_u16s};
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::{c, cvt};
17use crate::{fmt, io, ptr, slice};
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 Env {
80    base: *mut c::WCHAR,
81    iter: EnvIterator,
82}
83
84// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when <OsStr as Debug>::fmt matches <str as Debug>::fmt.
85pub struct EnvStrDebug<'a> {
86    iter: &'a EnvIterator,
87}
88
89impl fmt::Debug for EnvStrDebug<'_> {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        let Self { iter } = self;
92        let iter: EnvIterator = (*iter).clone();
93        let mut list = f.debug_list();
94        for (a, b) in iter {
95            list.entry(&(a.to_str().unwrap(), b.to_str().unwrap()));
96        }
97        list.finish()
98    }
99}
100
101impl Env {
102    pub fn str_debug(&self) -> impl fmt::Debug + '_ {
103        let Self { base: _, iter } = self;
104        EnvStrDebug { iter }
105    }
106}
107
108impl fmt::Debug for Env {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        let Self { base: _, iter } = self;
111        f.debug_list().entries(iter.clone()).finish()
112    }
113}
114
115impl Iterator for Env {
116    type Item = (OsString, OsString);
117
118    fn next(&mut self) -> Option<(OsString, OsString)> {
119        let Self { base: _, iter } = self;
120        iter.next()
121    }
122}
123
124#[derive(Clone)]
125struct EnvIterator(*mut c::WCHAR);
126
127impl Iterator for EnvIterator {
128    type Item = (OsString, OsString);
129
130    fn next(&mut self) -> Option<(OsString, OsString)> {
131        let Self(cur) = self;
132        loop {
133            unsafe {
134                if **cur == 0 {
135                    return None;
136                }
137                let p = *cur as *const u16;
138                let mut len = 0;
139                while *p.add(len) != 0 {
140                    len += 1;
141                }
142                let s = slice::from_raw_parts(p, len);
143                *cur = cur.add(len + 1);
144
145                // Windows allows environment variables to start with an equals
146                // symbol (in any other position, this is the separator between
147                // variable name and value). Since`s` has at least length 1 at
148                // this point (because the empty string terminates the array of
149                // environment variables), we can safely slice.
150                let pos = match s[1..].iter().position(|&u| u == b'=' as u16).map(|p| p + 1) {
151                    Some(p) => p,
152                    None => continue,
153                };
154                return Some((
155                    OsStringExt::from_wide(&s[..pos]),
156                    OsStringExt::from_wide(&s[pos + 1..]),
157                ));
158            }
159        }
160    }
161}
162
163impl Drop for Env {
164    fn drop(&mut self) {
165        unsafe {
166            c::FreeEnvironmentStringsW(self.base);
167        }
168    }
169}
170
171pub fn env() -> Env {
172    unsafe {
173        let ch = c::GetEnvironmentStringsW();
174        if ch.is_null() {
175            panic!("failure getting env string from OS: {}", io::Error::last_os_error());
176        }
177        Env { base: ch, iter: EnvIterator(ch) }
178    }
179}
180
181pub struct SplitPaths<'a> {
182    data: EncodeWide<'a>,
183    must_yield: bool,
184}
185
186pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
187    SplitPaths { data: unparsed.encode_wide(), must_yield: true }
188}
189
190impl<'a> Iterator for SplitPaths<'a> {
191    type Item = PathBuf;
192    fn next(&mut self) -> Option<PathBuf> {
193        // On Windows, the PATH environment variable is semicolon separated.
194        // Double quotes are used as a way of introducing literal semicolons
195        // (since c:\some;dir is a valid Windows path). Double quotes are not
196        // themselves permitted in path names, so there is no way to escape a
197        // double quote. Quoted regions can appear in arbitrary locations, so
198        //
199        //   c:\foo;c:\som"e;di"r;c:\bar
200        //
201        // Should parse as [c:\foo, c:\some;dir, c:\bar].
202        //
203        // (The above is based on testing; there is no clear reference available
204        // for the grammar.)
205
206        let must_yield = self.must_yield;
207        self.must_yield = false;
208
209        let mut in_progress = Vec::new();
210        let mut in_quote = false;
211        for b in self.data.by_ref() {
212            if b == '"' as u16 {
213                in_quote = !in_quote;
214            } else if b == ';' as u16 && !in_quote {
215                self.must_yield = true;
216                break;
217            } else {
218                in_progress.push(b)
219            }
220        }
221
222        if !must_yield && in_progress.is_empty() {
223            None
224        } else {
225            Some(super::os2path(&in_progress))
226        }
227    }
228}
229
230#[derive(Debug)]
231pub struct JoinPathsError;
232
233pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
234where
235    I: Iterator<Item = T>,
236    T: AsRef<OsStr>,
237{
238    let mut joined = Vec::new();
239    let sep = b';' as u16;
240
241    for (i, path) in paths.enumerate() {
242        let path = path.as_ref();
243        if i > 0 {
244            joined.push(sep)
245        }
246        let v = path.encode_wide().collect::<Vec<u16>>();
247        if v.contains(&(b'"' as u16)) {
248            return Err(JoinPathsError);
249        } else if v.contains(&sep) {
250            joined.push(b'"' as u16);
251            joined.extend_from_slice(&v[..]);
252            joined.push(b'"' as u16);
253        } else {
254            joined.extend_from_slice(&v[..]);
255        }
256    }
257
258    Ok(OsStringExt::from_wide(&joined[..]))
259}
260
261impl fmt::Display for JoinPathsError {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        "path segment contains `\"`".fmt(f)
264    }
265}
266
267impl StdError for JoinPathsError {
268    #[allow(deprecated)]
269    fn description(&self) -> &str {
270        "failed to join paths"
271    }
272}
273
274pub fn current_exe() -> io::Result<PathBuf> {
275    super::fill_utf16_buf(
276        |buf, sz| unsafe { c::GetModuleFileNameW(ptr::null_mut(), buf, sz) },
277        super::os2path,
278    )
279}
280
281pub fn getcwd() -> io::Result<PathBuf> {
282    super::fill_utf16_buf(|buf, sz| unsafe { c::GetCurrentDirectoryW(sz, buf) }, super::os2path)
283}
284
285pub fn chdir(p: &path::Path) -> io::Result<()> {
286    let p: &OsStr = p.as_ref();
287    let mut p = p.encode_wide().collect::<Vec<_>>();
288    p.push(0);
289
290    cvt(unsafe { c::SetCurrentDirectoryW(p.as_ptr()) }).map(drop)
291}
292
293pub fn getenv(k: &OsStr) -> Option<OsString> {
294    let k = to_u16s(k).ok()?;
295    super::fill_utf16_buf(
296        |buf, sz| unsafe { c::GetEnvironmentVariableW(k.as_ptr(), buf, sz) },
297        OsStringExt::from_wide,
298    )
299    .ok()
300}
301
302pub unsafe fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
303    // SAFETY: We ensure that k and v are null-terminated wide strings.
304    unsafe {
305        let k = to_u16s(k)?;
306        let v = to_u16s(v)?;
307
308        cvt(c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr())).map(drop)
309    }
310}
311
312pub unsafe fn unsetenv(n: &OsStr) -> io::Result<()> {
313    // SAFETY: We ensure that v is a null-terminated wide strings.
314    unsafe {
315        let v = to_u16s(n)?;
316        cvt(c::SetEnvironmentVariableW(v.as_ptr(), ptr::null())).map(drop)
317    }
318}
319
320pub fn temp_dir() -> PathBuf {
321    super::fill_utf16_buf(|buf, sz| unsafe { c::GetTempPath2W(sz, buf) }, super::os2path).unwrap()
322}
323
324#[cfg(all(not(target_vendor = "uwp"), not(target_vendor = "win7")))]
325fn home_dir_crt() -> Option<PathBuf> {
326    unsafe {
327        // Defined in processthreadsapi.h.
328        const CURRENT_PROCESS_TOKEN: usize = -4_isize as usize;
329
330        super::fill_utf16_buf(
331            |buf, mut sz| {
332                // GetUserProfileDirectoryW does not quite use the usual protocol for
333                // negotiating the buffer size, so we have to translate.
334                match c::GetUserProfileDirectoryW(
335                    ptr::without_provenance_mut(CURRENT_PROCESS_TOKEN),
336                    buf,
337                    &mut sz,
338                ) {
339                    0 if api::get_last_error() != WinError::INSUFFICIENT_BUFFER => 0,
340                    0 => sz,
341                    _ => sz - 1, // sz includes the null terminator
342                }
343            },
344            super::os2path,
345        )
346        .ok()
347    }
348}
349
350#[cfg(target_vendor = "win7")]
351fn home_dir_crt() -> Option<PathBuf> {
352    unsafe {
353        use crate::sys::handle::Handle;
354
355        let me = c::GetCurrentProcess();
356        let mut token = ptr::null_mut();
357        if c::OpenProcessToken(me, c::TOKEN_READ, &mut token) == 0 {
358            return None;
359        }
360        let _handle = Handle::from_raw_handle(token);
361        super::fill_utf16_buf(
362            |buf, mut sz| {
363                match c::GetUserProfileDirectoryW(token, buf, &mut sz) {
364                    0 if api::get_last_error() != WinError::INSUFFICIENT_BUFFER => 0,
365                    0 => sz,
366                    _ => sz - 1, // sz includes the null terminator
367                }
368            },
369            super::os2path,
370        )
371        .ok()
372    }
373}
374
375#[cfg(target_vendor = "uwp")]
376fn home_dir_crt() -> Option<PathBuf> {
377    None
378}
379
380pub fn home_dir() -> Option<PathBuf> {
381    crate::env::var_os("USERPROFILE")
382        .filter(|s| !s.is_empty())
383        .map(PathBuf::from)
384        .or_else(home_dir_crt)
385}
386
387pub fn exit(code: i32) -> ! {
388    unsafe { c::ExitProcess(code as u32) }
389}
390
391pub fn getpid() -> u32 {
392    unsafe { c::GetCurrentProcessId() }
393}