blob: 352337ba322371d77b89aedc5d1a7cc756351f0d [file] [log] [blame]
//! Implementation of `std::os` functionality for Windows.
#![allow(nonstandard_style)]
#[cfg(test)]
mod tests;
use crate::os::windows::prelude::*;
use crate::error::Error as StdError;
use crate::ffi::{OsStr, OsString};
use crate::fmt;
use crate::io;
use crate::os::windows::ffi::EncodeWide;
use crate::path::{self, PathBuf};
use crate::ptr;
use crate::slice;
use crate::sys::{c, cvt};
use super::to_u16s;
pub fn errno() -> i32 {
unsafe { c::GetLastError() as i32 }
}
/// Gets a detailed string description for the given error number.
pub fn error_string(mut errnum: i32) -> String {
// This value is calculated from the macro
// MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT)
let langId = 0x0800 as c::DWORD;
let mut buf = [0 as c::WCHAR; 2048];
unsafe {
let mut module = ptr::null_mut();
let mut flags = 0;
// NTSTATUS errors may be encoded as HRESULT, which may returned from
// GetLastError. For more information about Windows error codes, see
// `[MS-ERREF]`: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a
if (errnum & c::FACILITY_NT_BIT as i32) != 0 {
// format according to https://support.microsoft.com/en-us/help/259693
const NTDLL_DLL: &[u16] = &[
'N' as _, 'T' as _, 'D' as _, 'L' as _, 'L' as _, '.' as _, 'D' as _, 'L' as _,
'L' as _, 0,
];
module = c::GetModuleHandleW(NTDLL_DLL.as_ptr());
if !module.is_null() {
errnum ^= c::FACILITY_NT_BIT as i32;
flags = c::FORMAT_MESSAGE_FROM_HMODULE;
}
}
let res = c::FormatMessageW(
flags | c::FORMAT_MESSAGE_FROM_SYSTEM | c::FORMAT_MESSAGE_IGNORE_INSERTS,
module,
errnum as c::DWORD,
langId,
buf.as_mut_ptr(),
buf.len() as c::DWORD,
ptr::null(),
) as usize;
if res == 0 {
// Sometimes FormatMessageW can fail e.g., system doesn't like langId,
let fm_err = errno();
return format!("OS Error {errnum} (FormatMessageW() returned error {fm_err})");
}
match String::from_utf16(&buf[..res]) {
Ok(mut msg) => {
// Trim trailing CRLF inserted by FormatMessageW
let len = msg.trim_end().len();
msg.truncate(len);
msg
}
Err(..) => format!(
"OS Error {} (FormatMessageW() returned \
invalid UTF-16)",
errnum
),
}
}
}
pub struct Env {
base: c::LPWCH,
cur: c::LPWCH,
}
impl Iterator for Env {
type Item = (OsString, OsString);
fn next(&mut self) -> Option<(OsString, OsString)> {
loop {
unsafe {
if *self.cur == 0 {
return None;
}
let p = self.cur as *const u16;
let mut len = 0;
while *p.add(len) != 0 {
len += 1;
}
let s = slice::from_raw_parts(p, len);
self.cur = self.cur.add(len + 1);
// Windows allows environment variables to start with an equals
// symbol (in any other position, this is the separator between
// variable name and value). Since`s` has at least length 1 at
// this point (because the empty string terminates the array of
// environment variables), we can safely slice.
let pos = match s[1..].iter().position(|&u| u == b'=' as u16).map(|p| p + 1) {
Some(p) => p,
None => continue,
};
return Some((
OsStringExt::from_wide(&s[..pos]),
OsStringExt::from_wide(&s[pos + 1..]),
));
}
}
}
}
impl Drop for Env {
fn drop(&mut self) {
unsafe {
c::FreeEnvironmentStringsW(self.base);
}
}
}
pub fn env() -> Env {
unsafe {
let ch = c::GetEnvironmentStringsW();
if ch.is_null() {
panic!("failure getting env string from OS: {}", io::Error::last_os_error());
}
Env { base: ch, cur: ch }
}
}
pub struct SplitPaths<'a> {
data: EncodeWide<'a>,
must_yield: bool,
}
pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> {
SplitPaths { data: unparsed.encode_wide(), must_yield: true }
}
impl<'a> Iterator for SplitPaths<'a> {
type Item = PathBuf;
fn next(&mut self) -> Option<PathBuf> {
// On Windows, the PATH environment variable is semicolon separated.
// Double quotes are used as a way of introducing literal semicolons
// (since c:\some;dir is a valid Windows path). Double quotes are not
// themselves permitted in path names, so there is no way to escape a
// double quote. Quoted regions can appear in arbitrary locations, so
//
// c:\foo;c:\som"e;di"r;c:\bar
//
// Should parse as [c:\foo, c:\some;dir, c:\bar].
//
// (The above is based on testing; there is no clear reference available
// for the grammar.)
let must_yield = self.must_yield;
self.must_yield = false;
let mut in_progress = Vec::new();
let mut in_quote = false;
for b in self.data.by_ref() {
if b == '"' as u16 {
in_quote = !in_quote;
} else if b == ';' as u16 && !in_quote {
self.must_yield = true;
break;
} else {
in_progress.push(b)
}
}
if !must_yield && in_progress.is_empty() {
None
} else {
Some(super::os2path(&in_progress))
}
}
}
#[derive(Debug)]
pub struct JoinPathsError;
pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
where
I: Iterator<Item = T>,
T: AsRef<OsStr>,
{
let mut joined = Vec::new();
let sep = b';' as u16;
for (i, path) in paths.enumerate() {
let path = path.as_ref();
if i > 0 {
joined.push(sep)
}
let v = path.encode_wide().collect::<Vec<u16>>();
if v.contains(&(b'"' as u16)) {
return Err(JoinPathsError);
} else if v.contains(&sep) {
joined.push(b'"' as u16);
joined.extend_from_slice(&v[..]);
joined.push(b'"' as u16);
} else {
joined.extend_from_slice(&v[..]);
}
}
Ok(OsStringExt::from_wide(&joined[..]))
}
impl fmt::Display for JoinPathsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"path segment contains `\"`".fmt(f)
}
}
impl StdError for JoinPathsError {
#[allow(deprecated)]
fn description(&self) -> &str {
"failed to join paths"
}
}
pub fn current_exe() -> io::Result<PathBuf> {
super::fill_utf16_buf(
|buf, sz| unsafe { c::GetModuleFileNameW(ptr::null_mut(), buf, sz) },
super::os2path,
)
}
pub fn getcwd() -> io::Result<PathBuf> {
super::fill_utf16_buf(|buf, sz| unsafe { c::GetCurrentDirectoryW(sz, buf) }, super::os2path)
}
pub fn chdir(p: &path::Path) -> io::Result<()> {
let p: &OsStr = p.as_ref();
let mut p = p.encode_wide().collect::<Vec<_>>();
p.push(0);
cvt(unsafe { c::SetCurrentDirectoryW(p.as_ptr()) }).map(drop)
}
pub fn getenv(k: &OsStr) -> Option<OsString> {
let k = to_u16s(k).ok()?;
super::fill_utf16_buf(
|buf, sz| unsafe { c::GetEnvironmentVariableW(k.as_ptr(), buf, sz) },
|buf| OsStringExt::from_wide(buf),
)
.ok()
}
pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> {
let k = to_u16s(k)?;
let v = to_u16s(v)?;
cvt(unsafe { c::SetEnvironmentVariableW(k.as_ptr(), v.as_ptr()) }).map(drop)
}
pub fn unsetenv(n: &OsStr) -> io::Result<()> {
let v = to_u16s(n)?;
cvt(unsafe { c::SetEnvironmentVariableW(v.as_ptr(), ptr::null()) }).map(drop)
}
pub fn temp_dir() -> PathBuf {
super::fill_utf16_buf(|buf, sz| unsafe { c::GetTempPath2W(sz, buf) }, super::os2path).unwrap()
}
#[cfg(not(target_vendor = "uwp"))]
fn home_dir_crt() -> Option<PathBuf> {
unsafe {
// The magic constant -4 can be used as the token passed to GetUserProfileDirectoryW below
// instead of us having to go through these multiple steps to get a token. However this is
// not implemented on Windows 7, only Windows 8 and up. When we drop support for Windows 7
// we can simplify this code. See #90144 for details.
use crate::sys::handle::Handle;
let me = c::GetCurrentProcess();
let mut token = ptr::null_mut();
if c::OpenProcessToken(me, c::TOKEN_READ, &mut token) == 0 {
return None;
}
let _handle = Handle::from_raw_handle(token);
super::fill_utf16_buf(
|buf, mut sz| {
match c::GetUserProfileDirectoryW(token, buf, &mut sz) {
0 if c::GetLastError() != c::ERROR_INSUFFICIENT_BUFFER => 0,
0 => sz,
_ => sz - 1, // sz includes the null terminator
}
},
super::os2path,
)
.ok()
}
}
#[cfg(target_vendor = "uwp")]
fn home_dir_crt() -> Option<PathBuf> {
None
}
pub fn home_dir() -> Option<PathBuf> {
crate::env::var_os("HOME")
.or_else(|| crate::env::var_os("USERPROFILE"))
.map(PathBuf::from)
.or_else(|| home_dir_crt())
}
pub fn exit(code: i32) -> ! {
unsafe { c::ExitProcess(code as c::UINT) }
}
pub fn getpid() -> u32 {
unsafe { c::GetCurrentProcessId() as u32 }
}