1use std::any::Any;
2use std::collections::BTreeMap;
3use std::io::{IsTerminal, SeekFrom, Write};
4use std::marker::CoercePointee;
5use std::ops::Deref;
6use std::rc::{Rc, Weak};
7use std::{fs, io};
8
9use rustc_abi::Size;
10
11use crate::shims::unix::UnixFileDescription;
12use crate::*;
13
14#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
19pub struct FdId(usize);
20
21#[derive(Debug, Clone)]
22struct FdIdWith<T: ?Sized> {
23 id: FdId,
24 inner: T,
25}
26
27#[repr(transparent)]
30#[derive(CoercePointee, Debug)]
31pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
32
33impl<T: ?Sized> Clone for FileDescriptionRef<T> {
34 fn clone(&self) -> Self {
35 FileDescriptionRef(self.0.clone())
36 }
37}
38
39impl<T: ?Sized> Deref for FileDescriptionRef<T> {
40 type Target = T;
41 fn deref(&self) -> &T {
42 &self.0.inner
43 }
44}
45
46impl<T: ?Sized> FileDescriptionRef<T> {
47 pub fn id(&self) -> FdId {
48 self.0.id
49 }
50}
51
52#[derive(Debug)]
54pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
55
56impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
57 fn clone(&self) -> Self {
58 WeakFileDescriptionRef(self.0.clone())
59 }
60}
61
62impl<T: ?Sized> FileDescriptionRef<T> {
63 pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
64 WeakFileDescriptionRef(Rc::downgrade(&this.0))
65 }
66}
67
68impl<T: ?Sized> WeakFileDescriptionRef<T> {
69 pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
70 self.0.upgrade().map(FileDescriptionRef)
71 }
72}
73
74impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
75 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
76 }
80}
81
82pub trait FileDescriptionExt: 'static {
86 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
87
88 fn close_ref<'tcx>(
91 self: FileDescriptionRef<Self>,
92 communicate_allowed: bool,
93 ecx: &mut MiriInterpCx<'tcx>,
94 ) -> InterpResult<'tcx, io::Result<()>>;
95}
96
97impl<T: FileDescription + 'static> FileDescriptionExt for T {
98 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
99 self.0
100 }
101
102 fn close_ref<'tcx>(
103 self: FileDescriptionRef<Self>,
104 communicate_allowed: bool,
105 ecx: &mut MiriInterpCx<'tcx>,
106 ) -> InterpResult<'tcx, io::Result<()>> {
107 match Rc::into_inner(self.0) {
108 Some(fd) => {
109 ecx.machine.epoll_interests.remove(fd.id);
111
112 fd.inner.close(communicate_allowed, ecx)
113 }
114 None => {
115 interp_ok(Ok(()))
117 }
118 }
119 }
120}
121
122pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
123
124impl FileDescriptionRef<dyn FileDescription> {
125 pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
126 let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
127 Some(FileDescriptionRef(inner))
128 }
129}
130
131pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
133 fn name(&self) -> &'static str;
134
135 fn read<'tcx>(
139 self: FileDescriptionRef<Self>,
140 _communicate_allowed: bool,
141 _ptr: Pointer,
142 _len: usize,
143 _ecx: &mut MiriInterpCx<'tcx>,
144 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
145 ) -> InterpResult<'tcx> {
146 throw_unsup_format!("cannot read from {}", self.name());
147 }
148
149 fn write<'tcx>(
153 self: FileDescriptionRef<Self>,
154 _communicate_allowed: bool,
155 _ptr: Pointer,
156 _len: usize,
157 _ecx: &mut MiriInterpCx<'tcx>,
158 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
159 ) -> InterpResult<'tcx> {
160 throw_unsup_format!("cannot write to {}", self.name());
161 }
162
163 fn seek<'tcx>(
166 &self,
167 _communicate_allowed: bool,
168 _offset: SeekFrom,
169 ) -> InterpResult<'tcx, io::Result<u64>> {
170 throw_unsup_format!("cannot seek on {}", self.name());
171 }
172
173 fn close<'tcx>(
175 self,
176 _communicate_allowed: bool,
177 _ecx: &mut MiriInterpCx<'tcx>,
178 ) -> InterpResult<'tcx, io::Result<()>>
179 where
180 Self: Sized,
181 {
182 throw_unsup_format!("cannot close {}", self.name());
183 }
184
185 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
186 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
187 }
188
189 fn is_tty(&self, _communicate_allowed: bool) -> bool {
190 false
193 }
194
195 fn as_unix(&self) -> &dyn UnixFileDescription {
196 panic!("Not a unix file descriptor: {}", self.name());
197 }
198}
199
200impl FileDescription for io::Stdin {
201 fn name(&self) -> &'static str {
202 "stdin"
203 }
204
205 fn read<'tcx>(
206 self: FileDescriptionRef<Self>,
207 communicate_allowed: bool,
208 ptr: Pointer,
209 len: usize,
210 ecx: &mut MiriInterpCx<'tcx>,
211 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
212 ) -> InterpResult<'tcx> {
213 if !communicate_allowed {
214 helpers::isolation_abort_error("`read` from stdin")?;
216 }
217
218 let result = ecx.read_from_host(&*self, len, ptr)?;
219 finish.call(ecx, result)
220 }
221
222 fn is_tty(&self, communicate_allowed: bool) -> bool {
223 communicate_allowed && self.is_terminal()
224 }
225}
226
227impl FileDescription for io::Stdout {
228 fn name(&self) -> &'static str {
229 "stdout"
230 }
231
232 fn write<'tcx>(
233 self: FileDescriptionRef<Self>,
234 _communicate_allowed: bool,
235 ptr: Pointer,
236 len: usize,
237 ecx: &mut MiriInterpCx<'tcx>,
238 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
239 ) -> InterpResult<'tcx> {
240 let result = ecx.write_to_host(&*self, len, ptr)?;
242 io::stdout().flush().unwrap();
248
249 finish.call(ecx, result)
250 }
251
252 fn is_tty(&self, communicate_allowed: bool) -> bool {
253 communicate_allowed && self.is_terminal()
254 }
255}
256
257impl FileDescription for io::Stderr {
258 fn name(&self) -> &'static str {
259 "stderr"
260 }
261
262 fn write<'tcx>(
263 self: FileDescriptionRef<Self>,
264 _communicate_allowed: bool,
265 ptr: Pointer,
266 len: usize,
267 ecx: &mut MiriInterpCx<'tcx>,
268 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
269 ) -> InterpResult<'tcx> {
270 let result = ecx.write_to_host(&*self, len, ptr)?;
272 finish.call(ecx, result)
274 }
275
276 fn is_tty(&self, communicate_allowed: bool) -> bool {
277 communicate_allowed && self.is_terminal()
278 }
279}
280
281#[derive(Debug)]
283pub struct NullOutput;
284
285impl FileDescription for NullOutput {
286 fn name(&self) -> &'static str {
287 "stderr and stdout"
288 }
289
290 fn write<'tcx>(
291 self: FileDescriptionRef<Self>,
292 _communicate_allowed: bool,
293 _ptr: Pointer,
294 len: usize,
295 ecx: &mut MiriInterpCx<'tcx>,
296 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
297 ) -> InterpResult<'tcx> {
298 finish.call(ecx, Ok(len))
300 }
301}
302
303#[derive(Debug)]
305pub struct FdTable {
306 pub fds: BTreeMap<i32, DynFileDescriptionRef>,
307 next_file_description_id: FdId,
309}
310
311impl VisitProvenance for FdTable {
312 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
313 }
315}
316
317impl FdTable {
318 fn new() -> Self {
319 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
320 }
321 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
322 let mut fds = FdTable::new();
323 fds.insert_new(io::stdin());
324 if mute_stdout_stderr {
325 assert_eq!(fds.insert_new(NullOutput), 1);
326 assert_eq!(fds.insert_new(NullOutput), 2);
327 } else {
328 assert_eq!(fds.insert_new(io::stdout()), 1);
329 assert_eq!(fds.insert_new(io::stderr()), 2);
330 }
331 fds
332 }
333
334 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
335 let file_handle =
336 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
337 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
338 file_handle
339 }
340
341 pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
343 let fd_ref = self.new_ref(fd);
344 self.insert(fd_ref)
345 }
346
347 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
348 self.insert_with_min_num(fd_ref, 0)
349 }
350
351 pub fn insert_with_min_num(
353 &mut self,
354 file_handle: DynFileDescriptionRef,
355 min_fd_num: i32,
356 ) -> i32 {
357 let candidate_new_fd =
362 self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
363 if *fd_num != counter {
364 Some(counter)
367 } else {
368 None
370 }
371 });
372 let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
373 self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
376 });
377
378 self.fds.try_insert(new_fd_num, file_handle).unwrap();
379 new_fd_num
380 }
381
382 pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
383 let fd = self.fds.get(&fd_num)?;
384 Some(fd.clone())
385 }
386
387 pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
388 self.fds.remove(&fd_num)
389 }
390
391 pub fn is_fd_num(&self, fd_num: i32) -> bool {
392 self.fds.contains_key(&fd_num)
393 }
394}
395
396impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
397pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
398 fn read_from_host(
401 &mut self,
402 mut file: impl io::Read,
403 len: usize,
404 ptr: Pointer,
405 ) -> InterpResult<'tcx, Result<usize, IoError>> {
406 let this = self.eval_context_mut();
407
408 let mut bytes = vec![0; len];
409 let result = file.read(&mut bytes);
410 match result {
411 Ok(read_size) => {
412 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
416 interp_ok(Ok(read_size))
417 }
418 Err(e) => interp_ok(Err(IoError::HostError(e))),
419 }
420 }
421
422 fn write_to_host(
424 &mut self,
425 mut file: impl io::Write,
426 len: usize,
427 ptr: Pointer,
428 ) -> InterpResult<'tcx, Result<usize, IoError>> {
429 let this = self.eval_context_mut();
430
431 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
432 let result = file.write(bytes);
433 interp_ok(result.map_err(IoError::HostError))
434 }
435}