Skip to content

Commit 2fa0700

Browse files
committed
Windows shims for env var emulation
Shims for GetEnvironmentVariableW / SetEnvironmentVariableW / GetEnvironmentStringsW. Passes test 'tests/run-pass/env.rs'
1 parent e2a9c7b commit 2fa0700

File tree

3 files changed

+136
-16
lines changed

3 files changed

+136
-16
lines changed

src/shims/env.rs

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::ffi::{OsString, OsStr};
22
use std::env;
33
use std::convert::TryFrom;
4+
use std::collections::hash_map::Values;
45

56
use crate::stacked_borrows::Tag;
67
use crate::rustc_target::abi::LayoutOf;
@@ -40,6 +41,10 @@ impl<'tcx> EnvVars<'tcx> {
4041
}
4142
ecx.update_environ()
4243
}
44+
45+
pub(super) fn values(&self) -> InterpResult<'tcx, Values<'_, OsString, Pointer<Tag>>> {
46+
Ok(self.map.values())
47+
}
4348
}
4449

4550
fn alloc_env_var_as_c_str<'mir, 'tcx>(
@@ -82,6 +87,79 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
8287
})
8388
}
8489

90+
fn getenvironmentvariablew(
91+
&mut self,
92+
name_op: OpTy<'tcx, Tag>, // LPCWSTR lpName
93+
buf_op: OpTy<'tcx, Tag>, // LPWSTR lpBuffer
94+
size_op: OpTy<'tcx, Tag>, // DWORD nSize
95+
) -> InterpResult<'tcx, u64> {
96+
let this = self.eval_context_mut();
97+
this.assert_target_os("windows", "GetEnvironmentVariableW");
98+
99+
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
100+
let name = this.read_os_str_from_wide_str(name_ptr)?;
101+
Ok(match this.machine.env_vars.map.get(&name) {
102+
Some(var_ptr) => {
103+
// The offset is used to strip the "{name}=" part of the string.
104+
let name_offset_bytes =
105+
u64::try_from(name.len()).unwrap().checked_add(1).unwrap().checked_mul(2).unwrap();
106+
let var_ptr = Scalar::from(var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?);
107+
108+
let var_size = u64::try_from(this.read_os_str_from_wide_str(var_ptr)?.len()).unwrap();
109+
let buf_size = u64::try_from(this.read_scalar(size_op)?.to_i32()?).unwrap();
110+
let return_val = if var_size.checked_add(1).unwrap() > buf_size {
111+
// If lpBuffer is not large enough to hold the data, the return value is the buffer size, in characters,
112+
// required to hold the string and its terminating null character and the contents of lpBuffer are undefined.
113+
var_size + 1
114+
} else {
115+
let buf_ptr = this.read_scalar(buf_op)?.not_undef()?;
116+
for i in 0..var_size {
117+
this.memory.copy(
118+
this.force_ptr(var_ptr.ptr_offset(Size::from_bytes(i) * 2, this)?)?,
119+
this.force_ptr(buf_ptr.ptr_offset(Size::from_bytes(i) * 2, this)?)?,
120+
Size::from_bytes(2),
121+
true,
122+
)?;
123+
}
124+
// If the function succeeds, the return value is the number of characters stored in the buffer pointed to by lpBuffer,
125+
// not including the terminating null character.
126+
var_size
127+
};
128+
return_val
129+
}
130+
None => {
131+
this.set_last_error(Scalar::from_u32(203))?; // ERROR_ENVVAR_NOT_FOUND
132+
0 // return zero upon failure
133+
}
134+
})
135+
}
136+
137+
fn getenvironmentstringsw(&mut self) -> InterpResult<'tcx, Scalar<Tag>> {
138+
let this = self.eval_context_mut();
139+
this.assert_target_os("windows", "GetEnvironmentStringsW");
140+
141+
// Info on layout of environment blocks in Windows:
142+
// https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
143+
let mut env_vars = std::ffi::OsString::new();
144+
for &item in this.machine.env_vars.values()? {
145+
let env_var = this.read_os_str_from_target_str(Scalar::from(item))?;
146+
env_vars.push(env_var);
147+
env_vars.push("\0");
148+
}
149+
150+
// Allocate environment block
151+
let tcx = this.tcx;
152+
let env_block_size = env_vars.len().checked_add(1).unwrap();
153+
let env_block_type = tcx.mk_array(tcx.types.u16, u64::try_from(env_block_size).unwrap());
154+
let env_block_place = this.allocate(this.layout_of(env_block_type)?, MiriMemoryKind::WinHeap.into());
155+
156+
// Store environment variables to environment block
157+
// Final null terminator(block terminator) is pushed by `write_os_str_to_wide_str`
158+
this.write_os_str_to_wide_str(&env_vars, env_block_place, u64::try_from(env_block_size).unwrap())?;
159+
160+
Ok(env_block_place.ptr)
161+
}
162+
85163
fn setenv(
86164
&mut self,
87165
name_op: OpTy<'tcx, Tag>,
@@ -118,6 +196,46 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
118196
}
119197
}
120198

199+
fn setenvironmentvariablew(
200+
&mut self,
201+
name_op: OpTy<'tcx, Tag>, // LPCWSTR lpName,
202+
value_op: OpTy<'tcx, Tag>, // LPCWSTR lpValue,
203+
) -> InterpResult<'tcx, i32> {
204+
let mut this = self.eval_context_mut();
205+
this.assert_target_os("windows", "SetEnvironmentVariableW");
206+
207+
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
208+
let value_ptr = this.read_scalar(value_op)?.not_undef()?;
209+
210+
let mut new = None;
211+
if !this.is_null(name_ptr)? {
212+
let name = this.read_os_str_from_target_str(name_ptr)?;
213+
if this.is_null(value_ptr)? {
214+
// Delete environment variable `{name}`
215+
if let Some(var) = this.machine.env_vars.map.remove(&name) {
216+
this.memory.deallocate(var, None, MiriMemoryKind::Machine.into())?;
217+
this.update_environ()?;
218+
}
219+
return Ok(1); // return non-zero on success
220+
}
221+
if !name.is_empty() && !name.to_string_lossy().contains('=') {
222+
let value = this.read_os_str_from_target_str(value_ptr)?;
223+
new = Some((name.to_owned(), value.to_owned()));
224+
}
225+
}
226+
if let Some((name, value)) = new {
227+
let var_ptr = alloc_env_var_as_target_str(&name, &value, &mut this)?;
228+
if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
229+
this.memory
230+
.deallocate(var, None, MiriMemoryKind::Machine.into())?;
231+
}
232+
this.update_environ()?;
233+
Ok(1) // return non-zero on success
234+
} else {
235+
Ok(0)
236+
}
237+
}
238+
121239
fn unsetenv(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
122240
let this = self.eval_context_mut();
123241
let target_os = &this.tcx.sess.target.target.target_os;
@@ -126,7 +244,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
126244
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
127245
let mut success = None;
128246
if !this.is_null(name_ptr)? {
129-
let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
247+
let name = this.read_os_str_from_target_str(name_ptr)?.to_owned();
130248
if !name.is_empty() && !name.to_string_lossy().contains('=') {
131249
success = Some(this.machine.env_vars.map.remove(&name));
132250
}

src/shims/foreign_items/windows.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
2323

2424
// Environment related shims
2525
"GetEnvironmentVariableW" => {
26-
// args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars)
27-
// args[1] : LPWSTR lpBuffer (32-bit pointer to a string of 16-bit Unicode chars)
28-
// lpBuffer : ptr to buffer that receives contents of the env_var as a null-terminated string.
29-
// Return `# of chars` stored in the buffer pointed to by lpBuffer, excluding null-terminator.
30-
// Return 0 upon failure.
31-
32-
// This is not the env var you are looking for.
33-
this.set_last_error(Scalar::from_u32(203))?; // ERROR_ENVVAR_NOT_FOUND
34-
this.write_null(dest)?;
26+
let result = this.getenvironmentvariablew(args[0], args[1], args[2])?;
27+
this.write_scalar(Scalar::from_uint(result, dest.layout.size), dest)?;
3528
}
3629

3730
"SetEnvironmentVariableW" => {
38-
// args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars)
39-
// args[1] : LPCWSTR lpValue (32-bit ptr to a const string of 16-bit Unicode chars)
40-
// Return nonzero if success, else return 0.
41-
throw_unsup_format!("can't set environment variable on Windows");
31+
let result = this.setenvironmentvariablew(args[0], args[1])?;
32+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
33+
}
34+
35+
"GetEnvironmentStringsW" => {
36+
let result = this.getenvironmentstringsw()?;
37+
// If the function succeeds, the return value is a pointer to the environment block of the current process.
38+
this.write_scalar(result, dest)?;
39+
}
40+
41+
"FreeEnvironmentStringsW" => {
42+
let old_vars_ptr = this.read_scalar(args[0])?.not_undef()?;
43+
let result = this.memory.deallocate(this.force_ptr(old_vars_ptr)?, None, MiriMemoryKind::WinHeap.into()).is_ok();
44+
// If the function succeeds, the return value is nonzero.
45+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
4246
}
4347

4448
// File related shims

tests/run-pass/env.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//ignore-windows: TODO env var emulation stubbed out on Windows
2-
31
use std::env;
42

53
fn main() {

0 commit comments

Comments
 (0)