1use std::any::Any;
2use std::collections::BTreeMap;
3use std::fs::{File, Metadata};
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
22#[derive(Debug, Clone)]
23struct FdIdWith<T: ?Sized> {
24 id: FdId,
25 inner: T,
26}
27
28#[repr(transparent)]
31#[derive(CoercePointee, Debug)]
32pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
33
34impl<T: ?Sized> Clone for FileDescriptionRef<T> {
35 fn clone(&self) -> Self {
36 FileDescriptionRef(self.0.clone())
37 }
38}
39
40impl<T: ?Sized> Deref for FileDescriptionRef<T> {
41 type Target = T;
42 fn deref(&self) -> &T {
43 &self.0.inner
44 }
45}
46
47impl<T: ?Sized> FileDescriptionRef<T> {
48 pub fn id(&self) -> FdId {
49 self.0.id
50 }
51}
52
53#[derive(Debug)]
55pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
56
57impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
58 fn clone(&self) -> Self {
59 WeakFileDescriptionRef(self.0.clone())
60 }
61}
62
63impl<T: ?Sized> FileDescriptionRef<T> {
64 pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
65 WeakFileDescriptionRef(Rc::downgrade(&this.0))
66 }
67}
68
69impl<T: ?Sized> WeakFileDescriptionRef<T> {
70 pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
71 self.0.upgrade().map(FileDescriptionRef)
72 }
73}
74
75impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
76 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
77 }
81}
82
83pub trait FileDescriptionExt: 'static {
87 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
88
89 fn close_ref<'tcx>(
92 self: FileDescriptionRef<Self>,
93 communicate_allowed: bool,
94 ecx: &mut MiriInterpCx<'tcx>,
95 ) -> InterpResult<'tcx, io::Result<()>>;
96}
97
98impl<T: FileDescription + 'static> FileDescriptionExt for T {
99 fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
100 self.0
101 }
102
103 fn close_ref<'tcx>(
104 self: FileDescriptionRef<Self>,
105 communicate_allowed: bool,
106 ecx: &mut MiriInterpCx<'tcx>,
107 ) -> InterpResult<'tcx, io::Result<()>> {
108 match Rc::into_inner(self.0) {
109 Some(fd) => {
110 ecx.machine.epoll_interests.remove_epolls(fd.id);
112
113 fd.inner.destroy(fd.id, communicate_allowed, ecx)
114 }
115 None => {
116 interp_ok(Ok(()))
118 }
119 }
120 }
121}
122
123pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
124
125impl FileDescriptionRef<dyn FileDescription> {
126 pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
127 let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
128 Some(FileDescriptionRef(inner))
129 }
130}
131
132pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
134 fn name(&self) -> &'static str;
135
136 fn read<'tcx>(
143 self: FileDescriptionRef<Self>,
144 _communicate_allowed: bool,
145 _ptr: Pointer,
146 _len: usize,
147 _ecx: &mut MiriInterpCx<'tcx>,
148 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
149 ) -> InterpResult<'tcx> {
150 throw_unsup_format!("cannot read from {}", self.name());
151 }
152
153 fn write<'tcx>(
160 self: FileDescriptionRef<Self>,
161 _communicate_allowed: bool,
162 _ptr: Pointer,
163 _len: usize,
164 _ecx: &mut MiriInterpCx<'tcx>,
165 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
166 ) -> InterpResult<'tcx> {
167 throw_unsup_format!("cannot write to {}", self.name());
168 }
169
170 fn short_fd_operations(&self) -> bool {
172 false
174 }
175
176 fn seek<'tcx>(
179 &self,
180 _communicate_allowed: bool,
181 _offset: SeekFrom,
182 ) -> InterpResult<'tcx, io::Result<u64>> {
183 throw_unsup_format!("cannot seek on {}", self.name());
184 }
185
186 fn destroy<'tcx>(
190 self,
191 _self_id: FdId,
192 _communicate_allowed: bool,
193 _ecx: &mut MiriInterpCx<'tcx>,
194 ) -> InterpResult<'tcx, io::Result<()>>
195 where
196 Self: Sized,
197 {
198 throw_unsup_format!("cannot close {}", self.name());
199 }
200
201 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
202 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
203 }
204
205 fn is_tty(&self, _communicate_allowed: bool) -> bool {
206 false
209 }
210
211 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
212 panic!("Not a unix file descriptor: {}", self.name());
213 }
214
215 fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
217 throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
218 }
219
220 fn set_flags<'tcx>(
222 &self,
223 _flag: i32,
224 _ecx: &mut MiriInterpCx<'tcx>,
225 ) -> InterpResult<'tcx, Scalar> {
226 throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
227 }
228}
229
230impl FileDescription for io::Stdin {
231 fn name(&self) -> &'static str {
232 "stdin"
233 }
234
235 fn read<'tcx>(
236 self: FileDescriptionRef<Self>,
237 communicate_allowed: bool,
238 ptr: Pointer,
239 len: usize,
240 ecx: &mut MiriInterpCx<'tcx>,
241 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
242 ) -> InterpResult<'tcx> {
243 if !communicate_allowed {
244 helpers::isolation_abort_error("`read` from stdin")?;
246 }
247
248 let mut stdin = &*self;
249 let result = ecx.read_from_host(|buf| stdin.read(buf), len, ptr)?;
250 finish.call(ecx, result)
251 }
252
253 fn destroy<'tcx>(
254 self,
255 _self_id: FdId,
256 _communicate_allowed: bool,
257 _ecx: &mut MiriInterpCx<'tcx>,
258 ) -> InterpResult<'tcx, io::Result<()>> {
259 interp_ok(Ok(()))
260 }
261
262 fn is_tty(&self, communicate_allowed: bool) -> bool {
263 communicate_allowed && self.is_terminal()
264 }
265}
266
267impl FileDescription for io::Stdout {
268 fn name(&self) -> &'static str {
269 "stdout"
270 }
271
272 fn write<'tcx>(
273 self: FileDescriptionRef<Self>,
274 _communicate_allowed: bool,
275 ptr: Pointer,
276 len: usize,
277 ecx: &mut MiriInterpCx<'tcx>,
278 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
279 ) -> InterpResult<'tcx> {
280 let result = ecx.write_to_host(&*self, len, ptr)?;
282 io::stdout().flush().unwrap();
288
289 finish.call(ecx, result)
290 }
291
292 fn destroy<'tcx>(
293 self,
294 _self_id: FdId,
295 _communicate_allowed: bool,
296 _ecx: &mut MiriInterpCx<'tcx>,
297 ) -> InterpResult<'tcx, io::Result<()>> {
298 interp_ok(Ok(()))
299 }
300
301 fn is_tty(&self, communicate_allowed: bool) -> bool {
302 communicate_allowed && self.is_terminal()
303 }
304}
305
306impl FileDescription for io::Stderr {
307 fn name(&self) -> &'static str {
308 "stderr"
309 }
310
311 fn destroy<'tcx>(
312 self,
313 _self_id: FdId,
314 _communicate_allowed: bool,
315 _ecx: &mut MiriInterpCx<'tcx>,
316 ) -> InterpResult<'tcx, io::Result<()>> {
317 interp_ok(Ok(()))
318 }
319
320 fn write<'tcx>(
321 self: FileDescriptionRef<Self>,
322 _communicate_allowed: bool,
323 ptr: Pointer,
324 len: usize,
325 ecx: &mut MiriInterpCx<'tcx>,
326 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
327 ) -> InterpResult<'tcx> {
328 let result = ecx.write_to_host(&*self, len, ptr)?;
330 finish.call(ecx, result)
332 }
333
334 fn is_tty(&self, communicate_allowed: bool) -> bool {
335 communicate_allowed && self.is_terminal()
336 }
337}
338
339#[derive(Debug)]
340pub struct FileHandle {
341 pub(crate) file: File,
342 pub(crate) writable: bool,
343}
344
345impl FileDescription for FileHandle {
346 fn name(&self) -> &'static str {
347 "file"
348 }
349
350 fn read<'tcx>(
351 self: FileDescriptionRef<Self>,
352 communicate_allowed: bool,
353 ptr: Pointer,
354 len: usize,
355 ecx: &mut MiriInterpCx<'tcx>,
356 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
357 ) -> InterpResult<'tcx> {
358 assert!(communicate_allowed, "isolation should have prevented even opening a file");
359
360 let mut file = &self.file;
361 let result = ecx.read_from_host(|buf| file.read(buf), len, ptr)?;
362 finish.call(ecx, result)
363 }
364
365 fn write<'tcx>(
366 self: FileDescriptionRef<Self>,
367 communicate_allowed: bool,
368 ptr: Pointer,
369 len: usize,
370 ecx: &mut MiriInterpCx<'tcx>,
371 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
372 ) -> InterpResult<'tcx> {
373 assert!(communicate_allowed, "isolation should have prevented even opening a file");
374
375 if !self.writable {
376 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
383 }
384 let result = ecx.write_to_host(&self.file, len, ptr)?;
385 finish.call(ecx, result)
386 }
387
388 fn seek<'tcx>(
389 &self,
390 communicate_allowed: bool,
391 offset: SeekFrom,
392 ) -> InterpResult<'tcx, io::Result<u64>> {
393 assert!(communicate_allowed, "isolation should have prevented even opening a file");
394 interp_ok((&mut &self.file).seek(offset))
395 }
396
397 fn destroy<'tcx>(
398 self,
399 _self_id: FdId,
400 communicate_allowed: bool,
401 _ecx: &mut MiriInterpCx<'tcx>,
402 ) -> InterpResult<'tcx, io::Result<()>> {
403 assert!(communicate_allowed, "isolation should have prevented even opening a file");
404 if self.writable {
406 let result = self.file.sync_all();
409 drop(self.file);
411 interp_ok(result)
412 } else {
413 drop(self.file);
420 interp_ok(Ok(()))
421 }
422 }
423
424 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
425 interp_ok(self.file.metadata())
426 }
427
428 fn is_tty(&self, communicate_allowed: bool) -> bool {
429 communicate_allowed && self.file.is_terminal()
430 }
431
432 fn short_fd_operations(&self) -> bool {
433 true
437 }
438
439 fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
440 assert!(
441 ecx.target_os_is_unix(),
442 "unix file operations are only available for unix targets"
443 );
444 self
445 }
446}
447
448#[derive(Debug)]
450pub struct NullOutput;
451
452impl FileDescription for NullOutput {
453 fn name(&self) -> &'static str {
454 "stderr and stdout"
455 }
456
457 fn write<'tcx>(
458 self: FileDescriptionRef<Self>,
459 _communicate_allowed: bool,
460 _ptr: Pointer,
461 len: usize,
462 ecx: &mut MiriInterpCx<'tcx>,
463 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
464 ) -> InterpResult<'tcx> {
465 finish.call(ecx, Ok(len))
467 }
468
469 fn destroy<'tcx>(
470 self,
471 _self_id: FdId,
472 _communicate_allowed: bool,
473 _ecx: &mut MiriInterpCx<'tcx>,
474 ) -> InterpResult<'tcx, io::Result<()>> {
475 interp_ok(Ok(()))
476 }
477}
478
479pub type FdNum = i32;
481
482#[derive(Debug)]
484pub struct FdTable {
485 pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
486 next_file_description_id: FdId,
488}
489
490impl VisitProvenance for FdTable {
491 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
492 }
494}
495
496impl FdTable {
497 fn new() -> Self {
498 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
499 }
500 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
501 let mut fds = FdTable::new();
502 fds.insert_new(io::stdin());
503 if mute_stdout_stderr {
504 assert_eq!(fds.insert_new(NullOutput), 1);
505 assert_eq!(fds.insert_new(NullOutput), 2);
506 } else {
507 assert_eq!(fds.insert_new(io::stdout()), 1);
508 assert_eq!(fds.insert_new(io::stderr()), 2);
509 }
510 fds
511 }
512
513 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
514 let file_handle =
515 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
516 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
517 file_handle
518 }
519
520 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
522 let fd_ref = self.new_ref(fd);
523 self.insert(fd_ref)
524 }
525
526 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
527 self.insert_with_min_num(fd_ref, 0)
528 }
529
530 pub fn insert_with_min_num(
532 &mut self,
533 file_handle: DynFileDescriptionRef,
534 min_fd_num: FdNum,
535 ) -> FdNum {
536 let candidate_new_fd =
541 self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
542 if *fd_num != counter {
543 Some(counter)
546 } else {
547 None
549 }
550 });
551 let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
552 self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
555 });
556
557 self.fds.try_insert(new_fd_num, file_handle).unwrap();
558 new_fd_num
559 }
560
561 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
562 let fd = self.fds.get(&fd_num)?;
563 Some(fd.clone())
564 }
565
566 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
567 self.fds.remove(&fd_num)
568 }
569
570 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
571 self.fds.contains_key(&fd_num)
572 }
573}
574
575impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
576pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
577 fn read_from_host(
580 &mut self,
581 mut read_cb: impl FnMut(&mut [u8]) -> io::Result<usize>,
582 len: usize,
583 ptr: Pointer,
584 ) -> InterpResult<'tcx, Result<usize, IoError>> {
585 let this = self.eval_context_mut();
586
587 let mut bytes = vec![0; len];
588 let result = read_cb(&mut bytes);
589 match result {
590 Ok(read_size) => {
591 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
595 interp_ok(Ok(read_size))
596 }
597 Err(e) => interp_ok(Err(IoError::HostError(e))),
598 }
599 }
600
601 fn write_to_host(
603 &mut self,
604 mut file: impl io::Write,
605 len: usize,
606 ptr: Pointer,
607 ) -> InterpResult<'tcx, Result<usize, IoError>> {
608 let this = self.eval_context_mut();
609
610 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
611 let result = file.write(bytes);
612 interp_ok(result.map_err(IoError::HostError))
613 }
614}