1use std::any::Any;
2use std::collections::BTreeMap;
3use std::fs::{File, Metadata};
4use std::io::{ErrorKind, IsTerminal, 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(fd.id);
112
113 fd.inner.close(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 close<'tcx>(
188 self,
189 _communicate_allowed: bool,
190 _ecx: &mut MiriInterpCx<'tcx>,
191 ) -> InterpResult<'tcx, io::Result<()>>
192 where
193 Self: Sized,
194 {
195 throw_unsup_format!("cannot close {}", self.name());
196 }
197
198 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
199 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
200 }
201
202 fn is_tty(&self, _communicate_allowed: bool) -> bool {
203 false
206 }
207
208 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
209 panic!("Not a unix file descriptor: {}", self.name());
210 }
211
212 fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
214 throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
215 }
216
217 fn set_flags<'tcx>(
219 &self,
220 _flag: i32,
221 _ecx: &mut MiriInterpCx<'tcx>,
222 ) -> InterpResult<'tcx, Scalar> {
223 throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
224 }
225}
226
227impl FileDescription for io::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(&*self, len, ptr)?;
246 finish.call(ecx, result)
247 }
248
249 fn is_tty(&self, communicate_allowed: bool) -> bool {
250 communicate_allowed && self.is_terminal()
251 }
252}
253
254impl FileDescription for io::Stdout {
255 fn name(&self) -> &'static str {
256 "stdout"
257 }
258
259 fn write<'tcx>(
260 self: FileDescriptionRef<Self>,
261 _communicate_allowed: bool,
262 ptr: Pointer,
263 len: usize,
264 ecx: &mut MiriInterpCx<'tcx>,
265 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
266 ) -> InterpResult<'tcx> {
267 let result = ecx.write_to_host(&*self, len, ptr)?;
269 io::stdout().flush().unwrap();
275
276 finish.call(ecx, result)
277 }
278
279 fn is_tty(&self, communicate_allowed: bool) -> bool {
280 communicate_allowed && self.is_terminal()
281 }
282}
283
284impl FileDescription for io::Stderr {
285 fn name(&self) -> &'static str {
286 "stderr"
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, len, ptr)?;
299 finish.call(ecx, result)
301 }
302
303 fn is_tty(&self, communicate_allowed: bool) -> bool {
304 communicate_allowed && self.is_terminal()
305 }
306}
307
308#[derive(Debug)]
309pub struct FileHandle {
310 pub(crate) file: File,
311 pub(crate) writable: bool,
312}
313
314impl FileDescription for FileHandle {
315 fn name(&self) -> &'static str {
316 "file"
317 }
318
319 fn read<'tcx>(
320 self: FileDescriptionRef<Self>,
321 communicate_allowed: bool,
322 ptr: Pointer,
323 len: usize,
324 ecx: &mut MiriInterpCx<'tcx>,
325 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
326 ) -> InterpResult<'tcx> {
327 assert!(communicate_allowed, "isolation should have prevented even opening a file");
328
329 let result = ecx.read_from_host(&self.file, len, ptr)?;
330 finish.call(ecx, result)
331 }
332
333 fn write<'tcx>(
334 self: FileDescriptionRef<Self>,
335 communicate_allowed: bool,
336 ptr: Pointer,
337 len: usize,
338 ecx: &mut MiriInterpCx<'tcx>,
339 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
340 ) -> InterpResult<'tcx> {
341 assert!(communicate_allowed, "isolation should have prevented even opening a file");
342
343 if !self.writable {
344 return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
351 }
352 let result = ecx.write_to_host(&self.file, len, ptr)?;
353 finish.call(ecx, result)
354 }
355
356 fn seek<'tcx>(
357 &self,
358 communicate_allowed: bool,
359 offset: SeekFrom,
360 ) -> InterpResult<'tcx, io::Result<u64>> {
361 assert!(communicate_allowed, "isolation should have prevented even opening a file");
362 interp_ok((&mut &self.file).seek(offset))
363 }
364
365 fn close<'tcx>(
366 self,
367 communicate_allowed: bool,
368 _ecx: &mut MiriInterpCx<'tcx>,
369 ) -> InterpResult<'tcx, io::Result<()>> {
370 assert!(communicate_allowed, "isolation should have prevented even opening a file");
371 if self.writable {
373 let result = self.file.sync_all();
376 drop(self.file);
378 interp_ok(result)
379 } else {
380 drop(self.file);
387 interp_ok(Ok(()))
388 }
389 }
390
391 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
392 interp_ok(self.file.metadata())
393 }
394
395 fn is_tty(&self, communicate_allowed: bool) -> bool {
396 communicate_allowed && self.file.is_terminal()
397 }
398
399 fn short_fd_operations(&self) -> bool {
400 true
404 }
405
406 fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
407 assert!(
408 ecx.target_os_is_unix(),
409 "unix file operations are only available for unix targets"
410 );
411 self
412 }
413}
414
415#[derive(Debug)]
417pub struct NullOutput;
418
419impl FileDescription for NullOutput {
420 fn name(&self) -> &'static str {
421 "stderr and stdout"
422 }
423
424 fn write<'tcx>(
425 self: FileDescriptionRef<Self>,
426 _communicate_allowed: bool,
427 _ptr: Pointer,
428 len: usize,
429 ecx: &mut MiriInterpCx<'tcx>,
430 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
431 ) -> InterpResult<'tcx> {
432 finish.call(ecx, Ok(len))
434 }
435}
436
437pub type FdNum = i32;
439
440#[derive(Debug)]
442pub struct FdTable {
443 pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
444 next_file_description_id: FdId,
446}
447
448impl VisitProvenance for FdTable {
449 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
450 }
452}
453
454impl FdTable {
455 fn new() -> Self {
456 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
457 }
458 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
459 let mut fds = FdTable::new();
460 fds.insert_new(io::stdin());
461 if mute_stdout_stderr {
462 assert_eq!(fds.insert_new(NullOutput), 1);
463 assert_eq!(fds.insert_new(NullOutput), 2);
464 } else {
465 assert_eq!(fds.insert_new(io::stdout()), 1);
466 assert_eq!(fds.insert_new(io::stderr()), 2);
467 }
468 fds
469 }
470
471 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
472 let file_handle =
473 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
474 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
475 file_handle
476 }
477
478 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
480 let fd_ref = self.new_ref(fd);
481 self.insert(fd_ref)
482 }
483
484 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
485 self.insert_with_min_num(fd_ref, 0)
486 }
487
488 pub fn insert_with_min_num(
490 &mut self,
491 file_handle: DynFileDescriptionRef,
492 min_fd_num: FdNum,
493 ) -> FdNum {
494 let candidate_new_fd =
499 self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
500 if *fd_num != counter {
501 Some(counter)
504 } else {
505 None
507 }
508 });
509 let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
510 self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
513 });
514
515 self.fds.try_insert(new_fd_num, file_handle).unwrap();
516 new_fd_num
517 }
518
519 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
520 let fd = self.fds.get(&fd_num)?;
521 Some(fd.clone())
522 }
523
524 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
525 self.fds.remove(&fd_num)
526 }
527
528 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
529 self.fds.contains_key(&fd_num)
530 }
531}
532
533impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
534pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
535 fn read_from_host(
538 &mut self,
539 mut file: impl io::Read,
540 len: usize,
541 ptr: Pointer,
542 ) -> InterpResult<'tcx, Result<usize, IoError>> {
543 let this = self.eval_context_mut();
544
545 let mut bytes = vec![0; len];
546 let result = file.read(&mut bytes);
547 match result {
548 Ok(read_size) => {
549 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
553 interp_ok(Ok(read_size))
554 }
555 Err(e) => interp_ok(Err(IoError::HostError(e))),
556 }
557 }
558
559 fn write_to_host(
561 &mut self,
562 mut file: impl io::Write,
563 len: usize,
564 ptr: Pointer,
565 ) -> InterpResult<'tcx, Result<usize, IoError>> {
566 let this = self.eval_context_mut();
567
568 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
569 let result = file.write(bytes);
570 interp_ok(result.map_err(IoError::HostError))
571 }
572}