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>>);
44
45impl<T: ?Sized> Clone for FileDescriptionRef<T> {
46 fn clone(&self) -> Self {
47 FileDescriptionRef(self.0.clone())
48 }
49}
50
51impl<T: ?Sized> Deref for FileDescriptionRef<T> {
52 type Target = T;
53 fn deref(&self) -> &T {
54 &self.0.inner
55 }
56}
57
58impl<T: ?Sized> FileDescriptionRef<T> {
59 pub fn id(&self) -> FdId {
60 self.0.id
61 }
62}
63
64impl<T: ?Sized> PartialEq for FileDescriptionRef<T> {
65 fn eq(&self, other: &Self) -> bool {
66 self.0.id == other.0.id
67 }
68}
69
70impl<T: ?Sized> Eq for FileDescriptionRef<T> {}
71
72#[derive(Debug)]
74pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
75
76impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
77 fn clone(&self) -> Self {
78 WeakFileDescriptionRef(self.0.clone())
79 }
80}
81
82impl<T: ?Sized> FileDescriptionRef<T> {
83 pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
84 WeakFileDescriptionRef(Rc::downgrade(&this.0))
85 }
86}
87
88impl<T: ?Sized> WeakFileDescriptionRef<T> {
89 pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
90 self.0.upgrade().map(FileDescriptionRef)
91 }
92}
93
94impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
95 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
96 }
100}
101
102pub trait FileDescriptionExt: 'static {
106 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
107
108 fn close_ref<'tcx>(
111 self: FileDescriptionRef<Self>,
112 communicate_allowed: bool,
113 ecx: &mut MiriInterpCx<'tcx>,
114 ) -> InterpResult<'tcx, io::Result<()>>;
115}
116
117impl<T: FileDescription + 'static> FileDescriptionExt for T {
118 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
119 self.0
120 }
121
122 fn close_ref<'tcx>(
123 self: FileDescriptionRef<Self>,
124 communicate_allowed: bool,
125 ecx: &mut MiriInterpCx<'tcx>,
126 ) -> InterpResult<'tcx, io::Result<()>> {
127 match Rc::into_inner(self.0) {
128 Some(fd) => {
129 ecx.machine.readiness_interests.remove_watchers_for_fd(fd.id);
131
132 fd.inner.destroy(fd.id, communicate_allowed, ecx)
133 }
134 None => {
135 interp_ok(Ok(()))
137 }
138 }
139 }
140}
141
142pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
143
144impl FileDescriptionRef<dyn FileDescription> {
145 pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
146 let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
147 Some(FileDescriptionRef(inner))
148 }
149}
150
151pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
153 fn name(&self) -> &'static str;
154
155 fn read<'tcx>(
162 self: FileDescriptionRef<Self>,
163 _communicate_allowed: bool,
164 _ptr: Pointer,
165 _len: usize,
166 _ecx: &mut MiriInterpCx<'tcx>,
167 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
168 ) -> InterpResult<'tcx> {
169 throw_unsup_format!("cannot read from {}", self.name());
170 }
171
172 fn write<'tcx>(
179 self: FileDescriptionRef<Self>,
180 _communicate_allowed: bool,
181 _ptr: Pointer,
182 _len: usize,
183 _ecx: &mut MiriInterpCx<'tcx>,
184 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
185 ) -> InterpResult<'tcx> {
186 throw_unsup_format!("cannot write to {}", self.name());
187 }
188
189 fn short_fd_operations(&self) -> bool {
191 false
193 }
194
195 fn seek<'tcx>(
198 &self,
199 _communicate_allowed: bool,
200 _offset: SeekFrom,
201 ) -> InterpResult<'tcx, io::Result<u64>> {
202 throw_unsup_format!("cannot seek on {}", self.name());
203 }
204
205 fn destroy<'tcx>(
209 self,
210 _self_id: FdId,
211 _communicate_allowed: bool,
212 _ecx: &mut MiriInterpCx<'tcx>,
213 ) -> InterpResult<'tcx, io::Result<()>>
214 where
215 Self: Sized,
216 {
217 throw_unsup_format!("cannot close {}", self.name());
218 }
219
220 fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
225 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
226 }
227
228 fn is_tty(&self, _communicate_allowed: bool) -> bool {
229 false
232 }
233
234 fn as_unix<'tcx>(
235 self: FileDescriptionRef<Self>,
236 _ecx: &MiriInterpCx<'tcx>,
237 ) -> FileDescriptionRef<dyn UnixFileDescription> {
238 panic!("Not a unix file descriptor: {}", self.name());
239 }
240
241 fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
243 throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
244 }
245
246 fn set_flags<'tcx>(
248 &self,
249 _flag: i32,
250 _ecx: &mut MiriInterpCx<'tcx>,
251 ) -> InterpResult<'tcx, Scalar> {
252 throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
253 }
254
255 fn readiness<'tcx>(&self) -> InterpResult<'tcx, Readiness> {
257 throw_unsup_format!("{}: this file description doesn't support I/O readiness", self.name());
258 }
259}
260
261impl FileDescription for io::Stdin {
262 fn name(&self) -> &'static str {
263 "stdin"
264 }
265
266 fn read<'tcx>(
267 self: FileDescriptionRef<Self>,
268 communicate_allowed: bool,
269 ptr: Pointer,
270 len: usize,
271 ecx: &mut MiriInterpCx<'tcx>,
272 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
273 ) -> InterpResult<'tcx> {
274 if !communicate_allowed {
275 helpers::isolation_abort_error("`read` from stdin")?;
277 }
278
279 let mut stdin = &*self;
280 let result = ecx.read_from_host(|buf| stdin.read(buf), len, ptr)?;
281 finish.call(ecx, result)
282 }
283
284 fn destroy<'tcx>(
285 self,
286 _self_id: FdId,
287 _communicate_allowed: bool,
288 _ecx: &mut MiriInterpCx<'tcx>,
289 ) -> InterpResult<'tcx, io::Result<()>> {
290 interp_ok(Ok(()))
291 }
292
293 fn is_tty(&self, communicate_allowed: bool) -> bool {
294 communicate_allowed && self.is_terminal()
295 }
296}
297
298impl FileDescription for io::Stdout {
299 fn name(&self) -> &'static str {
300 "stdout"
301 }
302
303 fn write<'tcx>(
304 self: FileDescriptionRef<Self>,
305 _communicate_allowed: bool,
306 ptr: Pointer,
307 len: usize,
308 ecx: &mut MiriInterpCx<'tcx>,
309 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
310 ) -> InterpResult<'tcx> {
311 let result = ecx.write_to_host(&*self, len, ptr)?;
313 io::stdout().flush().unwrap();
319
320 finish.call(ecx, result)
321 }
322
323 fn destroy<'tcx>(
324 self,
325 _self_id: FdId,
326 _communicate_allowed: bool,
327 _ecx: &mut MiriInterpCx<'tcx>,
328 ) -> InterpResult<'tcx, io::Result<()>> {
329 interp_ok(Ok(()))
330 }
331
332 fn is_tty(&self, communicate_allowed: bool) -> bool {
333 communicate_allowed && self.is_terminal()
334 }
335}
336
337impl FileDescription for io::Stderr {
338 fn name(&self) -> &'static str {
339 "stderr"
340 }
341
342 fn destroy<'tcx>(
343 self,
344 _self_id: FdId,
345 _communicate_allowed: bool,
346 _ecx: &mut MiriInterpCx<'tcx>,
347 ) -> InterpResult<'tcx, io::Result<()>> {
348 interp_ok(Ok(()))
349 }
350
351 fn write<'tcx>(
352 self: FileDescriptionRef<Self>,
353 _communicate_allowed: bool,
354 ptr: Pointer,
355 len: usize,
356 ecx: &mut MiriInterpCx<'tcx>,
357 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
358 ) -> InterpResult<'tcx> {
359 let result = ecx.write_to_host(&*self, len, ptr)?;
361 finish.call(ecx, result)
363 }
364
365 fn is_tty(&self, communicate_allowed: bool) -> bool {
366 communicate_allowed && self.is_terminal()
367 }
368}
369
370#[derive(Debug)]
371pub struct FileHandle {
372 pub(crate) file: File,
373 pub(crate) readable: bool,
374 pub(crate) writable: bool,
375}
376
377impl FileDescription for FileHandle {
378 fn name(&self) -> &'static str {
379 "file"
380 }
381
382 fn read<'tcx>(
383 self: FileDescriptionRef<Self>,
384 communicate_allowed: bool,
385 ptr: Pointer,
386 len: usize,
387 ecx: &mut MiriInterpCx<'tcx>,
388 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
389 ) -> InterpResult<'tcx> {
390 assert!(communicate_allowed, "isolation should have prevented even opening a file");
391
392 if !self.readable {
393 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
394 }
395
396 let mut file = &self.file;
397 let result = ecx.read_from_host(|buf| file.read(buf), len, ptr)?;
398 finish.call(ecx, result)
399 }
400
401 fn write<'tcx>(
402 self: FileDescriptionRef<Self>,
403 communicate_allowed: bool,
404 ptr: Pointer,
405 len: usize,
406 ecx: &mut MiriInterpCx<'tcx>,
407 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
408 ) -> InterpResult<'tcx> {
409 assert!(communicate_allowed, "isolation should have prevented even opening a file");
410
411 if !self.writable {
412 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
419 }
420 let result = ecx.write_to_host(&self.file, len, ptr)?;
421 finish.call(ecx, result)
422 }
423
424 fn seek<'tcx>(
425 &self,
426 communicate_allowed: bool,
427 offset: SeekFrom,
428 ) -> InterpResult<'tcx, io::Result<u64>> {
429 assert!(communicate_allowed, "isolation should have prevented even opening a file");
430 interp_ok((&mut &self.file).seek(offset))
431 }
432
433 fn destroy<'tcx>(
434 self,
435 _self_id: FdId,
436 communicate_allowed: bool,
437 _ecx: &mut MiriInterpCx<'tcx>,
438 ) -> InterpResult<'tcx, io::Result<()>> {
439 assert!(communicate_allowed, "isolation should have prevented even opening a file");
440 if self.writable {
442 let result = self.file.sync_all();
445 drop(self.file);
447 interp_ok(result)
448 } else {
449 drop(self.file);
456 interp_ok(Ok(()))
457 }
458 }
459
460 fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either<io::Result<fs::Metadata>, &'static str>> {
461 interp_ok(Either::Left(self.file.metadata()))
462 }
463
464 fn is_tty(&self, communicate_allowed: bool) -> bool {
465 communicate_allowed && self.file.is_terminal()
466 }
467
468 fn short_fd_operations(&self) -> bool {
469 true
473 }
474
475 fn as_unix<'tcx>(
476 self: FileDescriptionRef<Self>,
477 ecx: &MiriInterpCx<'tcx>,
478 ) -> FileDescriptionRef<dyn UnixFileDescription> {
479 assert!(
480 ecx.target_os_is_unix(),
481 "unix file operations are only available for unix targets"
482 );
483 self
484 }
485}
486
487#[derive(Debug)]
488pub struct DirHandle {
489 pub(crate) dir: Dir,
490}
491
492impl FileDescription for DirHandle {
493 fn name(&self) -> &'static str {
494 "directory"
495 }
496
497 fn metadata<'tcx>(
498 &self,
499 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
500 return interp_ok(Either::Left(self.dir.metadata()));
501 }
502
503 fn destroy<'tcx>(
504 self,
505 _self_id: FdId,
506 _communicate_allowed: bool,
507 _ecx: &mut MiriInterpCx<'tcx>,
508 ) -> InterpResult<'tcx, io::Result<()>> {
509 interp_ok(Ok(()))
510 }
511}
512
513#[derive(Debug)]
515pub struct NullOutput;
516
517impl FileDescription for NullOutput {
518 fn name(&self) -> &'static str {
519 "stderr and stdout"
520 }
521
522 fn write<'tcx>(
523 self: FileDescriptionRef<Self>,
524 _communicate_allowed: bool,
525 _ptr: Pointer,
526 len: usize,
527 ecx: &mut MiriInterpCx<'tcx>,
528 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
529 ) -> InterpResult<'tcx> {
530 finish.call(ecx, Ok(len))
532 }
533
534 fn destroy<'tcx>(
535 self,
536 _self_id: FdId,
537 _communicate_allowed: bool,
538 _ecx: &mut MiriInterpCx<'tcx>,
539 ) -> InterpResult<'tcx, io::Result<()>> {
540 interp_ok(Ok(()))
541 }
542}
543
544pub type FdNum = i32;
546
547#[derive(Debug)]
549pub struct FdTable {
550 pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
551 next_file_description_id: FdId,
553}
554
555impl VisitProvenance for FdTable {
556 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
557 }
559}
560
561impl FdTable {
562 fn new() -> Self {
563 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
564 }
565 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
566 let mut fds = FdTable::new();
567 fds.insert_new(io::stdin());
568 if mute_stdout_stderr {
569 assert_eq!(fds.insert_new(NullOutput), 1);
570 assert_eq!(fds.insert_new(NullOutput), 2);
571 } else {
572 assert_eq!(fds.insert_new(io::stdout()), 1);
573 assert_eq!(fds.insert_new(io::stderr()), 2);
574 }
575 fds
576 }
577
578 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
579 let file_handle =
580 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
581 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
582 file_handle
583 }
584
585 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
587 let fd_ref = self.new_ref(fd);
588 self.insert(fd_ref)
589 }
590
591 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
592 self.insert_with_min_num(fd_ref, 0)
593 }
594
595 pub fn insert_with_min_num(
597 &mut self,
598 file_handle: DynFileDescriptionRef,
599 min_fd_num: FdNum,
600 ) -> FdNum {
601 let candidate_new_fd =
606 self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
607 if *fd_num != counter {
608 Some(counter)
611 } else {
612 None
614 }
615 });
616 let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
617 self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
620 });
621
622 self.fds.try_insert(new_fd_num, file_handle).unwrap();
623 new_fd_num
624 }
625
626 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
627 let fd = self.fds.get(&fd_num)?;
628 Some(fd.clone())
629 }
630
631 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
632 self.fds.remove(&fd_num)
633 }
634
635 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
636 self.fds.contains_key(&fd_num)
637 }
638}
639
640impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
641pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
642 fn read_from_host(
645 &mut self,
646 mut read_cb: impl FnMut(&mut [u8]) -> io::Result<usize>,
647 len: usize,
648 ptr: Pointer,
649 ) -> InterpResult<'tcx, Result<usize, IoError>> {
650 let this = self.eval_context_mut();
651
652 let mut bytes = vec![0; len];
653 let result = read_cb(&mut bytes);
654 match result {
655 Ok(read_size) => {
656 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
660 interp_ok(Ok(read_size))
661 }
662 Err(e) => interp_ok(Err(IoError::HostError(e))),
663 }
664 }
665
666 fn write_to_host(
668 &mut self,
669 mut file: impl io::Write,
670 len: usize,
671 ptr: Pointer,
672 ) -> InterpResult<'tcx, Result<usize, IoError>> {
673 let this = self.eval_context_mut();
674
675 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
676 let result = file.write(bytes);
677 interp_ok(result.map_err(IoError::HostError))
678 }
679}