Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Windows shims for env var emulation
Shims for GetEnvironmentVariableW / SetEnvironmentVariableW /
GetEnvironmentStringsW.
Passes test 'tests/run-pass/env.rs'
  • Loading branch information
JOE1994 committed Mar 27, 2020
commit 2fa07009f2a27c8d77ed48539cf66db6e2b5a676
120 changes: 119 additions & 1 deletion src/shims/env.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::ffi::{OsString, OsStr};
use std::env;
use std::convert::TryFrom;
use std::collections::hash_map::Values;

use crate::stacked_borrows::Tag;
use crate::rustc_target::abi::LayoutOf;
Expand Down Expand Up @@ -40,6 +41,10 @@ impl<'tcx> EnvVars<'tcx> {
}
ecx.update_environ()
}

pub(super) fn values(&self) -> InterpResult<'tcx, Values<'_, OsString, Pointer<Tag>>> {
Ok(self.map.values())
}
}

fn alloc_env_var_as_c_str<'mir, 'tcx>(
Expand Down Expand Up @@ -82,6 +87,79 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
})
}

fn getenvironmentvariablew(
&mut self,
name_op: OpTy<'tcx, Tag>, // LPCWSTR lpName
buf_op: OpTy<'tcx, Tag>, // LPWSTR lpBuffer
size_op: OpTy<'tcx, Tag>, // DWORD nSize
) -> InterpResult<'tcx, u64> {
let this = self.eval_context_mut();
this.assert_target_os("windows", "GetEnvironmentVariableW");

let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let name = this.read_os_str_from_wide_str(name_ptr)?;
Ok(match this.machine.env_vars.map.get(&name) {
Some(var_ptr) => {
// The offset is used to strip the "{name}=" part of the string.
let name_offset_bytes =
u64::try_from(name.len()).unwrap().checked_add(1).unwrap().checked_mul(2).unwrap();
let var_ptr = Scalar::from(var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?);

let var_size = u64::try_from(this.read_os_str_from_wide_str(var_ptr)?.len()).unwrap();
let buf_size = u64::try_from(this.read_scalar(size_op)?.to_i32()?).unwrap();
let return_val = if var_size.checked_add(1).unwrap() > buf_size {
// If lpBuffer is not large enough to hold the data, the return value is the buffer size, in characters,
// required to hold the string and its terminating null character and the contents of lpBuffer are undefined.
var_size + 1
} else {
let buf_ptr = this.read_scalar(buf_op)?.not_undef()?;
for i in 0..var_size {
this.memory.copy(
this.force_ptr(var_ptr.ptr_offset(Size::from_bytes(i) * 2, this)?)?,
this.force_ptr(buf_ptr.ptr_offset(Size::from_bytes(i) * 2, this)?)?,
Size::from_bytes(2),
true,
)?;
}
// If the function succeeds, the return value is the number of characters stored in the buffer pointed to by lpBuffer,
// not including the terminating null character.
var_size
};
return_val
}
None => {
this.set_last_error(Scalar::from_u32(203))?; // ERROR_ENVVAR_NOT_FOUND
0 // return zero upon failure
}
})
}

fn getenvironmentstringsw(&mut self) -> InterpResult<'tcx, Scalar<Tag>> {
let this = self.eval_context_mut();
this.assert_target_os("windows", "GetEnvironmentStringsW");

// Info on layout of environment blocks in Windows:
// https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
let mut env_vars = std::ffi::OsString::new();
for &item in this.machine.env_vars.values()? {
let env_var = this.read_os_str_from_target_str(Scalar::from(item))?;
env_vars.push(env_var);
env_vars.push("\0");
}

// Allocate environment block
let tcx = this.tcx;
let env_block_size = env_vars.len().checked_add(1).unwrap();
let env_block_type = tcx.mk_array(tcx.types.u16, u64::try_from(env_block_size).unwrap());
let env_block_place = this.allocate(this.layout_of(env_block_type)?, MiriMemoryKind::WinHeap.into());

// Store environment variables to environment block
// Final null terminator(block terminator) is pushed by `write_os_str_to_wide_str`
this.write_os_str_to_wide_str(&env_vars, env_block_place, u64::try_from(env_block_size).unwrap())?;

Ok(env_block_place.ptr)
}

fn setenv(
&mut self,
name_op: OpTy<'tcx, Tag>,
Expand Down Expand Up @@ -118,6 +196,46 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
}
}

fn setenvironmentvariablew(
&mut self,
name_op: OpTy<'tcx, Tag>, // LPCWSTR lpName,
value_op: OpTy<'tcx, Tag>, // LPCWSTR lpValue,
) -> InterpResult<'tcx, i32> {
let mut this = self.eval_context_mut();
this.assert_target_os("windows", "SetEnvironmentVariableW");

let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let value_ptr = this.read_scalar(value_op)?.not_undef()?;

let mut new = None;
if !this.is_null(name_ptr)? {
let name = this.read_os_str_from_target_str(name_ptr)?;
if this.is_null(value_ptr)? {
// Delete environment variable `{name}`
if let Some(var) = this.machine.env_vars.map.remove(&name) {
this.memory.deallocate(var, None, MiriMemoryKind::Machine.into())?;
this.update_environ()?;
}
return Ok(1); // return non-zero on success
}
if !name.is_empty() && !name.to_string_lossy().contains('=') {
let value = this.read_os_str_from_target_str(value_ptr)?;
new = Some((name.to_owned(), value.to_owned()));
}
}
if let Some((name, value)) = new {
let var_ptr = alloc_env_var_as_target_str(&name, &value, &mut this)?;
if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
this.memory
.deallocate(var, None, MiriMemoryKind::Machine.into())?;
}
this.update_environ()?;
Ok(1) // return non-zero on success
} else {
Ok(0)
}
}

fn unsetenv(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let target_os = &this.tcx.sess.target.target.target_os;
Expand All @@ -126,7 +244,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let mut success = None;
if !this.is_null(name_ptr)? {
let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
let name = this.read_os_str_from_target_str(name_ptr)?.to_owned();
if !name.is_empty() && !name.to_string_lossy().contains('=') {
success = Some(this.machine.env_vars.map.remove(&name));
}
Expand Down
30 changes: 17 additions & 13 deletions src/shims/foreign_items/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx

// Environment related shims
"GetEnvironmentVariableW" => {
// args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars)
// args[1] : LPWSTR lpBuffer (32-bit pointer to a string of 16-bit Unicode chars)
// lpBuffer : ptr to buffer that receives contents of the env_var as a null-terminated string.
// Return `# of chars` stored in the buffer pointed to by lpBuffer, excluding null-terminator.
// Return 0 upon failure.

// This is not the env var you are looking for.
this.set_last_error(Scalar::from_u32(203))?; // ERROR_ENVVAR_NOT_FOUND
this.write_null(dest)?;
let result = this.getenvironmentvariablew(args[0], args[1], args[2])?;
this.write_scalar(Scalar::from_uint(result, dest.layout.size), dest)?;
}

"SetEnvironmentVariableW" => {
// args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars)
// args[1] : LPCWSTR lpValue (32-bit ptr to a const string of 16-bit Unicode chars)
// Return nonzero if success, else return 0.
throw_unsup_format!("can't set environment variable on Windows");
let result = this.setenvironmentvariablew(args[0], args[1])?;
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
}

"GetEnvironmentStringsW" => {
let result = this.getenvironmentstringsw()?;
// If the function succeeds, the return value is a pointer to the environment block of the current process.
this.write_scalar(result, dest)?;
}

"FreeEnvironmentStringsW" => {
let old_vars_ptr = this.read_scalar(args[0])?.not_undef()?;
let result = this.memory.deallocate(this.force_ptr(old_vars_ptr)?, None, MiriMemoryKind::WinHeap.into()).is_ok();
// If the function succeeds, the return value is nonzero.
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
}

// File related shims
Expand Down
2 changes: 0 additions & 2 deletions tests/run-pass/env.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//ignore-windows: TODO env var emulation stubbed out on Windows

use std::env;

fn main() {
Expand Down