1use std::any::Any;
2use std::collections::BTreeMap;
3use std::fs::{File, Metadata};
4use std::io::{ErrorKind, IsTerminal, Seek, SeekFrom, Write};
5use std::marker::CoercePointee;
6use std::ops::Deref;
7use std::rc::{Rc, Weak};
8use std::{fs, io};
9
10use rustc_abi::Size;
11
12use crate::shims::unix::UnixFileDescription;
13use crate::*;
14
15#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
20pub struct FdId(usize);
21
22#[derive(Debug, Clone)]
23struct FdIdWith<T: ?Sized> {
24 id: FdId,
25 inner: T,
26}
27
28#[repr(transparent)]
31#[derive(CoercePointee, Debug)]
32pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
33
34impl<T: ?Sized> Clone for FileDescriptionRef<T> {
35 fn clone(&self) -> Self {
36 FileDescriptionRef(self.0.clone())
37 }
38}
39
40impl<T: ?Sized> Deref for FileDescriptionRef<T> {
41 type Target = T;
42 fn deref(&self) -> &T {
43 &self.0.inner
44 }
45}
46
47impl<T: ?Sized> FileDescriptionRef<T> {
48 pub fn id(&self) -> FdId {
49 self.0.id
50 }
51}
52
53#[derive(Debug)]
55pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
56
57impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
58 fn clone(&self) -> Self {
59 WeakFileDescriptionRef(self.0.clone())
60 }
61}
62
63impl<T: ?Sized> FileDescriptionRef<T> {
64 pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
65 WeakFileDescriptionRef(Rc::downgrade(&this.0))
66 }
67}
68
69impl<T: ?Sized> WeakFileDescriptionRef<T> {
70 pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
71 self.0.upgrade().map(FileDescriptionRef)
72 }
73}
74
75impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
76 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
77 }
81}
82
83pub trait FileDescriptionExt: 'static {
87 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
88
89 fn close_ref<'tcx>(
92 self: FileDescriptionRef<Self>,
93 communicate_allowed: bool,
94 ecx: &mut MiriInterpCx<'tcx>,
95 ) -> InterpResult<'tcx, io::Result<()>>;
96}
97
98impl<T: FileDescription + 'static> FileDescriptionExt for T {
99 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
100 self.0
101 }
102
103 fn close_ref<'tcx>(
104 self: FileDescriptionRef<Self>,
105 communicate_allowed: bool,
106 ecx: &mut MiriInterpCx<'tcx>,
107 ) -> InterpResult<'tcx, io::Result<()>> {
108 match Rc::into_inner(self.0) {
109 Some(fd) => {
110 ecx.machine.epoll_interests.remove(fd.id);
112
113 fd.inner.close(communicate_allowed, ecx)
114 }
115 None => {
116 interp_ok(Ok(()))
118 }
119 }
120 }
121}
122
123pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
124
125impl FileDescriptionRef<dyn FileDescription> {
126 pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
127 let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
128 Some(FileDescriptionRef(inner))
129 }
130}
131
132pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
134 fn name(&self) -> &'static str;
135
136 fn read<'tcx>(
143 self: FileDescriptionRef<Self>,
144 _communicate_allowed: bool,
145 _ptr: Pointer,
146 _len: usize,
147 _ecx: &mut MiriInterpCx<'tcx>,
148 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
149 ) -> InterpResult<'tcx> {
150 throw_unsup_format!("cannot read from {}", self.name());
151 }
152
153 fn write<'tcx>(
160 self: FileDescriptionRef<Self>,
161 _communicate_allowed: bool,
162 _ptr: Pointer,
163 _len: usize,
164 _ecx: &mut MiriInterpCx<'tcx>,
165 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
166 ) -> InterpResult<'tcx> {
167 throw_unsup_format!("cannot write to {}", self.name());
168 }
169
170 fn nondet_short_accesses(&self) -> bool {
172 true
173 }
174
175 fn seek<'tcx>(
178 &self,
179 _communicate_allowed: bool,
180 _offset: SeekFrom,
181 ) -> InterpResult<'tcx, io::Result<u64>> {
182 throw_unsup_format!("cannot seek on {}", self.name());
183 }
184
185 fn close<'tcx>(
187 self,
188 _communicate_allowed: bool,
189 _ecx: &mut MiriInterpCx<'tcx>,
190 ) -> InterpResult<'tcx, io::Result<()>>
191 where
192 Self: Sized,
193 {
194 throw_unsup_format!("cannot close {}", self.name());
195 }
196
197 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
198 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
199 }
200
201 fn is_tty(&self, _communicate_allowed: bool) -> bool {
202 false
205 }
206
207 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
208 panic!("Not a unix file descriptor: {}", self.name());
209 }
210
211 fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
213 throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
214 }
215
216 fn set_flags<'tcx>(
218 &self,
219 _flag: i32,
220 _ecx: &mut MiriInterpCx<'tcx>,
221 ) -> InterpResult<'tcx, Scalar> {
222 throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
223 }
224}
225
226impl FileDescription for io::Stdin {
227 fn name(&self) -> &'static str {
228 "stdin"
229 }
230
231 fn read<'tcx>(
232 self: FileDescriptionRef<Self>,
233 communicate_allowed: bool,
234 ptr: Pointer,
235 len: usize,
236 ecx: &mut MiriInterpCx<'tcx>,
237 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
238 ) -> InterpResult<'tcx> {
239 if !communicate_allowed {
240 helpers::isolation_abort_error("`read` from stdin")?;
242 }
243
244 let result = ecx.read_from_host(&*self, len, ptr)?;
245 finish.call(ecx, result)
246 }
247
248 fn is_tty(&self, communicate_allowed: bool) -> bool {
249 communicate_allowed && self.is_terminal()
250 }
251}
252
253impl FileDescription for io::Stdout {
254 fn name(&self) -> &'static str {
255 "stdout"
256 }
257
258 fn write<'tcx>(
259 self: FileDescriptionRef<Self>,
260 _communicate_allowed: bool,
261 ptr: Pointer,
262 len: usize,
263 ecx: &mut MiriInterpCx<'tcx>,
264 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
265 ) -> InterpResult<'tcx> {
266 let result = ecx.write_to_host(&*self, len, ptr)?;
268 io::stdout().flush().unwrap();
274
275 finish.call(ecx, result)
276 }
277
278 fn is_tty(&self, communicate_allowed: bool) -> bool {
279 communicate_allowed && self.is_terminal()
280 }
281}
282
283impl FileDescription for io::Stderr {
284 fn name(&self) -> &'static str {
285 "stderr"
286 }
287
288 fn write<'tcx>(
289 self: FileDescriptionRef<Self>,
290 _communicate_allowed: bool,
291 ptr: Pointer,
292 len: usize,
293 ecx: &mut MiriInterpCx<'tcx>,
294 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
295 ) -> InterpResult<'tcx> {
296 let result = ecx.write_to_host(&*self, len, ptr)?;
298 finish.call(ecx, result)
300 }
301
302 fn is_tty(&self, communicate_allowed: bool) -> bool {
303 communicate_allowed && self.is_terminal()
304 }
305}
306
307#[derive(Debug)]
308pub struct FileHandle {
309 pub(crate) file: File,
310 pub(crate) writable: bool,
311}
312
313impl FileDescription for FileHandle {
314 fn name(&self) -> &'static str {
315 "file"
316 }
317
318 fn read<'tcx>(
319 self: FileDescriptionRef<Self>,
320 communicate_allowed: bool,
321 ptr: Pointer,
322 len: usize,
323 ecx: &mut MiriInterpCx<'tcx>,
324 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
325 ) -> InterpResult<'tcx> {
326 assert!(communicate_allowed, "isolation should have prevented even opening a file");
327
328 let result = ecx.read_from_host(&self.file, len, ptr)?;
329 finish.call(ecx, result)
330 }
331
332 fn write<'tcx>(
333 self: FileDescriptionRef<Self>,
334 communicate_allowed: bool,
335 ptr: Pointer,
336 len: usize,
337 ecx: &mut MiriInterpCx<'tcx>,
338 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
339 ) -> InterpResult<'tcx> {
340 assert!(communicate_allowed, "isolation should have prevented even opening a file");
341
342 if !self.writable {
343 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
350 }
351 let result = ecx.write_to_host(&self.file, len, ptr)?;
352 finish.call(ecx, result)
353 }
354
355 fn seek<'tcx>(
356 &self,
357 communicate_allowed: bool,
358 offset: SeekFrom,
359 ) -> InterpResult<'tcx, io::Result<u64>> {
360 assert!(communicate_allowed, "isolation should have prevented even opening a file");
361 interp_ok((&mut &self.file).seek(offset))
362 }
363
364 fn close<'tcx>(
365 self,
366 communicate_allowed: bool,
367 _ecx: &mut MiriInterpCx<'tcx>,
368 ) -> InterpResult<'tcx, io::Result<()>> {
369 assert!(communicate_allowed, "isolation should have prevented even opening a file");
370 if self.writable {
372 let result = self.file.sync_all();
375 drop(self.file);
377 interp_ok(result)
378 } else {
379 drop(self.file);
386 interp_ok(Ok(()))
387 }
388 }
389
390 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
391 interp_ok(self.file.metadata())
392 }
393
394 fn is_tty(&self, communicate_allowed: bool) -> bool {
395 communicate_allowed && self.file.is_terminal()
396 }
397
398 fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
399 assert!(
400 ecx.target_os_is_unix(),
401 "unix file operations are only available for unix targets"
402 );
403 self
404 }
405}
406
407#[derive(Debug)]
409pub struct NullOutput;
410
411impl FileDescription for NullOutput {
412 fn name(&self) -> &'static str {
413 "stderr and stdout"
414 }
415
416 fn write<'tcx>(
417 self: FileDescriptionRef<Self>,
418 _communicate_allowed: bool,
419 _ptr: Pointer,
420 len: usize,
421 ecx: &mut MiriInterpCx<'tcx>,
422 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
423 ) -> InterpResult<'tcx> {
424 finish.call(ecx, Ok(len))
426 }
427}
428
429pub type FdNum = i32;
431
432#[derive(Debug)]
434pub struct FdTable {
435 pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
436 next_file_description_id: FdId,
438}
439
440impl VisitProvenance for FdTable {
441 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
442 }
444}
445
446impl FdTable {
447 fn new() -> Self {
448 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
449 }
450 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
451 let mut fds = FdTable::new();
452 fds.insert_new(io::stdin());
453 if mute_stdout_stderr {
454 assert_eq!(fds.insert_new(NullOutput), 1);
455 assert_eq!(fds.insert_new(NullOutput), 2);
456 } else {
457 assert_eq!(fds.insert_new(io::stdout()), 1);
458 assert_eq!(fds.insert_new(io::stderr()), 2);
459 }
460 fds
461 }
462
463 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
464 let file_handle =
465 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
466 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
467 file_handle
468 }
469
470 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
472 let fd_ref = self.new_ref(fd);
473 self.insert(fd_ref)
474 }
475
476 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
477 self.insert_with_min_num(fd_ref, 0)
478 }
479
480 pub fn insert_with_min_num(
482 &mut self,
483 file_handle: DynFileDescriptionRef,
484 min_fd_num: FdNum,
485 ) -> FdNum {
486 let candidate_new_fd =
491 self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
492 if *fd_num != counter {
493 Some(counter)
496 } else {
497 None
499 }
500 });
501 let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
502 self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
505 });
506
507 self.fds.try_insert(new_fd_num, file_handle).unwrap();
508 new_fd_num
509 }
510
511 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
512 let fd = self.fds.get(&fd_num)?;
513 Some(fd.clone())
514 }
515
516 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
517 self.fds.remove(&fd_num)
518 }
519
520 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
521 self.fds.contains_key(&fd_num)
522 }
523}
524
525impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
526pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
527 fn read_from_host(
530 &mut self,
531 mut file: impl io::Read,
532 len: usize,
533 ptr: Pointer,
534 ) -> InterpResult<'tcx, Result<usize, IoError>> {
535 let this = self.eval_context_mut();
536
537 let mut bytes = vec![0; len];
538 let result = file.read(&mut bytes);
539 match result {
540 Ok(read_size) => {
541 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
545 interp_ok(Ok(read_size))
546 }
547 Err(e) => interp_ok(Err(IoError::HostError(e))),
548 }
549 }
550
551 fn write_to_host(
553 &mut self,
554 mut file: impl io::Write,
555 len: usize,
556 ptr: Pointer,
557 ) -> InterpResult<'tcx, Result<usize, IoError>> {
558 let this = self.eval_context_mut();
559
560 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
561 let result = file.write(bytes);
562 interp_ok(result.map_err(IoError::HostError))
563 }
564}