]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/windows/os.rs
Rollup merge of #89876 - AlexApps99:const_ops, r=oli-obk
[rust.git] / library / std / src / sys / windows / os.rs
1 //! Implementation of `std::os` functionality for Windows.
2
3 #![allow(nonstandard_style)]
4
5 #[cfg(test)]
6 mod tests;
7
8 use crate::os::windows::prelude::*;
9
10 use crate::error::Error as StdError;
11 use crate::ffi::{OsStr, OsString};
12 use crate::fmt;
13 use crate::io;
14 use crate::os::windows::ffi::EncodeWide;
15 use crate::path::{self, PathBuf};
16 use crate::ptr;
17 use crate::slice;
18 use crate::sys::{c, cvt};
19
20 use super::to_u16s;
21
22 pub fn errno() -> i32 {
23     unsafe { c::GetLastError() as i32 }
24 }
25
26 /// Gets a detailed string description for the given error number.
27 pub fn error_string(mut errnum: i32) -> String {
28     // This value is calculated from the macro
29     // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT)
30     let langId = 0x0800 as c::DWORD;
31
32     let mut buf = [0 as c::WCHAR; 2048];
33
34     unsafe {
35         let mut module = ptr::null_mut();
36         let mut flags = 0;
37
38         // NTSTATUS errors may be encoded as HRESULT, which may returned from
39         // GetLastError. For more information about Windows error codes, see
40         // `[MS-ERREF]`: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a
41         if (errnum & c::FACILITY_NT_BIT as i32) != 0 {
42             // format according to https://support.microsoft.com/en-us/help/259693
43             const NTDLL_DLL: &[u16] = &[
44                 'N' as _, 'T' as _, 'D' as _, 'L' as _, 'L' as _, '.' as _, 'D' as _, 'L' as _,
45                 'L' as _, 0,
46             ];
47             module = c::GetModuleHandleW(NTDLL_DLL.as_ptr());
48
49             if !module.is_null() {
50                 errnum ^= c::FACILITY_NT_BIT as i32;
51                 flags = c::FORMAT_MESSAGE_FROM_HMODULE;
52             }
53         }
54
55         let res = c::FormatMessageW(
56             flags | c::FORMAT_MESSAGE_FROM_SYSTEM | c::FORMAT_MESSAGE_IGNORE_INSERTS,
57             module,
58             errnum as c::DWORD,
59             langId,
60             buf.as_mut_ptr(),
61             buf.len() as c::DWORD,
62             ptr::null(),
63         ) as usize;
64         if res == 0 {
65             // Sometimes FormatMessageW can fail e.g., system doesn't like langId,
66             let fm_err = errno();
67             return format!("OS Error {} (FormatMessageW() returned error {})", errnum, fm_err);
68         }
69
70         match String::from_utf16(&buf[..res]) {
71             Ok(mut msg) => {
72                 // Trim trailing CRLF inserted by FormatMessageW
73                 let len = msg.trim_end().len();
74                 msg.truncate(len);
75                 msg
76             }
77             Err(..) => format!(
78                 "OS Error {} (FormatMessageW() returned \
79                  invalid UTF-16)",
80                 errnum
81             ),
82         }
83     }
84 }
85
86 pub struct Env {
87     base: c::LPWCH,
88     cur: c::LPWCH,
89 }
90
91 impl Iterator for Env {
92     type Item = (OsString, OsString);
93
94     fn next(&mut self) -> Option<(OsString, OsString)> {
95         loop {
96             unsafe {
97                 if *self.cur == 0 {
98                     return None;
99                 }
100                 let p = self.cur as *const u16;
101                 let mut len = 0;
102                 while *p.offset(len) != 0 {
103                     len += 1;
104                 }
105                 let s = slice::from_raw_parts(p, len as usize);
106                 self.cur = self.cur.offset(len + 1);
107
108                 // Windows allows environment variables to start with an equals
109                 // symbol (in any other position, this is the separator between
110                 // variable name and value). Since`s` has at least length 1 at
111                 // this point (because the empty string terminates the array of
112                 // environment variables), we can safely slice.
113                 let pos = match s[1..].iter().position(|&u| u == b'=' as u16).map(|p| p + 1) {
114                     Some(p) => p,
115                     None => continue,
116                 };
117                 return Some((
118                     OsStringExt::from_wide(&s[..pos]),
119                     OsStringExt::from_wide(&s[pos + 1..]),
120                 ));
121             }
122         }
123     }
124 }
125
126 impl Drop for Env {
127     fn drop(&mut self) {
128         unsafe {
129             c::FreeEnvironmentStringsW(self.base);
130         }
131     }
132 }
133
134 pub fn env() -> Env {
135     unsafe {
136         let ch = c::GetEnvironmentStringsW();
137         if ch as usize == 0 {
138             panic!("failure getting env string from OS: {}", io::Error::last_os_error());
139         }
140         Env { base: ch, cur: ch }
141     }
142 }
143
144 pub struct SplitPaths<'a> {
145     data: EncodeWide<'a>,
146     must_yield: bool,
147 }
148
149 pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
150     SplitPaths { data: unparsed.encode_wide(), must_yield: true }
151 }
152
153 impl<'a> Iterator for SplitPaths<'a> {
154     type Item = PathBuf;
155     fn next(&mut self) -> Option<PathBuf> {
156         // On Windows, the PATH environment variable is semicolon separated.
157         // Double quotes are used as a way of introducing literal semicolons
158         // (since c:\some;dir is a valid Windows path). Double quotes are not
159         // themselves permitted in path names, so there is no way to escape a
160         // double quote.  Quoted regions can appear in arbitrary locations, so
161         //
162         //   c:\foo;c:\som"e;di"r;c:\bar
163         //
164         // Should parse as [c:\foo, c:\some;dir, c:\bar].
165         //
166         // (The above is based on testing; there is no clear reference available
167         // for the grammar.)
168
169         let must_yield = self.must_yield;
170         self.must_yield = false;
171
172         let mut in_progress = Vec::new();
173         let mut in_quote = false;
174         for b in self.data.by_ref() {
175             if b == '"' as u16 {
176                 in_quote = !in_quote;
177             } else if b == ';' as u16 && !in_quote {
178                 self.must_yield = true;
179                 break;
180             } else {
181                 in_progress.push(b)
182             }
183         }
184
185         if !must_yield && in_progress.is_empty() {
186             None
187         } else {
188             Some(super::os2path(&in_progress))
189         }
190     }
191 }
192
193 #[derive(Debug)]
194 pub struct JoinPathsError;
195
196 pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
197 where
198     I: Iterator<Item = T>,
199     T: AsRef<OsStr>,
200 {
201     let mut joined = Vec::new();
202     let sep = b';' as u16;
203
204     for (i, path) in paths.enumerate() {
205         let path = path.as_ref();
206         if i > 0 {
207             joined.push(sep)
208         }
209         let v = path.encode_wide().collect::<Vec<u16>>();
210         if v.contains(&(b'"' as u16)) {
211             return Err(JoinPathsError);
212         } else if v.contains(&sep) {
213             joined.push(b'"' as u16);
214             joined.extend_from_slice(&v[..]);
215             joined.push(b'"' as u16);
216         } else {
217             joined.extend_from_slice(&v[..]);
218         }
219     }
220
221     Ok(OsStringExt::from_wide(&joined[..]))
222 }
223
224 impl fmt::Display for JoinPathsError {
225     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226         "path segment contains `\"`".fmt(f)
227     }
228 }
229
230 impl StdError for JoinPathsError {
231     #[allow(deprecated)]
232     fn description(&self) -> &str {
233         "failed to join paths"
234     }
235 }
236
237 pub fn current_exe() -> io::Result<PathBuf> {
238     super::fill_utf16_buf(
239         |buf, sz| unsafe { c::GetModuleFileNameW(ptr::null_mut(), buf, sz) },
240         super::os2path,
241     )
242 }
243
244 pub fn getcwd() -> io::Result<PathBuf> {
245     super::fill_utf16_buf(|buf, sz| unsafe { c::GetCurrentDirectoryW(sz, buf) }, super::os2path)
246 }
247
248 pub fn chdir(p: &path::Path) -> io::Result<()> {
249     let p: &OsStr = p.as_ref();
250     let mut p = p.encode_wide().collect::<Vec<_>>();
251     p.push(0);
252
253     cvt(unsafe { c::SetCurrentDirectoryW(p.as_ptr()) }).map(drop)
254 }
255
256 pub fn getenv(k: &OsStr) -> Option<OsString> {
257     let k = to_u16s(k).ok()?;
258     super::fill_utf16_buf(
259         |buf, sz| unsafe { c::GetEnvironmentVariableW(k.as_ptr(), buf, sz) },
260         |buf| OsStringExt::from_wide(buf),
261     )
262     .ok()
263 }
264
265 pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
266     let k = to_u16s(k)?;
267     let v = to_u16s(v)?;
268
269     cvt(unsafe { c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr()) }).map(drop)
270 }
271
272 pub fn unsetenv(n: &OsStr) -> io::Result<()> {
273     let v = to_u16s(n)?;
274     cvt(unsafe { c::SetEnvironmentVariableW(v.as_ptr(), ptr::null()) }).map(drop)
275 }
276
277 pub fn temp_dir() -> PathBuf {
278     super::fill_utf16_buf(|buf, sz| unsafe { c::GetTempPathW(sz, buf) }, super::os2path).unwrap()
279 }
280
281 #[cfg(not(target_vendor = "uwp"))]
282 fn home_dir_crt() -> Option<PathBuf> {
283     unsafe {
284         // The magic constant -4 can be used as the token passed to GetUserProfileDirectoryW below
285         // instead of us having to go through these multiple steps to get a token. However this is
286         // not implemented on Windows 7, only Windows 8 and up. When we drop support for Windows 7
287         // we can simplify this code. See #90144 for details.
288         use crate::sys::handle::Handle;
289
290         let me = c::GetCurrentProcess();
291         let mut token = ptr::null_mut();
292         if c::OpenProcessToken(me, c::TOKEN_READ, &mut token) == 0 {
293             return None;
294         }
295         let _handle = Handle::from_raw_handle(token);
296         super::fill_utf16_buf(
297             |buf, mut sz| {
298                 match c::GetUserProfileDirectoryW(token, buf, &mut sz) {
299                     0 if c::GetLastError() != c::ERROR_INSUFFICIENT_BUFFER => 0,
300                     0 => sz,
301                     _ => sz - 1, // sz includes the null terminator
302                 }
303             },
304             super::os2path,
305         )
306         .ok()
307     }
308 }
309
310 #[cfg(target_vendor = "uwp")]
311 fn home_dir_crt() -> Option<PathBuf> {
312     None
313 }
314
315 pub fn home_dir() -> Option<PathBuf> {
316     crate::env::var_os("HOME")
317         .or_else(|| crate::env::var_os("USERPROFILE"))
318         .map(PathBuf::from)
319         .or_else(|| home_dir_crt())
320 }
321
322 pub fn exit(code: i32) -> ! {
323     unsafe { c::ExitProcess(code as c::UINT) }
324 }
325
326 pub fn getpid() -> u32 {
327     unsafe { c::GetCurrentProcessId() as u32 }
328 }