1use std::any::Any;
2use std::collections::BTreeMap;
3use std::convert::BoundedCastFromInt;
4use std::fs::{Dir, File};
5use std::io::{ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
6use std::marker::CoercePointee;
7use std::ops::Deref;
8use std::rc::{Rc, Weak};
9use std::{fs, io};
10
11use rustc_abi::Size;
12
13use crate::shims::unix::UnixFileDescription;
14use crate::*;
15
16pub type FdNum = i32;
18
19pub const FD_LIMIT: FdNum = 524288; pub const BIG_ACCESS: u64 = 8 * 1024;
25
26#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
31pub struct FdId(usize);
32
33impl FdId {
34 pub fn to_usize(self) -> usize {
35 self.0
36 }
37
38 pub fn new_unchecked(id: usize) -> Self {
40 Self(id)
41 }
42}
43
44#[derive(Debug, Clone)]
45struct FdIdWith<T: ?Sized> {
46 id: FdId,
47 inner: T,
48}
49
50#[repr(transparent)]
53#[derive(CoercePointee, Debug)]
54pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
56
57impl<T: ?Sized> Clone for FileDescriptionRef<T> {
58 fn clone(&self) -> Self {
59 FileDescriptionRef(self.0.clone())
60 }
61}
62
63impl<T: ?Sized> Deref for FileDescriptionRef<T> {
64 type Target = T;
65 fn deref(&self) -> &T {
66 &self.0.inner
67 }
68}
69
70impl<T: ?Sized> FileDescriptionRef<T> {
71 pub fn id(&self) -> FdId {
72 self.0.id
73 }
74}
75
76impl<T: ?Sized> PartialEq for FileDescriptionRef<T> {
77 fn eq(&self, other: &Self) -> bool {
78 self.0.id == other.0.id
79 }
80}
81
82impl<T: ?Sized> Eq for FileDescriptionRef<T> {}
83
84#[derive(Debug)]
86pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
87
88impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
89 fn clone(&self) -> Self {
90 WeakFileDescriptionRef(self.0.clone())
91 }
92}
93
94impl<T: ?Sized> FileDescriptionRef<T> {
95 pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
96 WeakFileDescriptionRef(Rc::downgrade(&this.0))
97 }
98}
99
100impl<T: ?Sized> WeakFileDescriptionRef<T> {
101 pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
102 self.0.upgrade().map(FileDescriptionRef)
103 }
104
105 pub fn is_closed(&self) -> bool {
108 self.0.strong_count() == 0
109 }
110}
111
112impl<T> VisitProvenance for FileDescriptionRef<T> {
113 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
114 }
116}
117
118pub trait FileDescriptionExt: 'static {
122 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
123}
124
125impl<T: FileDescription + 'static> FileDescriptionExt for T {
126 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
127 self.0
128 }
129}
130
131pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
132pub type WeakDynFileDescriptionRef = WeakFileDescriptionRef<dyn FileDescription>;
133
134impl FileDescriptionRef<dyn FileDescription> {
135 pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
136 let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
137 Some(FileDescriptionRef(inner))
138 }
139}
140
141pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
143 fn name(&self) -> &'static str;
144
145 fn read<'tcx>(
152 self: FileDescriptionRef<Self>,
153 _communicate_allowed: bool,
154 _ptr: Pointer,
155 _len: u64,
156 _ecx: &mut MiriInterpCx<'tcx>,
157 _finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
158 ) -> InterpResult<'tcx> {
159 throw_unsup_format!("cannot read from {}", self.name());
160 }
161
162 fn write<'tcx>(
169 self: FileDescriptionRef<Self>,
170 _communicate_allowed: bool,
171 _ptr: Pointer,
172 _len: u64,
173 _ecx: &mut MiriInterpCx<'tcx>,
174 _finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
175 ) -> InterpResult<'tcx> {
176 throw_unsup_format!("cannot write to {}", self.name());
177 }
178
179 fn short_fd_operations(&self) -> bool {
181 false
183 }
184
185 fn seek<'tcx>(
188 &self,
189 _communicate_allowed: bool,
190 _offset: SeekFrom,
191 ) -> InterpResult<'tcx, io::Result<u64>> {
192 throw_unsup_format!("cannot seek on {}", self.name());
193 }
194
195 fn metadata<'tcx>(
200 &self,
201 _communicate_allowed: bool,
202 ) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
203 throw_unsup_format!("obtaining metadata is not supported on {}", self.name());
204 }
205
206 fn is_tty(&self, _communicate_allowed: bool) -> bool {
207 false
210 }
211
212 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
215 panic!("Not a unix file description: {}", self.name());
216 }
217
218 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
220 None
221 }
222
223 fn readiness(&self) -> Readiness {
225 panic!("FD type {} implements `readiness_watched` but not `readiness`", self.name());
226 }
227}
228
229#[derive(Debug)]
230pub struct Stdin {
231 stdin: io::Stdin,
232 watched: ReadinessWatched,
233}
234
235impl Stdin {
236 fn new() -> Self {
237 Self { stdin: io::stdin(), watched: ReadinessWatched::default() }
238 }
239}
240
241impl FileDescription for Stdin {
242 fn name(&self) -> &'static str {
243 "stdin"
244 }
245
246 fn read<'tcx>(
247 self: FileDescriptionRef<Self>,
248 communicate_allowed: bool,
249 ptr: Pointer,
250 len: u64,
251 ecx: &mut MiriInterpCx<'tcx>,
252 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
253 ) -> InterpResult<'tcx> {
254 if !communicate_allowed {
255 helpers::isolation_abort_error("`read` from stdin")?;
257 }
258
259 let result = ecx.read_from_host(ptr, len, |buf| (&mut &self.stdin).read(buf))?;
261 finish.call(ecx, result)
262 }
263
264 fn metadata<'tcx>(
265 &self,
266 communicate_allowed: bool,
267 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
268 let ftype = if self.is_tty(communicate_allowed) { "S_IFCHR" } else { "S_IFIFO" };
269 interp_ok(Either::Right(ftype))
270 }
271
272 fn is_tty(&self, communicate_allowed: bool) -> bool {
273 !communicate_allowed || self.stdin.is_terminal()
275 }
276
277 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
278 Some(&self.watched)
279 }
280
281 fn readiness(&self) -> Readiness {
282 let mut readiness = Readiness::EMPTY;
285 readiness.readable = true;
286 readiness.writable = true;
287 readiness
288 }
289
290 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
291 self
292 }
293}
294
295#[derive(Debug)]
296pub struct Stdout {
297 stdout: io::Stdout,
298 watched: ReadinessWatched,
299}
300
301impl Stdout {
302 fn new() -> Self {
303 Self { stdout: io::stdout(), watched: ReadinessWatched::default() }
304 }
305}
306
307impl FileDescription for Stdout {
308 fn name(&self) -> &'static str {
309 "stdout"
310 }
311
312 fn write<'tcx>(
313 self: FileDescriptionRef<Self>,
314 _communicate_allowed: bool,
315 ptr: Pointer,
316 len: u64,
317 ecx: &mut MiriInterpCx<'tcx>,
318 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
319 ) -> InterpResult<'tcx> {
320 let result = ecx.write_to_host(ptr, len, |buf| (&self.stdout).write(buf))?;
322 io::stdout().flush().unwrap();
328
329 finish.call(ecx, result)
330 }
331
332 fn metadata<'tcx>(
333 &self,
334 communicate_allowed: bool,
335 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
336 let ftype = if self.is_tty(communicate_allowed) { "S_IFCHR" } else { "S_IFIFO" };
337 interp_ok(Either::Right(ftype))
338 }
339
340 fn is_tty(&self, communicate_allowed: bool) -> bool {
341 !communicate_allowed || self.stdout.is_terminal()
343 }
344
345 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
346 Some(&self.watched)
347 }
348
349 fn readiness(&self) -> Readiness {
350 let mut readiness = Readiness::EMPTY;
352 readiness.writable = true;
353 readiness
354 }
355
356 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
357 self
358 }
359}
360
361#[derive(Debug)]
362pub struct Stderr {
363 stderr: io::Stderr,
364 watched: ReadinessWatched,
365}
366
367impl Stderr {
368 fn new() -> Self {
369 Self { stderr: io::stderr(), watched: ReadinessWatched::default() }
370 }
371}
372
373impl FileDescription for Stderr {
374 fn name(&self) -> &'static str {
375 "stderr"
376 }
377
378 fn write<'tcx>(
379 self: FileDescriptionRef<Self>,
380 _communicate_allowed: bool,
381 ptr: Pointer,
382 len: u64,
383 ecx: &mut MiriInterpCx<'tcx>,
384 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
385 ) -> InterpResult<'tcx> {
386 let result = ecx.write_to_host(ptr, len, |buf| (&self.stderr).write(buf))?;
388 finish.call(ecx, result)
390 }
391
392 fn metadata<'tcx>(
393 &self,
394 communicate_allowed: bool,
395 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
396 let ftype = if self.is_tty(communicate_allowed) { "S_IFCHR" } else { "S_IFIFO" };
397 interp_ok(Either::Right(ftype))
398 }
399
400 fn is_tty(&self, communicate_allowed: bool) -> bool {
401 !communicate_allowed || self.stderr.is_terminal()
403 }
404
405 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
406 Some(&self.watched)
407 }
408
409 fn readiness(&self) -> Readiness {
410 let mut readiness = Readiness::EMPTY;
412 readiness.writable = true;
413 readiness
414 }
415
416 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
417 self
418 }
419}
420
421#[derive(Debug)]
423pub struct NullOutput {
424 watched: ReadinessWatched,
425}
426
427impl NullOutput {
428 fn new() -> Self {
429 Self { watched: ReadinessWatched::default() }
430 }
431}
432
433impl FileDescription for NullOutput {
434 fn name(&self) -> &'static str {
435 "null output"
436 }
437
438 fn write<'tcx>(
439 self: FileDescriptionRef<Self>,
440 _communicate_allowed: bool,
441 _ptr: Pointer,
442 len: u64,
443 ecx: &mut MiriInterpCx<'tcx>,
444 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
445 ) -> InterpResult<'tcx> {
446 finish.call(ecx, Ok(len))
448 }
449
450 fn metadata<'tcx>(
451 &self,
452 _communicate_allowed: bool,
453 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
454 interp_ok(Either::Right("S_IFIFO"))
455 }
456
457 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
458 Some(&self.watched)
459 }
460
461 fn readiness(&self) -> Readiness {
462 let mut readiness = Readiness::EMPTY;
464 readiness.writable = true;
465 readiness
466 }
467
468 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
469 self
470 }
471}
472
473#[derive(Debug)]
474pub struct FileHandle {
475 pub(crate) file: File,
476 pub(crate) readable: bool,
477 pub(crate) writable: bool,
478 pub(crate) append: bool,
479}
480
481impl FileDescription for FileHandle {
482 fn name(&self) -> &'static str {
483 "file"
484 }
485
486 fn read<'tcx>(
487 self: FileDescriptionRef<Self>,
488 communicate_allowed: bool,
489 ptr: Pointer,
490 len: u64,
491 ecx: &mut MiriInterpCx<'tcx>,
492 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
493 ) -> InterpResult<'tcx> {
494 assert!(communicate_allowed, "isolation should have prevented even opening a file");
495
496 if !self.readable {
497 let err = if ecx.target_os_is_unix() {
499 LibcError("EBADF")
500 } else {
501 ErrorKind::PermissionDenied.into()
502 };
503 return finish.call(ecx, Err(err));
504 }
505
506 let mut file = &self.file;
507 let result = ecx.read_from_host(ptr, len, |buf| file.read(buf))?;
508 finish.call(ecx, result)
509 }
510
511 fn write<'tcx>(
512 self: FileDescriptionRef<Self>,
513 communicate_allowed: bool,
514 ptr: Pointer,
515 len: u64,
516 ecx: &mut MiriInterpCx<'tcx>,
517 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
518 ) -> InterpResult<'tcx> {
519 assert!(communicate_allowed, "isolation should have prevented even opening a file");
520
521 if !self.writable {
522 let err = if ecx.target_os_is_unix() {
524 LibcError("EBADF")
525 } else {
526 ErrorKind::PermissionDenied.into()
527 };
528 return finish.call(ecx, Err(err));
529 }
530 let result = ecx.write_to_host(ptr, len, |buf| (&self.file).write(buf))?;
531 finish.call(ecx, result)
532 }
533
534 fn seek<'tcx>(
535 &self,
536 communicate_allowed: bool,
537 offset: SeekFrom,
538 ) -> InterpResult<'tcx, io::Result<u64>> {
539 assert!(communicate_allowed, "isolation should have prevented even opening a file");
540 interp_ok((&mut &self.file).seek(offset))
541 }
542
543 fn metadata<'tcx>(
544 &self,
545 communicate_allowed: bool,
546 ) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
547 assert!(communicate_allowed);
548 interp_ok(Either::Left(self.file.metadata()))
549 }
550
551 fn is_tty(&self, communicate_allowed: bool) -> bool {
552 assert!(communicate_allowed);
553 self.file.is_terminal()
554 }
555
556 fn short_fd_operations(&self) -> bool {
557 true
561 }
562
563 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
564 self
565 }
566}
567
568#[derive(Debug)]
569pub struct DirHandle {
570 pub(super) dir: Dir,
571 #[cfg(bootstrap)]
572 pub(super) fallback: std::path::PathBuf,
573}
574
575impl FileDescription for DirHandle {
576 fn name(&self) -> &'static str {
577 "directory"
578 }
579
580 fn metadata<'tcx>(
581 &self,
582 communicate_allowed: bool,
583 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
584 assert!(communicate_allowed);
585
586 #[cfg(bootstrap)]
587 return interp_ok(Either::Left(self.dir.metadata()));
588 #[cfg(not(bootstrap))]
589 return interp_ok(Either::Left(self.dir.self_metadata()));
590 }
591
592 fn read<'tcx>(
593 self: FileDescriptionRef<Self>,
594 _communicate_allowed: bool,
595 _ptr: Pointer,
596 _len: u64,
597 ecx: &mut MiriInterpCx<'tcx>,
598 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
599 ) -> InterpResult<'tcx> {
600 if ecx.target_os_is_unix() {
601 finish.call(ecx, Err(LibcError("EISDIR")))
602 } else {
603 throw_unsup_format!("reading directories is not supported on this target");
605 }
606 }
607
608 fn write<'tcx>(
609 self: FileDescriptionRef<Self>,
610 _communicate_allowed: bool,
611 _ptr: Pointer,
612 _len: u64,
613 ecx: &mut MiriInterpCx<'tcx>,
614 finish: DynMachineCallback<'tcx, Result<u64, IoError>>,
615 ) -> InterpResult<'tcx> {
616 if ecx.target_os_is_unix() {
617 finish.call(ecx, Err(LibcError("EBADF")))
619 } else {
620 throw_unsup_format!("writing directories is not supported on this target");
622 }
623 }
624
625 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
626 self
627 }
628}
629
630#[derive(Debug)]
632pub struct FdTable {
633 fds: BTreeMap<FdNum, DynFileDescriptionRef>,
634 next_file_description_id: FdId,
636}
637
638impl VisitProvenance for FdTable {
639 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
640 }
642}
643
644impl FdTable {
645 fn new() -> Self {
646 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
647 }
648 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
649 let mut fds = FdTable::new();
650 fds.insert_new(Stdin::new());
651 if mute_stdout_stderr {
652 assert_eq!(fds.insert_new(NullOutput::new()).unwrap(), 1);
653 assert_eq!(fds.insert_new(NullOutput::new()).unwrap(), 2);
654 } else {
655 assert_eq!(fds.insert_new(Stdout::new()).unwrap(), 1);
656 assert_eq!(fds.insert_new(Stderr::new()).unwrap(), 2);
657 }
658 fds
659 }
660
661 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
662 let file_handle =
663 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
664 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
665 file_handle
666 }
667
668 pub fn fd_num_valid(fd_num: FdNum) -> bool {
669 (0..FD_LIMIT).contains(&fd_num)
670 }
671
672 pub fn insert_new(&mut self, fd: impl FileDescription) -> Option<FdNum> {
674 let fd_ref = self.new_ref(fd);
675 self.insert(fd_ref)
676 }
677
678 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> Option<FdNum> {
680 self.insert_with_min_num(fd_ref, 0)
681 }
682
683 pub fn insert_with_min_num(
685 &mut self,
686 file_handle: DynFileDescriptionRef,
687 min_fd_num: FdNum,
688 ) -> Option<FdNum> {
689 let mut candidate = min_fd_num;
690 for (&fd_num, _) in self.fds.range(min_fd_num..) {
691 if fd_num == candidate {
692 candidate = candidate.strict_add(1);
694 } else {
695 break;
697 }
698 }
699 if !Self::fd_num_valid(candidate) {
704 return None;
705 }
706
707 self.fds.try_insert(candidate, file_handle).unwrap();
708 Some(candidate)
709 }
710
711 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
712 let fd = self.fds.get(&fd_num)?;
713 Some(fd.clone())
714 }
715
716 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
717 self.fds.remove(&fd_num)
718 }
719
720 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
721 self.fds.contains_key(&fd_num)
722 }
723}
724
725impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
726pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
727 fn read_from_host(
730 &mut self,
731 ptr: Pointer,
732 len: u64,
733 read_cb: impl FnOnce(&mut [u8]) -> io::Result<usize>,
734 ) -> InterpResult<'tcx, Result<u64, IoError>> {
735 let ecx = self.eval_context_mut();
736
737 let len = usize::saturating_cast_from(len);
738 let mut bytes = vec![0; len];
739 let result = read_cb(&mut bytes);
740 match result {
741 Ok(read_size) => {
742 ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
746 interp_ok(Ok(read_size.to_u64()))
747 }
748 Err(e) => interp_ok(Err(IoError::HostError(e))),
749 }
750 }
751
752 fn write_to_host(
754 &self,
755 ptr: Pointer,
756 len: u64,
757 write_cb: impl FnOnce(&[u8]) -> io::Result<usize>,
758 ) -> InterpResult<'tcx, Result<u64, IoError>> {
759 let ecx = self.eval_context_ref();
760
761 let len = usize::saturating_cast_from(len);
762 let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
763 let result = write_cb(bytes);
764 interp_ok(result.map_err(IoError::HostError).map(|written_size| written_size.to_u64()))
765 }
766}
767
768pub fn open_file_or_dir(
772 root: Option<&fs::Dir>,
773 path: &std::path::Path,
774 opts: &fs::OpenOptions,
775) -> io::Result<Either<fs::File, fs::Dir>> {
776 let file = match root {
780 Some(root) => root.open_file_with(path, opts),
781 None => opts.open(path),
782 };
783 #[cfg(windows)]
784 let file = file.or_else(|err| {
785 use std::os::windows::io::OwnedHandle;
786
787 if err.kind() == io::ErrorKind::PermissionDenied {
788 let dir = match root {
791 Some(root) => root.open_dir_with(path, opts),
792 None => Dir::open_with(path, opts),
793 };
794 match dir {
795 Ok(dir) => Ok(File::from(OwnedHandle::from(dir))),
797 Err(err2) => {
798 Err(if err2.kind() == io::ErrorKind::NotADirectory { err } else { err2 })
803 }
804 }
805 } else {
806 Err(err)
807 }
808 });
809 let file = file?;
810
811 let metadata = file.metadata().expect("just-opened file/dir should have metadata");
812 if metadata.is_dir() {
813 assert!(!metadata.is_symlink()); cfg_select! {
816 unix => {
817 use std::os::fd::OwnedFd;
818 Ok(Either::Right(OwnedFd::from(file).into()))
819 }
820 windows => {
821 use std::os::windows::io::OwnedHandle;
822 Ok(Either::Right(OwnedHandle::from(file).into()))
823 }
824 }
825 } else {
826 Ok(Either::Left(file))
827 }
828}