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(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
201 panic!("Not a unix file description: {}", self.name());
202 }
203
204 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
206 None
207 }
208
209 fn readiness(&self) -> Readiness {
211 panic!("FD type {} implements `readiness_watched` but not `readiness`", self.name());
212 }
213}
214
215#[derive(Debug)]
216pub struct Stdin {
217 stdin: io::Stdin,
218 watched: ReadinessWatched,
219}
220
221impl Stdin {
222 fn new() -> Self {
223 Self { stdin: io::stdin(), watched: ReadinessWatched::default() }
224 }
225}
226
227impl FileDescription for Stdin {
228 fn name(&self) -> &'static str {
229 "stdin"
230 }
231
232 fn read<'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 if !communicate_allowed {
241 helpers::isolation_abort_error("`read` from stdin")?;
243 }
244
245 let result = ecx.read_from_host(|buf| (&mut &self.stdin).read(buf), len, ptr)?;
247 finish.call(ecx, result)
248 }
249
250 fn is_tty(&self, communicate_allowed: bool) -> bool {
251 communicate_allowed && self.stdin.is_terminal()
252 }
253
254 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
255 Some(&self.watched)
256 }
257
258 fn readiness(&self) -> Readiness {
259 let mut readiness = Readiness::EMPTY;
262 readiness.readable = true;
263 readiness.writable = true;
264 readiness
265 }
266
267 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
268 self
269 }
270}
271
272#[derive(Debug)]
273pub struct Stdout {
274 stdout: io::Stdout,
275 watched: ReadinessWatched,
276}
277
278impl Stdout {
279 fn new() -> Self {
280 Self { stdout: io::stdout(), watched: ReadinessWatched::default() }
281 }
282}
283
284impl FileDescription for Stdout {
285 fn name(&self) -> &'static str {
286 "stdout"
287 }
288
289 fn write<'tcx>(
290 self: FileDescriptionRef<Self>,
291 _communicate_allowed: bool,
292 ptr: Pointer,
293 len: usize,
294 ecx: &mut MiriInterpCx<'tcx>,
295 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
296 ) -> InterpResult<'tcx> {
297 let result = ecx.write_to_host(&self.stdout, len, ptr)?;
299 io::stdout().flush().unwrap();
305
306 finish.call(ecx, result)
307 }
308
309 fn is_tty(&self, communicate_allowed: bool) -> bool {
310 communicate_allowed && self.stdout.is_terminal()
311 }
312
313 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
314 Some(&self.watched)
315 }
316
317 fn readiness(&self) -> Readiness {
318 let mut readiness = Readiness::EMPTY;
320 readiness.writable = true;
321 readiness
322 }
323
324 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
325 self
326 }
327}
328
329#[derive(Debug)]
330pub struct Stderr {
331 stderr: io::Stderr,
332 watched: ReadinessWatched,
333}
334
335impl Stderr {
336 fn new() -> Self {
337 Self { stderr: io::stderr(), watched: ReadinessWatched::default() }
338 }
339}
340
341impl FileDescription for Stderr {
342 fn name(&self) -> &'static str {
343 "stderr"
344 }
345
346 fn write<'tcx>(
347 self: FileDescriptionRef<Self>,
348 _communicate_allowed: bool,
349 ptr: Pointer,
350 len: usize,
351 ecx: &mut MiriInterpCx<'tcx>,
352 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
353 ) -> InterpResult<'tcx> {
354 let result = ecx.write_to_host(&self.stderr, len, ptr)?;
356 finish.call(ecx, result)
358 }
359
360 fn is_tty(&self, communicate_allowed: bool) -> bool {
361 communicate_allowed && self.stderr.is_terminal()
362 }
363
364 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
365 Some(&self.watched)
366 }
367
368 fn readiness(&self) -> Readiness {
369 let mut readiness = Readiness::EMPTY;
371 readiness.writable = true;
372 readiness
373 }
374
375 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
376 self
377 }
378}
379
380#[derive(Debug)]
382pub struct NullOutput {
383 watched: ReadinessWatched,
384}
385
386impl NullOutput {
387 fn new() -> Self {
388 Self { watched: ReadinessWatched::default() }
389 }
390}
391
392impl FileDescription for NullOutput {
393 fn name(&self) -> &'static str {
394 "null output"
395 }
396
397 fn write<'tcx>(
398 self: FileDescriptionRef<Self>,
399 _communicate_allowed: bool,
400 _ptr: Pointer,
401 len: usize,
402 ecx: &mut MiriInterpCx<'tcx>,
403 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
404 ) -> InterpResult<'tcx> {
405 finish.call(ecx, Ok(len))
407 }
408
409 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
410 Some(&self.watched)
411 }
412
413 fn readiness(&self) -> Readiness {
414 let mut readiness = Readiness::EMPTY;
416 readiness.writable = true;
417 readiness
418 }
419
420 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
421 self
422 }
423}
424
425#[derive(Debug)]
426pub struct FileHandle {
427 pub(crate) file: File,
428 pub(crate) readable: bool,
429 pub(crate) writable: bool,
430}
431
432impl FileDescription for FileHandle {
433 fn name(&self) -> &'static str {
434 "file"
435 }
436
437 fn read<'tcx>(
438 self: FileDescriptionRef<Self>,
439 communicate_allowed: bool,
440 ptr: Pointer,
441 len: usize,
442 ecx: &mut MiriInterpCx<'tcx>,
443 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
444 ) -> InterpResult<'tcx> {
445 assert!(communicate_allowed, "isolation should have prevented even opening a file");
446
447 if !self.readable {
448 let err = if ecx.target_os_is_unix() {
450 LibcError("EBADF")
451 } else {
452 ErrorKind::PermissionDenied.into()
453 };
454 return finish.call(ecx, Err(err));
455 }
456
457 let mut file = &self.file;
458 let result = ecx.read_from_host(|buf| file.read(buf), len, ptr)?;
459 finish.call(ecx, result)
460 }
461
462 fn write<'tcx>(
463 self: FileDescriptionRef<Self>,
464 communicate_allowed: bool,
465 ptr: Pointer,
466 len: usize,
467 ecx: &mut MiriInterpCx<'tcx>,
468 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
469 ) -> InterpResult<'tcx> {
470 assert!(communicate_allowed, "isolation should have prevented even opening a file");
471
472 if !self.writable {
473 let err = if ecx.target_os_is_unix() {
475 LibcError("EBADF")
476 } else {
477 ErrorKind::PermissionDenied.into()
478 };
479 return finish.call(ecx, Err(err));
480 }
481 let result = ecx.write_to_host(&self.file, len, ptr)?;
482 finish.call(ecx, result)
483 }
484
485 fn seek<'tcx>(
486 &self,
487 communicate_allowed: bool,
488 offset: SeekFrom,
489 ) -> InterpResult<'tcx, io::Result<u64>> {
490 assert!(communicate_allowed, "isolation should have prevented even opening a file");
491 interp_ok((&mut &self.file).seek(offset))
492 }
493
494 fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
495 interp_ok(Either::Left(self.file.metadata()))
496 }
497
498 fn is_tty(&self, communicate_allowed: bool) -> bool {
499 communicate_allowed && self.file.is_terminal()
500 }
501
502 fn short_fd_operations(&self) -> bool {
503 true
507 }
508
509 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
510 self
511 }
512}
513
514#[derive(Debug)]
515pub struct DirHandle {
516 pub(super) dir: Dir,
517 #[cfg(bootstrap)]
518 pub(super) fallback: std::path::PathBuf,
519}
520
521impl DirHandle {
522 pub fn new(dir: Dir, path: &std::path::Path) -> Self {
523 cfg_select! {
524 bootstrap => {
525 let fallback =
527 path.canonicalize().expect("canonicalizing directory fallback should succeed");
528 DirHandle { dir, fallback }
529 }
530 _ => {
531 let _unused = path;
532 DirHandle { dir }
533 }
534 }
535 }
536}
537
538impl FileDescription for DirHandle {
539 fn name(&self) -> &'static str {
540 "directory"
541 }
542
543 fn metadata<'tcx>(
544 &self,
545 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
546 #[cfg(bootstrap)]
547 return interp_ok(Either::Left(self.dir.metadata()));
548 #[cfg(not(bootstrap))]
549 return interp_ok(Either::Left(self.dir.self_metadata()));
550 }
551
552 fn read<'tcx>(
553 self: FileDescriptionRef<Self>,
554 _communicate_allowed: bool,
555 _ptr: Pointer,
556 _len: usize,
557 ecx: &mut MiriInterpCx<'tcx>,
558 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
559 ) -> InterpResult<'tcx> {
560 if ecx.target_os_is_unix() {
561 finish.call(ecx, Err(LibcError("EISDIR")))
562 } else {
563 throw_unsup_format!("reading directories is not supported on this target");
565 }
566 }
567
568 fn write<'tcx>(
569 self: FileDescriptionRef<Self>,
570 _communicate_allowed: bool,
571 _ptr: Pointer,
572 _len: usize,
573 ecx: &mut MiriInterpCx<'tcx>,
574 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
575 ) -> InterpResult<'tcx> {
576 if ecx.target_os_is_unix() {
577 finish.call(ecx, Err(LibcError("EBADF")))
579 } else {
580 throw_unsup_format!("writing directories is not supported on this target");
582 }
583 }
584
585 fn as_unix(self: FileDescriptionRef<Self>) -> FileDescriptionRef<dyn UnixFileDescription> {
586 self
587 }
588}
589
590pub type FdNum = i32;
592
593#[derive(Debug)]
595pub struct FdTable {
596 fds: BTreeMap<FdNum, DynFileDescriptionRef>,
597 next_file_description_id: FdId,
599}
600
601impl VisitProvenance for FdTable {
602 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
603 }
605}
606
607impl FdTable {
608 fn new() -> Self {
609 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
610 }
611 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
612 let mut fds = FdTable::new();
613 fds.insert_new(Stdin::new());
614 if mute_stdout_stderr {
615 assert_eq!(fds.insert_new(NullOutput::new()), 1);
616 assert_eq!(fds.insert_new(NullOutput::new()), 2);
617 } else {
618 assert_eq!(fds.insert_new(Stdout::new()), 1);
619 assert_eq!(fds.insert_new(Stderr::new()), 2);
620 }
621 fds
622 }
623
624 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
625 let file_handle =
626 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
627 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
628 file_handle
629 }
630
631 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
633 let fd_ref = self.new_ref(fd);
634 self.insert(fd_ref)
635 }
636
637 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
639 self.insert_with_min_num(fd_ref, 0)
640 }
641
642 pub fn insert_with_min_num(
644 &mut self,
645 file_handle: DynFileDescriptionRef,
646 min_fd_num: FdNum,
647 ) -> FdNum {
648 let mut candidate = min_fd_num;
649 for (&fd_num, _) in self.fds.range(min_fd_num..) {
650 if fd_num == candidate {
651 candidate = candidate.strict_add(1);
653 } else {
654 break;
656 }
657 }
658 self.fds.try_insert(candidate, file_handle).unwrap();
662 candidate
663 }
664
665 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
666 let fd = self.fds.get(&fd_num)?;
667 Some(fd.clone())
668 }
669
670 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
671 self.fds.remove(&fd_num)
672 }
673
674 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
675 self.fds.contains_key(&fd_num)
676 }
677}
678
679impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
680pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
681 fn read_from_host(
684 &mut self,
685 mut read_cb: impl FnMut(&mut [u8]) -> io::Result<usize>,
686 len: usize,
687 ptr: Pointer,
688 ) -> InterpResult<'tcx, Result<usize, IoError>> {
689 let this = self.eval_context_mut();
690
691 let mut bytes = vec![0; len];
692 let result = read_cb(&mut bytes);
693 match result {
694 Ok(read_size) => {
695 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
699 interp_ok(Ok(read_size))
700 }
701 Err(e) => interp_ok(Err(IoError::HostError(e))),
702 }
703 }
704
705 fn write_to_host(
707 &mut self,
708 mut file: impl io::Write,
709 len: usize,
710 ptr: Pointer,
711 ) -> InterpResult<'tcx, Result<usize, IoError>> {
712 let this = self.eval_context_mut();
713
714 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
715 let result = file.write(bytes);
716 interp_ok(result.map_err(IoError::HostError))
717 }
718}
719
720pub fn open_file_or_dir(
724 path: &std::path::Path,
725 mut opts: fs::OpenOptions,
726 #[cfg(unix)] custom_flags: i32,
727 #[cfg(windows)] custom_flags: u32,
728) -> io::Result<Either<fs::File, fs::Dir>> {
729 #[cfg(unix)]
730 use std::os::unix::fs::OpenOptionsExt;
731 #[cfg(windows)]
732 use std::os::windows::fs::OpenOptionsExt;
733
734 let file = match opts.custom_flags(custom_flags).open(path) {
738 Ok(file) => file,
739
740 #[cfg(windows)]
741 Err(err) if err.kind() == io::ErrorKind::PermissionDenied => {
742 opts.custom_flags(
745 custom_flags | windows_sys::Win32::Storage::FileSystem::FILE_FLAG_BACKUP_SEMANTICS,
746 )
747 .open(path)?
748 }
749
750 Err(err) => return Err(err),
751 };
752
753 let metadata = file.metadata().expect("just-opened file should have metadata");
754 if metadata.is_dir() {
755 assert!(!metadata.is_symlink()); cfg_select! {
758 unix => {
759 use std::os::fd::OwnedFd;
760 Ok(Either::Right(OwnedFd::from(file).into()))
761 }
762 windows => {
763 use std::os::windows::io::OwnedHandle;
764 Ok(Either::Right(OwnedHandle::from(file).into()))
765 }
766 }
767 } else {
768 Ok(Either::Left(file))
769 }
770}