Skip to content

Commit 032fa63

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

File tree

3 files changed

+143
-20
lines changed

3 files changed

+143
-20
lines changed

src/shims/env.rs

Lines changed: 126 additions & 5 deletions
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;
@@ -36,6 +37,10 @@ impl<'tcx> EnvVars<'tcx> {
3637
}
3738
ecx.update_environ()
3839
}
40+
41+
pub(super) fn values(&self) -> InterpResult<'tcx, Values<'_, OsString, Pointer<Tag>>> {
42+
Ok(self.map.values())
43+
}
3944
}
4045

4146
fn alloc_env_var_as_target_str<'mir, 'tcx>(
@@ -54,33 +59,109 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
5459
fn getenv(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, Scalar<Tag>> {
5560
let this = self.eval_context_mut();
5661
let target_os = this.tcx.sess.target.target.target_os.as_str();
57-
assert!(target_os == "linux" || target_os == "macos", "`{}` is only available for the UNIX target family");
62+
assert!(target_os == "linux" || target_os == "macos", "`getenv` is only available for the UNIX target family");
5863

5964
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
6065
let name = this.read_os_str_from_c_str(name_ptr)?;
6166
Ok(match this.machine.env_vars.map.get(name) {
62-
// The offset is used to strip the "{name}=" part of the string.
6367
Some(var_ptr) => {
68+
// The offset is used to strip the "{name}=" part of the string.
6469
Scalar::from(var_ptr.offset(Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()), this)?)
6570
}
6671
None => Scalar::ptr_null(&*this.tcx),
6772
})
6873
}
6974

75+
fn getenvironmentvariablew(
76+
&mut self,
77+
name_op: OpTy<'tcx, Tag>, // LPCWSTR lpName
78+
buf_op: OpTy<'tcx, Tag>, // LPWSTR lpBuffer
79+
size_op: OpTy<'tcx, Tag>, // DWORD nSize
80+
) -> InterpResult<'tcx, u64> {
81+
let this = self.eval_context_mut();
82+
this.assert_target_os("windows", "GetEnvironmentVariableW");
83+
84+
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
85+
let name = this.read_os_str_from_wide_str(name_ptr)?;
86+
Ok(match this.machine.env_vars.map.get(&name) {
87+
Some(var_ptr) => {
88+
// The offset is used to strip the "{name}=" part of the string.
89+
let name_offset_bytes =
90+
u64::try_from(name.len()).unwrap().checked_add(1).unwrap().checked_mul(2).unwrap();
91+
let var_ptr = Scalar::from(var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?);
92+
93+
let var_size = u64::try_from(this.read_os_str_from_wide_str(var_ptr)?.len()).unwrap();
94+
let buf_size = u64::try_from(this.read_scalar(size_op)?.to_i32()?).unwrap();
95+
let return_val = if var_size.checked_add(1).unwrap() > buf_size {
96+
// If lpBuffer is not large enough to hold the data, the return value is the buffer size, in characters,
97+
// required to hold the string and its terminating null character and the contents of lpBuffer are undefined.
98+
var_size + 1
99+
} else {
100+
let buf_ptr = this.read_scalar(buf_op)?.not_undef()?;
101+
for i in 0..var_size {
102+
this.memory.copy(
103+
this.force_ptr(var_ptr.ptr_offset(Size::from_bytes(i) * 2, this)?)?,
104+
this.force_ptr(buf_ptr.ptr_offset(Size::from_bytes(i) * 2, this)?)?,
105+
Size::from_bytes(2),
106+
true,
107+
)?;
108+
}
109+
// If the function succeeds, the return value is the number of characters stored in the buffer pointed to by lpBuffer,
110+
// not including the terminating null character.
111+
var_size
112+
};
113+
return_val
114+
}
115+
None => {
116+
this.set_last_error(Scalar::from_u32(203))?; // ERROR_ENVVAR_NOT_FOUND
117+
0 // return zero upon failure
118+
}
119+
})
120+
}
121+
122+
fn getenvironmentstringsw(&mut self) -> InterpResult<'tcx, Scalar<Tag>> {
123+
let this = self.eval_context_mut();
124+
this.assert_target_os("windows", "GetEnvironmentStringsW");
125+
126+
// Info on layout of environment blocks in Windows:
127+
// https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
128+
let mut env_vars = std::ffi::OsString::new();
129+
for &item in this.machine.env_vars.values()? {
130+
let env_var = this.read_os_str_from_target_str(Scalar::from(item))?;
131+
env_vars.push(env_var);
132+
env_vars.push("\0");
133+
}
134+
135+
// Allocate environment block
136+
let tcx = this.tcx;
137+
let env_block_size = env_vars.len().checked_add(1).unwrap();
138+
let env_block_type = tcx.mk_array(tcx.types.u16, u64::try_from(env_block_size).unwrap());
139+
let env_block_place = this.allocate(this.layout_of(env_block_type)?, MiriMemoryKind::WinHeap.into());
140+
141+
// Store environment variables to environment block
142+
// Final null terminator(block terminator) is pushed by `write_os_str_to_wide_str`
143+
this.write_os_str_to_wide_str(&env_vars, env_block_place, u64::try_from(env_block_size).unwrap())?;
144+
145+
Ok(env_block_place.ptr)
146+
}
147+
70148
fn setenv(
71149
&mut self,
72150
name_op: OpTy<'tcx, Tag>,
73151
value_op: OpTy<'tcx, Tag>,
74152
) -> InterpResult<'tcx, i32> {
75153
let mut this = self.eval_context_mut();
154+
let target_os = this.tcx.sess.target.target.target_os.as_str();
155+
assert!(target_os == "linux" || target_os == "macos", "`setenv` is only available for the UNIX target family");
76156

77157
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
78158
let value_ptr = this.read_scalar(value_op)?.not_undef()?;
79-
let value = this.read_os_str_from_target_str(value_ptr)?;
159+
80160
let mut new = None;
81161
if !this.is_null(name_ptr)? {
82162
let name = this.read_os_str_from_target_str(name_ptr)?;
83163
if !name.is_empty() && !name.to_string_lossy().contains('=') {
164+
let value = this.read_os_str_from_target_str(value_ptr)?;
84165
new = Some((name.to_owned(), value.to_owned()));
85166
}
86167
}
@@ -91,19 +172,59 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
91172
.deallocate(var, None, MiriMemoryKind::Machine.into())?;
92173
}
93174
this.update_environ()?;
94-
Ok(0)
175+
Ok(0) // return zero on success
95176
} else {
96177
Ok(-1)
97178
}
98179
}
99180

181+
fn setenvironmentvariablew(
182+
&mut self,
183+
name_op: OpTy<'tcx, Tag>, // LPCWSTR lpName,
184+
value_op: OpTy<'tcx, Tag>, // LPCWSTR lpValue,
185+
) -> InterpResult<'tcx, i32> {
186+
let mut this = self.eval_context_mut();
187+
this.assert_target_os("windows", "SetEnvironmentVariableW");
188+
189+
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
190+
let value_ptr = this.read_scalar(value_op)?.not_undef()?;
191+
192+
let mut new = None;
193+
if !this.is_null(name_ptr)? {
194+
let name = this.read_os_str_from_target_str(name_ptr)?;
195+
if this.is_null(value_ptr)? {
196+
// Delete environment variable `{name}`
197+
if let Some(var) = this.machine.env_vars.map.remove(&name) {
198+
this.memory.deallocate(var, None, MiriMemoryKind::Machine.into())?;
199+
this.update_environ()?;
200+
}
201+
return Ok(1); // return non-zero on success
202+
}
203+
if !name.is_empty() && !name.to_string_lossy().contains('=') {
204+
let value = this.read_os_str_from_target_str(value_ptr)?;
205+
new = Some((name.to_owned(), value.to_owned()));
206+
}
207+
}
208+
if let Some((name, value)) = new {
209+
let var_ptr = alloc_env_var_as_target_str(&name, &value, &mut this)?;
210+
if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
211+
this.memory
212+
.deallocate(var, None, MiriMemoryKind::Machine.into())?;
213+
}
214+
this.update_environ()?;
215+
Ok(1) // return non-zero on success
216+
} else {
217+
Ok(0)
218+
}
219+
}
220+
100221
fn unsetenv(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
101222
let this = self.eval_context_mut();
102223

103224
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
104225
let mut success = None;
105226
if !this.is_null(name_ptr)? {
106-
let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
227+
let name = this.read_os_str_from_target_str(name_ptr)?.to_owned();
107228
if !name.is_empty() && !name.to_string_lossy().contains('=') {
108229
success = Some(this.machine.env_vars.map.remove(&name));
109230
}

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)