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::path::PathBuf;
8use std::rc::{Rc, Weak};
9use std::{fs, io};
10
11use rustc_abi::Size;
12
13use crate::shims::unix::UnixFileDescription;
14use crate::*;
15
16#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
21pub struct FdId(usize);
22
23impl FdId {
24 pub fn to_usize(self) -> usize {
25 self.0
26 }
27
28 pub fn new_unchecked(id: usize) -> Self {
30 Self(id)
31 }
32}
33
34#[derive(Debug, Clone)]
35struct FdIdWith<T: ?Sized> {
36 id: FdId,
37 inner: T,
38}
39
40#[repr(transparent)]
43#[derive(CoercePointee, Debug)]
44pub 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
95impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
96 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
97 }
101}
102
103pub trait FileDescriptionExt: 'static {
107 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
108
109 fn close_ref<'tcx>(
112 self: FileDescriptionRef<Self>,
113 communicate_allowed: bool,
114 ecx: &mut MiriInterpCx<'tcx>,
115 ) -> InterpResult<'tcx, io::Result<()>>;
116}
117
118impl<T: FileDescription + 'static> FileDescriptionExt for T {
119 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
120 self.0
121 }
122
123 fn close_ref<'tcx>(
124 self: FileDescriptionRef<Self>,
125 communicate_allowed: bool,
126 ecx: &mut MiriInterpCx<'tcx>,
127 ) -> InterpResult<'tcx, io::Result<()>> {
128 match Rc::into_inner(self.0) {
129 Some(fd) => {
130 ecx.machine.readiness_interests.remove_watchers_for_fd(fd.id);
132
133 fd.inner.destroy(fd.id, communicate_allowed, ecx)
134 }
135 None => {
136 interp_ok(Ok(()))
138 }
139 }
140 }
141}
142
143pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
144
145impl FileDescriptionRef<dyn FileDescription> {
146 pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
147 let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
148 Some(FileDescriptionRef(inner))
149 }
150}
151
152pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
154 fn name(&self) -> &'static str;
155
156 fn read<'tcx>(
163 self: FileDescriptionRef<Self>,
164 _communicate_allowed: bool,
165 _ptr: Pointer,
166 _len: usize,
167 _ecx: &mut MiriInterpCx<'tcx>,
168 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
169 ) -> InterpResult<'tcx> {
170 throw_unsup_format!("cannot read from {}", self.name());
171 }
172
173 fn write<'tcx>(
180 self: FileDescriptionRef<Self>,
181 _communicate_allowed: bool,
182 _ptr: Pointer,
183 _len: usize,
184 _ecx: &mut MiriInterpCx<'tcx>,
185 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
186 ) -> InterpResult<'tcx> {
187 throw_unsup_format!("cannot write to {}", self.name());
188 }
189
190 fn short_fd_operations(&self) -> bool {
192 false
194 }
195
196 fn seek<'tcx>(
199 &self,
200 _communicate_allowed: bool,
201 _offset: SeekFrom,
202 ) -> InterpResult<'tcx, io::Result<u64>> {
203 throw_unsup_format!("cannot seek on {}", self.name());
204 }
205
206 fn destroy<'tcx>(
210 self,
211 _self_id: FdId,
212 _communicate_allowed: bool,
213 _ecx: &mut MiriInterpCx<'tcx>,
214 ) -> InterpResult<'tcx, io::Result<()>>
215 where
216 Self: Sized,
217 {
218 throw_unsup_format!("cannot close {}", self.name());
219 }
220
221 fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
226 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
227 }
228
229 fn is_tty(&self, _communicate_allowed: bool) -> bool {
230 false
233 }
234
235 fn as_unix<'tcx>(
236 self: FileDescriptionRef<Self>,
237 _ecx: &MiriInterpCx<'tcx>,
238 ) -> FileDescriptionRef<dyn UnixFileDescription> {
239 panic!("Not a unix file descriptor: {}", self.name());
240 }
241
242 fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
244 throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
245 }
246
247 fn set_flags<'tcx>(
249 &self,
250 _flag: i32,
251 _ecx: &mut MiriInterpCx<'tcx>,
252 ) -> InterpResult<'tcx, Scalar> {
253 throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
254 }
255
256 fn readiness<'tcx>(&self) -> InterpResult<'tcx, Readiness> {
258 throw_unsup_format!("{}: this file description doesn't support I/O readiness", self.name());
259 }
260}
261
262impl FileDescription for io::Stdin {
263 fn name(&self) -> &'static str {
264 "stdin"
265 }
266
267 fn read<'tcx>(
268 self: FileDescriptionRef<Self>,
269 communicate_allowed: bool,
270 ptr: Pointer,
271 len: usize,
272 ecx: &mut MiriInterpCx<'tcx>,
273 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
274 ) -> InterpResult<'tcx> {
275 if !communicate_allowed {
276 helpers::isolation_abort_error("`read` from stdin")?;
278 }
279
280 let mut stdin = &*self;
281 let result = ecx.read_from_host(|buf| stdin.read(buf), len, ptr)?;
282 finish.call(ecx, result)
283 }
284
285 fn destroy<'tcx>(
286 self,
287 _self_id: FdId,
288 _communicate_allowed: bool,
289 _ecx: &mut MiriInterpCx<'tcx>,
290 ) -> InterpResult<'tcx, io::Result<()>> {
291 interp_ok(Ok(()))
292 }
293
294 fn is_tty(&self, communicate_allowed: bool) -> bool {
295 communicate_allowed && self.is_terminal()
296 }
297}
298
299impl FileDescription for io::Stdout {
300 fn name(&self) -> &'static str {
301 "stdout"
302 }
303
304 fn write<'tcx>(
305 self: FileDescriptionRef<Self>,
306 _communicate_allowed: bool,
307 ptr: Pointer,
308 len: usize,
309 ecx: &mut MiriInterpCx<'tcx>,
310 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
311 ) -> InterpResult<'tcx> {
312 let result = ecx.write_to_host(&*self, len, ptr)?;
314 io::stdout().flush().unwrap();
320
321 finish.call(ecx, result)
322 }
323
324 fn destroy<'tcx>(
325 self,
326 _self_id: FdId,
327 _communicate_allowed: bool,
328 _ecx: &mut MiriInterpCx<'tcx>,
329 ) -> InterpResult<'tcx, io::Result<()>> {
330 interp_ok(Ok(()))
331 }
332
333 fn is_tty(&self, communicate_allowed: bool) -> bool {
334 communicate_allowed && self.is_terminal()
335 }
336}
337
338impl FileDescription for io::Stderr {
339 fn name(&self) -> &'static str {
340 "stderr"
341 }
342
343 fn destroy<'tcx>(
344 self,
345 _self_id: FdId,
346 _communicate_allowed: bool,
347 _ecx: &mut MiriInterpCx<'tcx>,
348 ) -> InterpResult<'tcx, io::Result<()>> {
349 interp_ok(Ok(()))
350 }
351
352 fn write<'tcx>(
353 self: FileDescriptionRef<Self>,
354 _communicate_allowed: bool,
355 ptr: Pointer,
356 len: usize,
357 ecx: &mut MiriInterpCx<'tcx>,
358 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
359 ) -> InterpResult<'tcx> {
360 let result = ecx.write_to_host(&*self, len, ptr)?;
362 finish.call(ecx, result)
364 }
365
366 fn is_tty(&self, communicate_allowed: bool) -> bool {
367 communicate_allowed && self.is_terminal()
368 }
369}
370
371#[derive(Debug)]
372pub struct FileHandle {
373 pub(crate) file: File,
374 pub(crate) readable: bool,
375 pub(crate) writable: bool,
376}
377
378impl FileDescription for FileHandle {
379 fn name(&self) -> &'static str {
380 "file"
381 }
382
383 fn read<'tcx>(
384 self: FileDescriptionRef<Self>,
385 communicate_allowed: bool,
386 ptr: Pointer,
387 len: usize,
388 ecx: &mut MiriInterpCx<'tcx>,
389 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
390 ) -> InterpResult<'tcx> {
391 assert!(communicate_allowed, "isolation should have prevented even opening a file");
392
393 if !self.readable {
394 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
395 }
396
397 let mut file = &self.file;
398 let result = ecx.read_from_host(|buf| file.read(buf), len, ptr)?;
399 finish.call(ecx, result)
400 }
401
402 fn write<'tcx>(
403 self: FileDescriptionRef<Self>,
404 communicate_allowed: bool,
405 ptr: Pointer,
406 len: usize,
407 ecx: &mut MiriInterpCx<'tcx>,
408 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
409 ) -> InterpResult<'tcx> {
410 assert!(communicate_allowed, "isolation should have prevented even opening a file");
411
412 if !self.writable {
413 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
420 }
421 let result = ecx.write_to_host(&self.file, len, ptr)?;
422 finish.call(ecx, result)
423 }
424
425 fn seek<'tcx>(
426 &self,
427 communicate_allowed: bool,
428 offset: SeekFrom,
429 ) -> InterpResult<'tcx, io::Result<u64>> {
430 assert!(communicate_allowed, "isolation should have prevented even opening a file");
431 interp_ok((&mut &self.file).seek(offset))
432 }
433
434 fn destroy<'tcx>(
435 self,
436 _self_id: FdId,
437 communicate_allowed: bool,
438 _ecx: &mut MiriInterpCx<'tcx>,
439 ) -> InterpResult<'tcx, io::Result<()>> {
440 assert!(communicate_allowed, "isolation should have prevented even opening a file");
441 if self.writable {
443 let result = self.file.sync_all();
446 drop(self.file);
448 interp_ok(result)
449 } else {
450 drop(self.file);
457 interp_ok(Ok(()))
458 }
459 }
460
461 fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
462 interp_ok(Either::Left(self.file.metadata()))
463 }
464
465 fn is_tty(&self, communicate_allowed: bool) -> bool {
466 communicate_allowed && self.file.is_terminal()
467 }
468
469 fn short_fd_operations(&self) -> bool {
470 true
474 }
475
476 fn as_unix<'tcx>(
477 self: FileDescriptionRef<Self>,
478 ecx: &MiriInterpCx<'tcx>,
479 ) -> FileDescriptionRef<dyn UnixFileDescription> {
480 assert!(
481 ecx.target_os_is_unix(),
482 "unix file operations are only available for unix targets"
483 );
484 self
485 }
486}
487
488#[derive(Debug)]
489pub struct DirHandle {
490 #[cfg_attr(bootstrap, allow(unused))]
491 pub(crate) dir: Dir,
492 #[cfg_attr(not(bootstrap), allow(unused))]
494 pub(crate) path: PathBuf,
495}
496
497impl FileDescription for DirHandle {
498 fn name(&self) -> &'static str {
499 "directory"
500 }
501
502 fn metadata<'tcx>(
503 &self,
504 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
505 #[cfg(not(bootstrap))]
506 return interp_ok(Either::Left(self.dir.metadata()));
507 #[cfg(bootstrap)]
508 return interp_ok(Either::Left(std::fs::metadata(&self.path)));
509 }
510
511 fn destroy<'tcx>(
512 self,
513 _self_id: FdId,
514 _communicate_allowed: bool,
515 _ecx: &mut MiriInterpCx<'tcx>,
516 ) -> InterpResult<'tcx, io::Result<()>> {
517 interp_ok(Ok(()))
518 }
519}
520
521#[derive(Debug)]
523pub struct NullOutput;
524
525impl FileDescription for NullOutput {
526 fn name(&self) -> &'static str {
527 "stderr and stdout"
528 }
529
530 fn write<'tcx>(
531 self: FileDescriptionRef<Self>,
532 _communicate_allowed: bool,
533 _ptr: Pointer,
534 len: usize,
535 ecx: &mut MiriInterpCx<'tcx>,
536 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
537 ) -> InterpResult<'tcx> {
538 finish.call(ecx, Ok(len))
540 }
541
542 fn destroy<'tcx>(
543 self,
544 _self_id: FdId,
545 _communicate_allowed: bool,
546 _ecx: &mut MiriInterpCx<'tcx>,
547 ) -> InterpResult<'tcx, io::Result<()>> {
548 interp_ok(Ok(()))
549 }
550}
551
552pub type FdNum = i32;
554
555#[derive(Debug)]
557pub struct FdTable {
558 pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
559 next_file_description_id: FdId,
561}
562
563impl VisitProvenance for FdTable {
564 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
565 }
567}
568
569impl FdTable {
570 fn new() -> Self {
571 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
572 }
573 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
574 let mut fds = FdTable::new();
575 fds.insert_new(io::stdin());
576 if mute_stdout_stderr {
577 assert_eq!(fds.insert_new(NullOutput), 1);
578 assert_eq!(fds.insert_new(NullOutput), 2);
579 } else {
580 assert_eq!(fds.insert_new(io::stdout()), 1);
581 assert_eq!(fds.insert_new(io::stderr()), 2);
582 }
583 fds
584 }
585
586 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
587 let file_handle =
588 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
589 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
590 file_handle
591 }
592
593 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
595 let fd_ref = self.new_ref(fd);
596 self.insert(fd_ref)
597 }
598
599 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
600 self.insert_with_min_num(fd_ref, 0)
601 }
602
603 pub fn insert_with_min_num(
605 &mut self,
606 file_handle: DynFileDescriptionRef,
607 min_fd_num: FdNum,
608 ) -> FdNum {
609 let candidate_new_fd =
614 self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
615 if *fd_num != counter {
616 Some(counter)
619 } else {
620 None
622 }
623 });
624 let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
625 self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
628 });
629
630 self.fds.try_insert(new_fd_num, file_handle).unwrap();
631 new_fd_num
632 }
633
634 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
635 let fd = self.fds.get(&fd_num)?;
636 Some(fd.clone())
637 }
638
639 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
640 self.fds.remove(&fd_num)
641 }
642
643 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
644 self.fds.contains_key(&fd_num)
645 }
646}
647
648impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
649pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
650 fn read_from_host(
653 &mut self,
654 mut read_cb: impl FnMut(&mut [u8]) -> io::Result<usize>,
655 len: usize,
656 ptr: Pointer,
657 ) -> InterpResult<'tcx, Result<usize, IoError>> {
658 let this = self.eval_context_mut();
659
660 let mut bytes = vec![0; len];
661 let result = read_cb(&mut bytes);
662 match result {
663 Ok(read_size) => {
664 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
668 interp_ok(Ok(read_size))
669 }
670 Err(e) => interp_ok(Err(IoError::HostError(e))),
671 }
672 }
673
674 fn write_to_host(
676 &mut self,
677 mut file: impl io::Write,
678 len: usize,
679 ptr: Pointer,
680 ) -> InterpResult<'tcx, Result<usize, IoError>> {
681 let this = self.eval_context_mut();
682
683 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
684 let result = file.write(bytes);
685 interp_ok(result.map_err(IoError::HostError))
686 }
687}