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.mut_raw_ptr;
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.mut_raw_ptr.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(&mut self, name_op: &OpTy<'tcx>) -> InterpResult<'tcx, Pointer> {
130 let this = self.eval_context_mut();
131 this.assert_target_os_is_unix("getenv");
132
133 let name_ptr = this.read_pointer(name_op)?;
134 let name = this.read_os_str_from_c_str(name_ptr)?;
135
136 let var_ptr = this.machine.env_vars.unix().get_ptr(this, 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 this = self.eval_context_mut();
146 this.assert_target_os_is_unix("setenv");
147
148 let name_ptr = this.read_pointer(name_op)?;
149 let value_ptr = this.read_pointer(value_op)?;
150
151 let mut new = None;
152 if !this.ptr_is_null(name_ptr)? {
153 let name = this.read_os_str_from_c_str(name_ptr)?;
154 if !name.is_empty() && !name.to_string_lossy().contains('=') {
155 let value = this.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(this, &name, &value)?;
161 if let Some(var) = this.machine.env_vars.unix_mut().map.insert(name, var_ptr) {
162 this.deallocate_ptr(var, None, MiriMemoryKind::Machine.into())?;
163 }
164 this.update_environ()?;
165 interp_ok(Scalar::from_i32(0)) } else {
167 this.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 this = self.eval_context_mut();
174 this.assert_target_os_is_unix("unsetenv");
175
176 let name_ptr = this.read_pointer(name_op)?;
177 let mut success = None;
178 if !this.ptr_is_null(name_ptr)? {
179 let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
180 if !name.is_empty() && !name.to_string_lossy().contains('=') {
181 success = Some(this.machine.env_vars.unix_mut().map.remove(&name));
182 }
183 }
184 if let Some(old) = success {
185 if let Some(var) = old {
186 this.deallocate_ptr(var, None, MiriMemoryKind::Machine.into())?;
187 }
188 this.update_environ()?;
189 interp_ok(Scalar::from_i32(0))
190 } else {
191 this.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 this = self.eval_context_mut();
198 this.assert_target_os_is_unix("getcwd");
199
200 let buf = this.read_pointer(buf_op)?;
201 let size = this.read_target_usize(size_op)?;
202
203 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
204 this.reject_in_isolation("`getcwd`", reject_with)?;
205 this.set_last_error(ErrorKind::PermissionDenied)?;
206 return interp_ok(Pointer::null());
207 }
208
209 match env::current_dir() {
211 Ok(cwd) => {
212 if this.write_path_to_c_str(&cwd, buf, size)?.0 {
213 return interp_ok(buf);
214 }
215 this.set_last_error(LibcError("ERANGE"))?;
216 }
217 Err(e) => this.set_last_error(e)?,
218 }
219
220 interp_ok(Pointer::null())
221 }
222
223 fn gethostname(
224 &mut self,
225 name_op: &OpTy<'tcx>,
226 len_op: &OpTy<'tcx>,
227 ) -> InterpResult<'tcx, Scalar> {
228 let this = self.eval_context_mut();
229 this.assert_target_os_is_unix("gethostname");
230
231 let name = this.read_pointer(name_op)?;
232 let len = this.read_target_usize(len_op)?;
233
234 if len == 0 {
243 throw_ub_format!("`gethostname` called with a length of zero");
244 }
245 this.check_ptr_access(name, Size::from_bytes(len), CheckInAllocMsg::MemoryAccess)?;
249
250 if len > u64::try_from(HOSTNAME.len()).unwrap() {
251 let (written, _) = this.write_c_str(HOSTNAME, name, len)?;
252 assert!(written); return interp_ok(Scalar::from_i32(0));
254 }
255
256 match (&this.tcx.sess.target.os, &this.tcx.sess.target.env) {
263 (Os::Android, _) => {
264 this.set_errno_and_return_neg1_i32(LibcError("ENAMETOOLONG"))
267 }
268 (Os::Linux, Env::Gnu) | (Os::FreeBsd, _) => {
269 let len: usize = len.try_into().unwrap();
271 this.write_bytes_ptr(name, HOSTNAME[..len].iter().copied())?;
272 this.set_errno_and_return_neg1_i32(LibcError("ENAMETOOLONG"))
273 }
274 (Os::Linux, _) | (Os::MacOs, _) | (Os::Solaris, _) | (Os::Illumos, _) => {
275 let len: usize = len.try_into().unwrap();
277 let copy_len = len.strict_sub(1);
278 this.write_bytes_ptr(
279 name,
280 HOSTNAME[..copy_len].iter().copied().chain(std::iter::once(0)),
281 )?;
282
283 interp_ok(Scalar::from_i32(0))
291 }
292 _ => {
293 throw_unsup_format!("`gethostname` is not supported on {}", this.tcx.sess.target.os)
294 }
295 }
296 }
297
298 fn chdir(&mut self, path_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
299 let this = self.eval_context_mut();
300 this.assert_target_os_is_unix("chdir");
301
302 let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
303
304 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
305 this.reject_in_isolation("`chdir`", reject_with)?;
306 return this.set_errno_and_return_neg1_i32(ErrorKind::PermissionDenied);
307 }
308
309 let result = env::set_current_dir(path).map(|()| 0);
310 interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
311 }
312
313 fn update_environ(&mut self) -> InterpResult<'tcx> {
315 let this = self.eval_context_mut();
316 let environ = this.machine.env_vars.unix().environ.clone();
318 let old_vars_ptr = this.read_pointer(&environ)?;
319 this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Machine.into())?;
320
321 let vals = this.machine.env_vars.unix().map.values().copied().collect();
323 let environ_block = alloc_environ_block(this, vals)?;
324 this.write_pointer(environ_block, &environ)?;
325
326 interp_ok(())
327 }
328
329 fn getpid(&mut self) -> InterpResult<'tcx, Scalar> {
330 let this = self.eval_context_mut();
331 this.assert_target_os_is_unix("getpid");
332
333 interp_ok(Scalar::from_u32(this.get_pid()))
338 }
339
340 fn unix_gettid(&mut self, link_name: &str) -> InterpResult<'tcx, Scalar> {
343 let this = self.eval_context_ref();
344 this.assert_target_os_is_unix(link_name);
345
346 interp_ok(Scalar::from_u32(this.get_tid(this.active_thread())))
349 }
350
351 fn uname(
353 &mut self,
354 uname: &OpTy<'tcx>,
355 fields_size: Option<&OpTy<'tcx>>,
356 ) -> InterpResult<'tcx, Scalar> {
357 let this = self.eval_context_mut();
358 this.assert_target_os_is_unix("uname");
359
360 let uname_ptr = this.read_pointer(uname)?;
361 let fields_size = match fields_size {
362 None => None,
363 Some(size) => Some(this.read_scalar(size)?.to_i32()?),
364 };
365
366 if this.ptr_is_null(uname_ptr)? {
367 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));
368 }
369
370 let uname = this.deref_pointer_as(uname, this.libc_ty_layout("utsname"))?;
371 let arch = this.machine.tcx.sess.target.arch.desc_symbol();
372 let mut values = vec![
374 ("sysname", "Miri"),
375 ("nodename", "Miri"),
376 ("release", env!("CARGO_PKG_VERSION")),
377 ("version", concat!("Miri ", env!("CARGO_PKG_VERSION"))),
378 ("machine", arch.as_str()),
379 ];
380 if matches!(this.machine.tcx.sess.target.os, Os::Linux | Os::Android) {
381 values.push(("domainname", "(none)"));
382 }
383
384 for (name, value) in values {
385 let field = this.project_field_named(&uname, name)?;
386 let size = field.layout().layout.size().bytes();
387 if fields_size.is_some_and(|fields_size| u64::try_from(fields_size) != Ok(size)) {
388 throw_unsup_format!(
389 "the fields size passed to `uname` does not match the type in the libc crate"
390 );
391 }
392 let (written, _) = this.write_c_str(value.as_bytes(), field.ptr(), size)?;
393 assert!(written); }
395 interp_ok(Scalar::from_i32(0))
396 }
397
398 fn apple_pthread_threadid_np(
403 &mut self,
404 thread_op: &OpTy<'tcx>,
405 tid_op: &OpTy<'tcx>,
406 ) -> InterpResult<'tcx, Scalar> {
407 let this = self.eval_context_mut();
408 this.assert_target_os(Os::MacOs, "pthread_threadip_np");
409
410 let tid_dest = this.read_pointer(tid_op)?;
411 if this.ptr_is_null(tid_dest)? {
412 return interp_ok(this.eval_libc("EINVAL"));
414 }
415
416 let thread = this.read_scalar(thread_op)?.to_int(this.libc_ty_layout("pthread_t").size)?;
417 let thread = if thread == 0 {
418 this.machine.threads.active_thread()
420 } else {
421 let Ok(thread) = this.thread_id_try_from(thread) else {
423 return interp_ok(this.eval_libc("ESRCH"));
424 };
425 thread
426 };
427
428 let tid = this.get_tid(thread);
430 let tid_dest = this.deref_pointer_as(tid_op, this.machine.layouts.u64)?;
431 this.write_int(tid, &tid_dest)?;
432
433 interp_ok(Scalar::from_u32(0))
435 }
436}