1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Implementation of `std::os` functionality for Windows
15 use os::windows::prelude::*;
17 use error::Error as StdError;
18 use ffi::{OsString, OsStr};
21 use os::windows::ffi::EncodeWide;
22 use path::{self, PathBuf};
26 use sys::handle::Handle;
30 pub fn errno() -> i32 {
31 unsafe { c::GetLastError() as i32 }
34 /// Gets a detailed string description for the given error number.
35 pub fn error_string(mut errnum: i32) -> String {
36 // This value is calculated from the macro
37 // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT)
38 let langId = 0x0800 as c::DWORD;
40 let mut buf = [0 as c::WCHAR; 2048];
43 let mut module = ptr::null_mut();
46 // NTSTATUS errors may be encoded as HRESULT, which may returned from
47 // GetLastError. For more information about Windows error codes, see
48 // `[MS-ERREF]`: https://msdn.microsoft.com/en-us/library/cc231198.aspx
49 if (errnum & c::FACILITY_NT_BIT as i32) != 0 {
50 // format according to https://support.microsoft.com/en-us/help/259693
51 const NTDLL_DLL: &'static [u16] = &['N' as _, 'T' as _, 'D' as _, 'L' as _, 'L' as _,
52 '.' as _, 'D' as _, 'L' as _, 'L' as _, 0];
53 module = c::GetModuleHandleW(NTDLL_DLL.as_ptr());
55 if module != ptr::null_mut() {
56 errnum ^= c::FACILITY_NT_BIT as i32;
57 flags = c::FORMAT_MESSAGE_FROM_HMODULE;
61 let res = c::FormatMessageW(flags | c::FORMAT_MESSAGE_FROM_SYSTEM |
62 c::FORMAT_MESSAGE_IGNORE_INSERTS,
67 buf.len() as c::DWORD,
68 ptr::null()) as usize;
70 // Sometimes FormatMessageW can fail e.g. system doesn't like langId,
72 return format!("OS Error {} (FormatMessageW() returned error {})",
76 match String::from_utf16(&buf[..res]) {
78 // Trim trailing CRLF inserted by FormatMessageW
79 let len = msg.trim_right().len();
83 Err(..) => format!("OS Error {} (FormatMessageW() returned \
84 invalid UTF-16)", errnum),
94 impl Iterator for Env {
95 type Item = (OsString, OsString);
97 fn next(&mut self) -> Option<(OsString, OsString)> {
100 if *self.cur == 0 { return None }
101 let p = &*self.cur as *const u16;
103 while *p.offset(len) != 0 {
106 let s = slice::from_raw_parts(p, len as usize);
107 self.cur = self.cur.offset(len + 1);
109 // Windows allows environment variables to start with an equals
110 // symbol (in any other position, this is the separator between
111 // variable name and value). Since`s` has at least length 1 at
112 // this point (because the empty string terminates the array of
113 // environment variables), we can safely slice.
114 let pos = match s[1..].iter().position(|&u| u == b'=' as u16).map(|p| p + 1) {
119 OsStringExt::from_wide(&s[..pos]),
120 OsStringExt::from_wide(&s[pos+1..]),
129 unsafe { c::FreeEnvironmentStringsW(self.base); }
133 pub fn env() -> Env {
135 let ch = c::GetEnvironmentStringsW();
136 if ch as usize == 0 {
137 panic!("failure getting env string from OS: {}",
138 io::Error::last_os_error());
140 Env { base: ch, cur: ch }
144 pub struct SplitPaths<'a> {
145 data: EncodeWide<'a>,
149 pub fn split_paths(unparsed: &OsStr) -> SplitPaths {
151 data: unparsed.encode_wide(),
156 impl<'a> Iterator for SplitPaths<'a> {
158 fn next(&mut self) -> Option<PathBuf> {
159 // On Windows, the PATH environment variable is semicolon separated.
160 // Double quotes are used as a way of introducing literal semicolons
161 // (since c:\some;dir is a valid Windows path). Double quotes are not
162 // themselves permitted in path names, so there is no way to escape a
163 // double quote. Quoted regions can appear in arbitrary locations, so
165 // c:\foo;c:\som"e;di"r;c:\bar
167 // Should parse as [c:\foo, c:\some;dir, c:\bar].
169 // (The above is based on testing; there is no clear reference available
173 let must_yield = self.must_yield;
174 self.must_yield = false;
176 let mut in_progress = Vec::new();
177 let mut in_quote = false;
178 for b in self.data.by_ref() {
180 in_quote = !in_quote;
181 } else if b == ';' as u16 && !in_quote {
182 self.must_yield = true;
189 if !must_yield && in_progress.is_empty() {
192 Some(super::os2path(&in_progress))
198 pub struct JoinPathsError;
200 pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
201 where I: Iterator<Item=T>, T: AsRef<OsStr>
203 let mut joined = Vec::new();
204 let sep = b';' as u16;
206 for (i, path) in paths.enumerate() {
207 let path = path.as_ref();
208 if i > 0 { joined.push(sep) }
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);
217 joined.extend_from_slice(&v[..]);
221 Ok(OsStringExt::from_wide(&joined[..]))
224 impl fmt::Display for JoinPathsError {
225 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226 "path segment contains `\"`".fmt(f)
230 impl StdError for JoinPathsError {
231 fn description(&self) -> &str { "failed to join paths" }
234 pub fn current_exe() -> io::Result<PathBuf> {
235 super::fill_utf16_buf(|buf, sz| unsafe {
236 c::GetModuleFileNameW(ptr::null_mut(), buf, sz)
240 pub fn getcwd() -> io::Result<PathBuf> {
241 super::fill_utf16_buf(|buf, sz| unsafe {
242 c::GetCurrentDirectoryW(sz, buf)
246 pub fn chdir(p: &path::Path) -> io::Result<()> {
247 let p: &OsStr = p.as_ref();
248 let mut p = p.encode_wide().collect::<Vec<_>>();
252 c::SetCurrentDirectoryW(p.as_ptr())
256 pub fn getenv(k: &OsStr) -> io::Result<Option<OsString>> {
258 let res = super::fill_utf16_buf(|buf, sz| unsafe {
259 c::GetEnvironmentVariableW(k.as_ptr(), buf, sz)
261 OsStringExt::from_wide(buf)
264 Ok(value) => Ok(Some(value)),
266 if e.raw_os_error() == Some(c::ERROR_ENVVAR_NOT_FOUND as i32) {
275 pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
280 c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr())
284 pub fn unsetenv(n: &OsStr) -> io::Result<()> {
287 c::SetEnvironmentVariableW(v.as_ptr(), ptr::null())
291 pub fn temp_dir() -> PathBuf {
292 super::fill_utf16_buf(|buf, sz| unsafe {
293 c::GetTempPathW(sz, buf)
294 }, super::os2path).unwrap()
297 pub fn home_dir() -> Option<PathBuf> {
298 ::env::var_os("HOME").or_else(|| {
299 ::env::var_os("USERPROFILE")
300 }).map(PathBuf::from).or_else(|| unsafe {
301 let me = c::GetCurrentProcess();
302 let mut token = ptr::null_mut();
303 if c::OpenProcessToken(me, c::TOKEN_READ, &mut token) == 0 {
306 let _handle = Handle::new(token);
307 super::fill_utf16_buf(|buf, mut sz| {
308 match c::GetUserProfileDirectoryW(token, buf, &mut sz) {
309 0 if c::GetLastError() != c::ERROR_INSUFFICIENT_BUFFER => 0,
311 _ => sz - 1, // sz includes the null terminator
313 }, super::os2path).ok()
317 pub fn exit(code: i32) -> ! {
318 unsafe { c::ExitProcess(code as c::UINT) }