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