1use std::env;
2use std::ffi::{OsStr, OsString};
3use std::io::ErrorKind;
4
5use rustc_abi::{FieldIdx, Size};
6use rustc_data_structures::fx::FxHashMap;
7use rustc_index::IndexVec;
8use rustc_middle::ty::Ty;
9use rustc_target::spec::{Env, Os};
10
11use super::HOSTNAME;
12use crate::*;
13
14pub struct UnixEnvVars<'tcx> {
15 map: FxHashMap<OsString, Pointer>,
18
19 environ: MPlaceTy<'tcx>,
21}
22
23impl VisitProvenance for UnixEnvVars<'_> {
24 fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
25 let UnixEnvVars { map, environ } = self;
26
27 environ.visit_provenance(visit);
28 for ptr in map.values() {
29 ptr.visit_provenance(visit);
30 }
31 }
32}
33
34impl<'tcx> UnixEnvVars<'tcx> {
35 pub(crate) fn new(
36 ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
37 env_vars: FxHashMap<OsString, OsString>,
38 ) -> InterpResult<'tcx, Self> {
39 let mut env_vars_machine = FxHashMap::default();
41 for (name, val) in env_vars.into_iter() {
42 let ptr = alloc_env_var(ecx, &name, &val)?;
43 env_vars_machine.insert(name, ptr);
44 }
45
46 let layout = ecx.machine.layouts.unit_ptr_mut;
48 let environ = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
49 let environ_block = alloc_environ_block(ecx, env_vars_machine.values().copied().collect())?;
50 ecx.write_pointer(environ_block, &environ)?;
51
52 interp_ok(UnixEnvVars { map: env_vars_machine, environ })
53 }
54
55 pub(crate) fn environ(&self) -> Pointer {
56 self.environ.ptr()
57 }
58
59 fn get_ptr(
60 &self,
61 ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
62 name: &OsStr,
63 ) -> InterpResult<'tcx, Option<Pointer>> {
64 let _vars_ptr = ecx.read_pointer(&self.environ)?;
67 let Some(var_ptr) = self.map.get(name) else {
68 return interp_ok(None);
69 };
70 let var_ptr = var_ptr.wrapping_offset(
72 Size::from_bytes(u64::try_from(name.len()).unwrap().strict_add(1)),
73 ecx,
74 );
75 interp_ok(Some(var_ptr))
76 }
77
78 pub(crate) fn get(
81 &self,
82 ecx: &InterpCx<'tcx, MiriMachine<'tcx>>,
83 name: &OsStr,
84 ) -> InterpResult<'tcx, Option<OsString>> {
85 let var_ptr = self.get_ptr(ecx, name)?;
86 if let Some(ptr) = var_ptr {
87 let var = ecx.read_os_str_from_c_str(ptr)?;
88 interp_ok(Some(var.to_owned()))
89 } else {
90 interp_ok(None)
91 }
92 }
93}
94
95fn alloc_env_var<'tcx>(
96 ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
97 name: &OsStr,
98 value: &OsStr,
99) -> InterpResult<'tcx, Pointer> {
100 let mut name_osstring = name.to_os_string();
101 name_osstring.push("=");
102 name_osstring.push(value);
103 ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Machine.into())
104}
105
106fn alloc_environ_block<'tcx>(
108 ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
109 mut vars: IndexVec<FieldIdx, Pointer>,
110) -> InterpResult<'tcx, Pointer> {
111 vars.push(Pointer::null());
113 let vars_layout = ecx.layout_of(Ty::new_array(
115 *ecx.tcx,
116 ecx.machine.layouts.unit_ptr_mut.ty,
117 u64::try_from(vars.len()).unwrap(),
118 ))?;
119 let vars_place = ecx.allocate(vars_layout, MiriMemoryKind::Machine.into())?;
120 for (idx, var) in vars.into_iter_enumerated() {
121 let place = ecx.project_field(&vars_place, idx)?;
122 ecx.write_pointer(var, &place)?;
123 }
124 interp_ok(vars_place.ptr())
125}
126
127impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
128pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
129 fn getenv(&self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Pointer> {
130 let ecx = self.eval_context_ref();
131 ecx.assert_target_os_is_unix("getenv");
132
133 let name_ptr = ecx.read_pointer(name_op)?;
134 let name = ecx.read_os_str_from_c_str(name_ptr)?;
135
136 let var_ptr = ecx.machine.env_vars.unix().get_ptr(ecx, name)?;
137 interp_ok(var_ptr.unwrap_or_else(Pointer::null))
138 }
139
140 fn setenv(
141 &mut self,
142 name_op: &OpTy<'tcx>,
143 value_op: &OpTy<'tcx>,
144 ) -> InterpResult<'tcx, Scalar> {
145 let ecx = self.eval_context_mut();
146 ecx.assert_target_os_is_unix("setenv");
147
148 let name_ptr = ecx.read_pointer(name_op)?;
149 let value_ptr = ecx.read_pointer(value_op)?;
150
151 let mut new = None;
152 if !ecx.ptr_is_null(name_ptr)? {
153 let name = ecx.read_os_str_from_c_str(name_ptr)?;
154 if !name.is_empty() && !name.to_string_lossy().contains('=') {
155 let value = ecx.read_os_str_from_c_str(value_ptr)?;
156 new = Some((name.to_owned(), value.to_owned()));
157 }
158 }
159 if let Some((name, value)) = new {
160 let var_ptr = alloc_env_var(ecx, &name, &value)?;
161 if let Some(var) = ecx.machine.env_vars.unix_mut().map.insert(name, var_ptr) {
162 ecx.deallocate_ptr(var, None, MiriMemoryKind::Machine.into())?;
163 }
164 ecx.update_environ()?;
165 interp_ok(Scalar::from_i32(0)) } else {
167 ecx.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
169 }
170 }
171
172 fn unsetenv(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
173 let ecx = self.eval_context_mut();
174 ecx.assert_target_os_is_unix("unsetenv");
175
176 let name_ptr = ecx.read_pointer(name_op)?;
177 let mut success = None;
178 if !ecx.ptr_is_null(name_ptr)? {
179 let name = ecx.read_os_str_from_c_str(name_ptr)?.to_owned();
180 if !name.is_empty() && !name.to_string_lossy().contains('=') {
181 success = Some(ecx.machine.env_vars.unix_mut().map.remove(&name));
182 }
183 }
184 if let Some(old) = success {
185 if let Some(var) = old {
186 ecx.deallocate_ptr(var, None, MiriMemoryKind::Machine.into())?;
187 }
188 ecx.update_environ()?;
189 interp_ok(Scalar::from_i32(0))
190 } else {
191 ecx.set_errno_and_return_neg1_i32(LibcError("EINVAL"))
193 }
194 }
195
196 fn getcwd(&mut self, buf_op: &OpTy<'tcx>, size_op: &OpTy<'tcx>) -> InterpResult<'tcx, Pointer> {
197 let ecx = self.eval_context_mut();
198 ecx.assert_target_os_is_unix("getcwd");
199
200 let buf = ecx.read_pointer(buf_op)?;
201 let size = ecx.read_target_usize(size_op)?;
202
203 if ecx.ptr_is_null(buf)? {
204 throw_unsup_format!("getcwd with a null pointer for `buf` is not supported");
206 }
207
208 if size == 0 {
209 ecx.set_last_error(LibcError("EINVAL"))?;
210 return interp_ok(Pointer::null());
211 }
212
213 if let IsolatedOp::Reject(reject_with) = ecx.machine.isolated_op {
214 ecx.reject_in_isolation("`getcwd`", reject_with)?;
215 ecx.set_last_error(ErrorKind::PermissionDenied)?;
216 return interp_ok(Pointer::null());
217 }
218
219 match env::current_dir() {
221 Ok(cwd) => {
222 if ecx.write_path_to_c_str(&cwd, buf, size)?.0 {
223 return interp_ok(buf);
224 }
225 ecx.set_last_error(LibcError("ERANGE"))?;
226 }
227 Err(e) => ecx.set_last_error(e)?,
228 }
229
230 interp_ok(Pointer::null())
231 }
232
233 fn gethostname(
234 &mut self,
235 name_op: &OpTy<'tcx>,
236 len_op: &OpTy<'tcx>,
237 ) -> InterpResult<'tcx, Scalar> {
238 let ecx = self.eval_context_mut();
239 ecx.assert_target_os_is_unix("gethostname");
240
241 let name = ecx.read_pointer(name_op)?;
242 let len = ecx.read_target_usize(len_op)?;
243
244 if len == 0 {
253 throw_ub_format!("`gethostname` called with a length of zero");
254 }
255 ecx.check_ptr_access(name, Size::from_bytes(len), CheckInAllocMsg::MemoryAccess)?;
259
260 if len > u64::try_from(HOSTNAME.len()).unwrap() {
261 let (written, _) = ecx.write_c_str(HOSTNAME, name, len)?;
262 assert!(written); return interp_ok(Scalar::from_i32(0));
264 }
265
266 match (&ecx.tcx.sess.target.os, &ecx.tcx.sess.target.env) {
273 (Os::Android, _) => {
274 ecx.set_errno_and_return_neg1_i32(LibcError("ENAMETOOLONG"))
277 }
278 (Os::Linux, Env::Gnu) | (Os::FreeBsd, _) => {
279 let len: usize = len.try_into().unwrap();
281 ecx.write_bytes_ptr(name, HOSTNAME[..len].iter().copied())?;
282 ecx.set_errno_and_return_neg1_i32(LibcError("ENAMETOOLONG"))
283 }
284 (Os::Linux, _) | (Os::MacOs, _) | (Os::Solaris, _) | (Os::Illumos, _) => {
285 let len: usize = len.try_into().unwrap();
287 let copy_len = len.strict_sub(1);
288 ecx.write_bytes_ptr(
289 name,
290 HOSTNAME[..copy_len].iter().copied().chain(std::iter::once(0)),
291 )?;
292
293 interp_ok(Scalar::from_i32(0))
301 }
302 _ => {
303 throw_unsup_format!("`gethostname` is not supported on {}", ecx.tcx.sess.target.os)
304 }
305 }
306 }
307
308 fn chdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
309 let ecx = self.eval_context_mut();
310 ecx.assert_target_os_is_unix("chdir");
311
312 let path = ecx.read_path_from_c_str(ecx.read_pointer(path_op)?)?;
313
314 if let IsolatedOp::Reject(reject_with) = ecx.machine.isolated_op {
315 ecx.reject_in_isolation("`chdir`", reject_with)?;
316 return ecx.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
317 }
318
319 let result = env::set_current_dir(path).map(|()| 0);
320 interp_ok(Scalar::from_i32(ecx.try_unwrap_io_result(result)?))
321 }
322
323 fn update_environ(&mut self) -> InterpResult<'tcx> {
325 let ecx = self.eval_context_mut();
326 let environ = ecx.machine.env_vars.unix().environ.clone();
328 let old_vars_ptr = ecx.read_pointer(&environ)?;
329 ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Machine.into())?;
330
331 let vals = ecx.machine.env_vars.unix().map.values().copied().collect();
333 let environ_block = alloc_environ_block(ecx, vals)?;
334 ecx.write_pointer(environ_block, &environ)?;
335
336 interp_ok(())
337 }
338
339 fn getpid(&self) -> InterpResult<'tcx, Scalar> {
340 let ecx = self.eval_context_ref();
341 ecx.assert_target_os_is_unix("getpid");
342
343 interp_ok(Scalar::from_u32(ecx.get_pid()))
348 }
349
350 fn unix_gettid(&mut self, link_name: &str) -> InterpResult<'tcx, Scalar> {
353 let ecx = self.eval_context_ref();
354 ecx.assert_target_os_is_unix(link_name);
355
356 interp_ok(Scalar::from_u32(ecx.get_tid(ecx.active_thread())))
359 }
360
361 fn uname(
363 &mut self,
364 uname: &OpTy<'tcx>,
365 fields_size: Option<&OpTy<'tcx>>,
366 ) -> InterpResult<'tcx, Scalar> {
367 let ecx = self.eval_context_mut();
368 ecx.assert_target_os_is_unix("uname");
369
370 let uname_ptr = ecx.read_pointer(uname)?;
371 let fields_size = match fields_size {
372 None => None,
373 Some(size) => Some(ecx.read_scalar(size)?.to_i32()?),
374 };
375
376 if ecx.ptr_is_null(uname_ptr)? {
377 return ecx.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
378 }
379
380 let uname = ecx.deref_pointer(uname)?;
381 let arch = ecx.machine.tcx.sess.target.arch.desc_symbol();
382 let mut values = vec![
384 ("sysname", "Miri"),
385 ("nodename", "Miri"),
386 ("release", env!("CARGO_PKG_VERSION")),
387 ("version", concat!("Miri ", env!("CARGO_PKG_VERSION"))),
388 ("machine", arch.as_str()),
389 ];
390 if matches!(ecx.machine.tcx.sess.target.os, Os::Linux | Os::Android) {
391 values.push(("domainname", "(none)"));
392 }
393
394 for (name, value) in values {
395 let field = ecx.project_field_named(&uname, name)?;
396 let size = field.layout().layout.size().bytes();
397 if fields_size.is_some_and(|fields_size| u64::try_from(fields_size) != Ok(size)) {
398 throw_unsup_format!(
399 "the fields size passed to `uname` does not match the type in the libc crate"
400 );
401 }
402 let (written, _) = ecx.write_c_str(value.as_bytes(), field.ptr(), size)?;
403 assert!(written); }
405 interp_ok(Scalar::from_i32(0))
406 }
407
408 fn apple_pthread_threadid_np(
413 &mut self,
414 thread_op: &OpTy<'tcx>,
415 tid_op: &OpTy<'tcx>,
416 ) -> InterpResult<'tcx, Scalar> {
417 let ecx = self.eval_context_mut();
418 ecx.assert_target_os(Os::MacOs, "pthread_threadip_np");
419
420 let tid_dest = ecx.read_pointer(tid_op)?;
421 if ecx.ptr_is_null(tid_dest)? {
422 return interp_ok(ecx.eval_libc("EINVAL"));
424 }
425
426 let thread = ecx.read_scalar(thread_op)?.to_int(ecx.libc_ty_layout("pthread_t").size)?;
427 let thread = if thread == 0 {
428 ecx.machine.threads.active_thread()
430 } else {
431 let Ok(thread) = ecx.thread_id_try_from(thread) else {
434 return interp_ok(ecx.eval_libc("ESRCH"));
435 };
436 thread
437 };
438
439 let tid = ecx.get_tid(thread);
441 let tid_dest = ecx.deref_pointer(tid_op)?;
442 ecx.write_int(tid, &tid_dest)?;
443
444 interp_ok(Scalar::from_u32(0))
446 }
447}