1use std::any::Any;
2use std::collections::BTreeMap;
3use std::fs::{Dir, File};
4use std::io::{ErrorKind, IsTerminal, Read, 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
22impl FdId {
23 pub fn to_usize(self) -> usize {
24 self.0
25 }
26
27 pub fn new_unchecked(id: usize) -> Self {
29 Self(id)
30 }
31}
32
33#[derive(Debug, Clone)]
34struct FdIdWith<T: ?Sized> {
35 id: FdId,
36 inner: T,
37}
38
39#[repr(transparent)]
42#[derive(CoercePointee, Debug)]
43pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
45
46impl<T: ?Sized> Clone for FileDescriptionRef<T> {
47 fn clone(&self) -> Self {
48 FileDescriptionRef(self.0.clone())
49 }
50}
51
52impl<T: ?Sized> Deref for FileDescriptionRef<T> {
53 type Target = T;
54 fn deref(&self) -> &T {
55 &self.0.inner
56 }
57}
58
59impl<T: ?Sized> FileDescriptionRef<T> {
60 pub fn id(&self) -> FdId {
61 self.0.id
62 }
63}
64
65impl<T: ?Sized> PartialEq for FileDescriptionRef<T> {
66 fn eq(&self, other: &Self) -> bool {
67 self.0.id == other.0.id
68 }
69}
70
71impl<T: ?Sized> Eq for FileDescriptionRef<T> {}
72
73#[derive(Debug)]
75pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
76
77impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
78 fn clone(&self) -> Self {
79 WeakFileDescriptionRef(self.0.clone())
80 }
81}
82
83impl<T: ?Sized> FileDescriptionRef<T> {
84 pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
85 WeakFileDescriptionRef(Rc::downgrade(&this.0))
86 }
87}
88
89impl<T: ?Sized> WeakFileDescriptionRef<T> {
90 pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
91 self.0.upgrade().map(FileDescriptionRef)
92 }
93
94 pub fn is_closed(&self) -> bool {
97 self.0.strong_count() == 0
98 }
99}
100
101impl<T> VisitProvenance for FileDescriptionRef<T> {
102 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
103 }
105}
106
107pub trait FileDescriptionExt: 'static {
111 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
112}
113
114impl<T: FileDescription + 'static> FileDescriptionExt for T {
115 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
116 self.0
117 }
118}
119
120pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
121pub type WeakDynFileDescriptionRef = WeakFileDescriptionRef<dyn FileDescription>;
122
123impl FileDescriptionRef<dyn FileDescription> {
124 pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
125 let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
126 Some(FileDescriptionRef(inner))
127 }
128}
129
130pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
132 fn name(&self) -> &'static str;
133
134 fn read<'tcx>(
141 self: FileDescriptionRef<Self>,
142 _communicate_allowed: bool,
143 _ptr: Pointer,
144 _len: usize,
145 _ecx: &mut MiriInterpCx<'tcx>,
146 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
147 ) -> InterpResult<'tcx> {
148 throw_unsup_format!("cannot read from {}", self.name());
149 }
150
151 fn write<'tcx>(
158 self: FileDescriptionRef<Self>,
159 _communicate_allowed: bool,
160 _ptr: Pointer,
161 _len: usize,
162 _ecx: &mut MiriInterpCx<'tcx>,
163 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
164 ) -> InterpResult<'tcx> {
165 throw_unsup_format!("cannot write to {}", self.name());
166 }
167
168 fn short_fd_operations(&self) -> bool {
170 false
172 }
173
174 fn seek<'tcx>(
177 &self,
178 _communicate_allowed: bool,
179 _offset: SeekFrom,
180 ) -> InterpResult<'tcx, io::Result<u64>> {
181 throw_unsup_format!("cannot seek on {}", self.name());
182 }
183
184 fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
189 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
190 }
191
192 fn is_tty(&self, _communicate_allowed: bool) -> bool {
193 false
196 }
197
198 fn as_unix<'tcx>(
199 self: FileDescriptionRef<Self>,
200 _ecx: &MiriInterpCx<'tcx>,
201 ) -> FileDescriptionRef<dyn UnixFileDescription> {
202 panic!("Not a unix file descriptor: {}", self.name());
203 }
204
205 fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
207 throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
208 }
209
210 fn set_flags<'tcx>(
212 &self,
213 _flag: i32,
214 _ecx: &mut MiriInterpCx<'tcx>,
215 ) -> InterpResult<'tcx, Scalar> {
216 throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
217 }
218
219 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
221 None
222 }
223
224 fn readiness(&self) -> Readiness {
226 panic!("FD type {} implements `readiness_watched` but not `readiness`", self.name());
227 }
228}
229
230#[derive(Debug)]
231struct Stdin {
232 stdin: io::Stdin,
233 watched: ReadinessWatched,
234}
235
236impl Stdin {
237 fn new() -> Self {
238 Self { stdin: io::stdin(), watched: ReadinessWatched::default() }
239 }
240}
241
242impl FileDescription for Stdin {
243 fn name(&self) -> &'static str {
244 "stdin"
245 }
246
247 fn read<'tcx>(
248 self: FileDescriptionRef<Self>,
249 communicate_allowed: bool,
250 ptr: Pointer,
251 len: usize,
252 ecx: &mut MiriInterpCx<'tcx>,
253 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
254 ) -> InterpResult<'tcx> {
255 if !communicate_allowed {
256 helpers::isolation_abort_error("`read` from stdin")?;
258 }
259
260 let result = ecx.read_from_host(|buf| (&mut &self.stdin).read(buf), len, ptr)?;
262 finish.call(ecx, result)
263 }
264
265 fn is_tty(&self, communicate_allowed: bool) -> bool {
266 communicate_allowed && self.stdin.is_terminal()
267 }
268
269 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
270 Some(&self.watched)
271 }
272
273 fn readiness(&self) -> Readiness {
274 let mut readiness = Readiness::EMPTY;
277 readiness.readable = true;
278 readiness.writable = true;
279 readiness
280 }
281}
282
283#[derive(Debug)]
284struct Stdout {
285 stdout: io::Stdout,
286 watched: ReadinessWatched,
287}
288
289impl Stdout {
290 fn new() -> Self {
291 Self { stdout: io::stdout(), watched: ReadinessWatched::default() }
292 }
293}
294
295impl FileDescription for Stdout {
296 fn name(&self) -> &'static str {
297 "stdout"
298 }
299
300 fn write<'tcx>(
301 self: FileDescriptionRef<Self>,
302 _communicate_allowed: bool,
303 ptr: Pointer,
304 len: usize,
305 ecx: &mut MiriInterpCx<'tcx>,
306 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
307 ) -> InterpResult<'tcx> {
308 let result = ecx.write_to_host(&self.stdout, len, ptr)?;
310 io::stdout().flush().unwrap();
316
317 finish.call(ecx, result)
318 }
319
320 fn is_tty(&self, communicate_allowed: bool) -> bool {
321 communicate_allowed && self.stdout.is_terminal()
322 }
323
324 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
325 Some(&self.watched)
326 }
327
328 fn readiness(&self) -> Readiness {
329 let mut readiness = Readiness::EMPTY;
331 readiness.writable = true;
332 readiness
333 }
334}
335
336#[derive(Debug)]
337struct Stderr {
338 stderr: io::Stderr,
339 watched: ReadinessWatched,
340}
341
342impl Stderr {
343 fn new() -> Self {
344 Self { stderr: io::stderr(), watched: ReadinessWatched::default() }
345 }
346}
347
348impl FileDescription for Stderr {
349 fn name(&self) -> &'static str {
350 "stderr"
351 }
352
353 fn write<'tcx>(
354 self: FileDescriptionRef<Self>,
355 _communicate_allowed: bool,
356 ptr: Pointer,
357 len: usize,
358 ecx: &mut MiriInterpCx<'tcx>,
359 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
360 ) -> InterpResult<'tcx> {
361 let result = ecx.write_to_host(&self.stderr, len, ptr)?;
363 finish.call(ecx, result)
365 }
366
367 fn is_tty(&self, communicate_allowed: bool) -> bool {
368 communicate_allowed && self.stderr.is_terminal()
369 }
370
371 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
372 Some(&self.watched)
373 }
374
375 fn readiness(&self) -> Readiness {
376 let mut readiness = Readiness::EMPTY;
378 readiness.writable = true;
379 readiness
380 }
381}
382
383#[derive(Debug)]
385pub struct NullOutput {
386 watched: ReadinessWatched,
387}
388
389impl NullOutput {
390 fn new() -> Self {
391 Self { watched: ReadinessWatched::default() }
392 }
393}
394
395impl FileDescription for NullOutput {
396 fn name(&self) -> &'static str {
397 "null output"
398 }
399
400 fn write<'tcx>(
401 self: FileDescriptionRef<Self>,
402 _communicate_allowed: bool,
403 _ptr: Pointer,
404 len: usize,
405 ecx: &mut MiriInterpCx<'tcx>,
406 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
407 ) -> InterpResult<'tcx> {
408 finish.call(ecx, Ok(len))
410 }
411
412 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
413 Some(&self.watched)
414 }
415
416 fn readiness(&self) -> Readiness {
417 let mut readiness = Readiness::EMPTY;
419 readiness.writable = true;
420 readiness
421 }
422}
423
424#[derive(Debug)]
425pub struct FileHandle {
426 pub(crate) file: File,
427 pub(crate) readable: bool,
428 pub(crate) writable: bool,
429}
430
431impl FileDescription for FileHandle {
432 fn name(&self) -> &'static str {
433 "file"
434 }
435
436 fn read<'tcx>(
437 self: FileDescriptionRef<Self>,
438 communicate_allowed: bool,
439 ptr: Pointer,
440 len: usize,
441 ecx: &mut MiriInterpCx<'tcx>,
442 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
443 ) -> InterpResult<'tcx> {
444 assert!(communicate_allowed, "isolation should have prevented even opening a file");
445
446 if !self.readable {
447 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
448 }
449
450 let mut file = &self.file;
451 let result = ecx.read_from_host(|buf| file.read(buf), len, ptr)?;
452 finish.call(ecx, result)
453 }
454
455 fn write<'tcx>(
456 self: FileDescriptionRef<Self>,
457 communicate_allowed: bool,
458 ptr: Pointer,
459 len: usize,
460 ecx: &mut MiriInterpCx<'tcx>,
461 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
462 ) -> InterpResult<'tcx> {
463 assert!(communicate_allowed, "isolation should have prevented even opening a file");
464
465 if !self.writable {
466 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
473 }
474 let result = ecx.write_to_host(&self.file, len, ptr)?;
475 finish.call(ecx, result)
476 }
477
478 fn seek<'tcx>(
479 &self,
480 communicate_allowed: bool,
481 offset: SeekFrom,
482 ) -> InterpResult<'tcx, io::Result<u64>> {
483 assert!(communicate_allowed, "isolation should have prevented even opening a file");
484 interp_ok((&mut &self.file).seek(offset))
485 }
486
487 fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
488 interp_ok(Either::Left(self.file.metadata()))
489 }
490
491 fn is_tty(&self, communicate_allowed: bool) -> bool {
492 communicate_allowed && self.file.is_terminal()
493 }
494
495 fn short_fd_operations(&self) -> bool {
496 true
500 }
501
502 fn as_unix<'tcx>(
503 self: FileDescriptionRef<Self>,
504 ecx: &MiriInterpCx<'tcx>,
505 ) -> FileDescriptionRef<dyn UnixFileDescription> {
506 assert!(
507 ecx.target_os_is_unix(),
508 "unix file operations are only available for unix targets"
509 );
510 self
511 }
512}
513
514#[derive(Debug)]
515pub struct DirHandle {
516 pub(crate) dir: Dir,
517}
518
519impl FileDescription for DirHandle {
520 fn name(&self) -> &'static str {
521 "directory"
522 }
523
524 fn metadata<'tcx>(
525 &self,
526 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
527 interp_ok(Either::Left(self.dir.metadata()))
528 }
529}
530
531pub type FdNum = i32;
533
534#[derive(Debug)]
536pub struct FdTable {
537 fds: BTreeMap<FdNum, DynFileDescriptionRef>,
538 next_file_description_id: FdId,
540}
541
542impl VisitProvenance for FdTable {
543 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
544 }
546}
547
548impl FdTable {
549 fn new() -> Self {
550 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
551 }
552 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
553 let mut fds = FdTable::new();
554 fds.insert_new(Stdin::new());
555 if mute_stdout_stderr {
556 assert_eq!(fds.insert_new(NullOutput::new()), 1);
557 assert_eq!(fds.insert_new(NullOutput::new()), 2);
558 } else {
559 assert_eq!(fds.insert_new(Stdout::new()), 1);
560 assert_eq!(fds.insert_new(Stderr::new()), 2);
561 }
562 fds
563 }
564
565 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
566 let file_handle =
567 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
568 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
569 file_handle
570 }
571
572 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
574 let fd_ref = self.new_ref(fd);
575 self.insert(fd_ref)
576 }
577
578 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
580 self.insert_with_min_num(fd_ref, 0)
581 }
582
583 pub fn insert_with_min_num(
585 &mut self,
586 file_handle: DynFileDescriptionRef,
587 min_fd_num: FdNum,
588 ) -> FdNum {
589 let mut candidate = min_fd_num;
590 for (&fd_num, _) in self.fds.range(min_fd_num..) {
591 if fd_num == candidate {
592 candidate = candidate.strict_add(1);
594 } else {
595 break;
597 }
598 }
599 self.fds.try_insert(candidate, file_handle).unwrap();
603 candidate
604 }
605
606 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
607 let fd = self.fds.get(&fd_num)?;
608 Some(fd.clone())
609 }
610
611 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
612 self.fds.remove(&fd_num)
613 }
614
615 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
616 self.fds.contains_key(&fd_num)
617 }
618}
619
620impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
621pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
622 fn read_from_host(
625 &mut self,
626 mut read_cb: impl FnMut(&mut [u8]) -> io::Result<usize>,
627 len: usize,
628 ptr: Pointer,
629 ) -> InterpResult<'tcx, Result<usize, IoError>> {
630 let this = self.eval_context_mut();
631
632 let mut bytes = vec![0; len];
633 let result = read_cb(&mut bytes);
634 match result {
635 Ok(read_size) => {
636 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
640 interp_ok(Ok(read_size))
641 }
642 Err(e) => interp_ok(Err(IoError::HostError(e))),
643 }
644 }
645
646 fn write_to_host(
648 &mut self,
649 mut file: impl io::Write,
650 len: usize,
651 ptr: Pointer,
652 ) -> InterpResult<'tcx, Result<usize, IoError>> {
653 let this = self.eval_context_mut();
654
655 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
656 let result = file.write(bytes);
657 interp_ok(result.map_err(IoError::HostError))
658 }
659}