1use std::any::Any;
2use std::collections::BTreeMap;
3use std::fs::{File, Metadata};
4use std::io::{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 seek<'tcx>(
173 &self,
174 _communicate_allowed: bool,
175 _offset: SeekFrom,
176 ) -> InterpResult<'tcx, io::Result<u64>> {
177 throw_unsup_format!("cannot seek on {}", self.name());
178 }
179
180 fn close<'tcx>(
182 self,
183 _communicate_allowed: bool,
184 _ecx: &mut MiriInterpCx<'tcx>,
185 ) -> InterpResult<'tcx, io::Result<()>>
186 where
187 Self: Sized,
188 {
189 throw_unsup_format!("cannot close {}", self.name());
190 }
191
192 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
193 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
194 }
195
196 fn is_tty(&self, _communicate_allowed: bool) -> bool {
197 false
200 }
201
202 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
203 panic!("Not a unix file descriptor: {}", self.name());
204 }
205
206 fn get_flags<'tcx>(&self, _ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
208 throw_unsup_format!("fcntl: {} is not supported for F_GETFL", self.name());
209 }
210
211 fn set_flags<'tcx>(
213 &self,
214 _flag: i32,
215 _ecx: &mut MiriInterpCx<'tcx>,
216 ) -> InterpResult<'tcx, Scalar> {
217 throw_unsup_format!("fcntl: {} is not supported for F_SETFL", self.name());
218 }
219}
220
221impl FileDescription for io::Stdin {
222 fn name(&self) -> &'static str {
223 "stdin"
224 }
225
226 fn read<'tcx>(
227 self: FileDescriptionRef<Self>,
228 communicate_allowed: bool,
229 ptr: Pointer,
230 len: usize,
231 ecx: &mut MiriInterpCx<'tcx>,
232 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
233 ) -> InterpResult<'tcx> {
234 if !communicate_allowed {
235 helpers::isolation_abort_error("`read` from stdin")?;
237 }
238
239 let result = ecx.read_from_host(&*self, len, ptr)?;
240 finish.call(ecx, result)
241 }
242
243 fn is_tty(&self, communicate_allowed: bool) -> bool {
244 communicate_allowed && self.is_terminal()
245 }
246}
247
248impl FileDescription for io::Stdout {
249 fn name(&self) -> &'static str {
250 "stdout"
251 }
252
253 fn write<'tcx>(
254 self: FileDescriptionRef<Self>,
255 _communicate_allowed: bool,
256 ptr: Pointer,
257 len: usize,
258 ecx: &mut MiriInterpCx<'tcx>,
259 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
260 ) -> InterpResult<'tcx> {
261 let result = ecx.write_to_host(&*self, len, ptr)?;
263 io::stdout().flush().unwrap();
269
270 finish.call(ecx, result)
271 }
272
273 fn is_tty(&self, communicate_allowed: bool) -> bool {
274 communicate_allowed && self.is_terminal()
275 }
276}
277
278impl FileDescription for io::Stderr {
279 fn name(&self) -> &'static str {
280 "stderr"
281 }
282
283 fn write<'tcx>(
284 self: FileDescriptionRef<Self>,
285 _communicate_allowed: bool,
286 ptr: Pointer,
287 len: usize,
288 ecx: &mut MiriInterpCx<'tcx>,
289 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
290 ) -> InterpResult<'tcx> {
291 let result = ecx.write_to_host(&*self, len, ptr)?;
293 finish.call(ecx, result)
295 }
296
297 fn is_tty(&self, communicate_allowed: bool) -> bool {
298 communicate_allowed && self.is_terminal()
299 }
300}
301
302#[derive(Debug)]
303pub struct FileHandle {
304 pub(crate) file: File,
305 pub(crate) writable: bool,
306}
307
308impl FileDescription for FileHandle {
309 fn name(&self) -> &'static str {
310 "file"
311 }
312
313 fn read<'tcx>(
314 self: FileDescriptionRef<Self>,
315 communicate_allowed: bool,
316 ptr: Pointer,
317 len: usize,
318 ecx: &mut MiriInterpCx<'tcx>,
319 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
320 ) -> InterpResult<'tcx> {
321 assert!(communicate_allowed, "isolation should have prevented even opening a file");
322
323 let result = ecx.read_from_host(&self.file, len, ptr)?;
324 finish.call(ecx, result)
325 }
326
327 fn write<'tcx>(
328 self: FileDescriptionRef<Self>,
329 communicate_allowed: bool,
330 ptr: Pointer,
331 len: usize,
332 ecx: &mut MiriInterpCx<'tcx>,
333 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
334 ) -> InterpResult<'tcx> {
335 assert!(communicate_allowed, "isolation should have prevented even opening a file");
336
337 let result = ecx.write_to_host(&self.file, len, ptr)?;
338 finish.call(ecx, result)
339 }
340
341 fn seek<'tcx>(
342 &self,
343 communicate_allowed: bool,
344 offset: SeekFrom,
345 ) -> InterpResult<'tcx, io::Result<u64>> {
346 assert!(communicate_allowed, "isolation should have prevented even opening a file");
347 interp_ok((&mut &self.file).seek(offset))
348 }
349
350 fn close<'tcx>(
351 self,
352 communicate_allowed: bool,
353 _ecx: &mut MiriInterpCx<'tcx>,
354 ) -> InterpResult<'tcx, io::Result<()>> {
355 assert!(communicate_allowed, "isolation should have prevented even opening a file");
356 if self.writable {
358 let result = self.file.sync_all();
361 drop(self.file);
363 interp_ok(result)
364 } else {
365 drop(self.file);
372 interp_ok(Ok(()))
373 }
374 }
375
376 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
377 interp_ok(self.file.metadata())
378 }
379
380 fn is_tty(&self, communicate_allowed: bool) -> bool {
381 communicate_allowed && self.file.is_terminal()
382 }
383
384 fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
385 assert!(
386 ecx.target_os_is_unix(),
387 "unix file operations are only available for unix targets"
388 );
389 self
390 }
391}
392
393#[derive(Debug)]
395pub struct NullOutput;
396
397impl FileDescription for NullOutput {
398 fn name(&self) -> &'static str {
399 "stderr and stdout"
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 finish.call(ecx, Ok(len))
412 }
413}
414
415pub type FdNum = i32;
417
418#[derive(Debug)]
420pub struct FdTable {
421 pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
422 next_file_description_id: FdId,
424}
425
426impl VisitProvenance for FdTable {
427 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
428 }
430}
431
432impl FdTable {
433 fn new() -> Self {
434 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
435 }
436 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
437 let mut fds = FdTable::new();
438 fds.insert_new(io::stdin());
439 if mute_stdout_stderr {
440 assert_eq!(fds.insert_new(NullOutput), 1);
441 assert_eq!(fds.insert_new(NullOutput), 2);
442 } else {
443 assert_eq!(fds.insert_new(io::stdout()), 1);
444 assert_eq!(fds.insert_new(io::stderr()), 2);
445 }
446 fds
447 }
448
449 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
450 let file_handle =
451 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
452 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
453 file_handle
454 }
455
456 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
458 let fd_ref = self.new_ref(fd);
459 self.insert(fd_ref)
460 }
461
462 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
463 self.insert_with_min_num(fd_ref, 0)
464 }
465
466 pub fn insert_with_min_num(
468 &mut self,
469 file_handle: DynFileDescriptionRef,
470 min_fd_num: FdNum,
471 ) -> FdNum {
472 let candidate_new_fd =
477 self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
478 if *fd_num != counter {
479 Some(counter)
482 } else {
483 None
485 }
486 });
487 let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
488 self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
491 });
492
493 self.fds.try_insert(new_fd_num, file_handle).unwrap();
494 new_fd_num
495 }
496
497 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
498 let fd = self.fds.get(&fd_num)?;
499 Some(fd.clone())
500 }
501
502 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
503 self.fds.remove(&fd_num)
504 }
505
506 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
507 self.fds.contains_key(&fd_num)
508 }
509}
510
511impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
512pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
513 fn read_from_host(
516 &mut self,
517 mut file: impl io::Read,
518 len: usize,
519 ptr: Pointer,
520 ) -> InterpResult<'tcx, Result<usize, IoError>> {
521 let this = self.eval_context_mut();
522
523 let mut bytes = vec![0; len];
524 let result = file.read(&mut bytes);
525 match result {
526 Ok(read_size) => {
527 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
531 interp_ok(Ok(read_size))
532 }
533 Err(e) => interp_ok(Err(IoError::HostError(e))),
534 }
535 }
536
537 fn write_to_host(
539 &mut self,
540 mut file: impl io::Write,
541 len: usize,
542 ptr: Pointer,
543 ) -> InterpResult<'tcx, Result<usize, IoError>> {
544 let this = self.eval_context_mut();
545
546 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
547 let result = file.write(bytes);
548 interp_ok(result.map_err(IoError::HostError))
549 }
550}