348 lines
9.9 KiB
Rust
348 lines
9.9 KiB
Rust
//! Implementation of `std::os` functionality for Windows.
|
|
|
|
#![allow(nonstandard_style)]
|
|
|
|
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 {} (FormatMessageW() returned error {})", errnum, 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.offset(len) != 0 {
|
|
len += 1;
|
|
}
|
|
let s = slice::from_raw_parts(p, len as usize);
|
|
self.cur = self.cur.offset(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 as usize == 0 {
|
|
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) -> io::Result<Option<OsString>> {
|
|
let k = to_u16s(k)?;
|
|
let res = super::fill_utf16_buf(
|
|
|buf, sz| unsafe { c::GetEnvironmentVariableW(k.as_ptr(), buf, sz) },
|
|
|buf| OsStringExt::from_wide(buf),
|
|
);
|
|
match res {
|
|
Ok(value) => Ok(Some(value)),
|
|
Err(e) => {
|
|
if e.raw_os_error() == Some(c::ERROR_ENVVAR_NOT_FOUND as i32) {
|
|
Ok(None)
|
|
} else {
|
|
Err(e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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::GetTempPathW(sz, buf) }, super::os2path).unwrap()
|
|
}
|
|
|
|
#[cfg(not(target_vendor = "uwp"))]
|
|
fn home_dir_crt() -> Option<PathBuf> {
|
|
unsafe {
|
|
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::new(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 }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::io::Error;
|
|
use crate::sys::c;
|
|
|
|
// tests `error_string` above
|
|
#[test]
|
|
fn ntstatus_error() {
|
|
const STATUS_UNSUCCESSFUL: u32 = 0xc000_0001;
|
|
assert!(
|
|
!Error::from_raw_os_error((STATUS_UNSUCCESSFUL | c::FACILITY_NT_BIT) as _)
|
|
.to_string()
|
|
.contains("FormatMessageW() returned error")
|
|
);
|
|
}
|
|
}
|