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>(
140 self: FileDescriptionRef<Self>,
141 _communicate_allowed: bool,
142 _ptr: Pointer,
143 _len: usize,
144 _ecx: &mut MiriInterpCx<'tcx>,
145 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
146 ) -> InterpResult<'tcx> {
147 throw_unsup_format!("cannot read from {}", self.name());
148 }
149
150 fn write<'tcx>(
154 self: FileDescriptionRef<Self>,
155 _communicate_allowed: bool,
156 _ptr: Pointer,
157 _len: usize,
158 _ecx: &mut MiriInterpCx<'tcx>,
159 _finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
160 ) -> InterpResult<'tcx> {
161 throw_unsup_format!("cannot write to {}", self.name());
162 }
163
164 fn seek<'tcx>(
167 &self,
168 _communicate_allowed: bool,
169 _offset: SeekFrom,
170 ) -> InterpResult<'tcx, io::Result<u64>> {
171 throw_unsup_format!("cannot seek on {}", self.name());
172 }
173
174 fn close<'tcx>(
176 self,
177 _communicate_allowed: bool,
178 _ecx: &mut MiriInterpCx<'tcx>,
179 ) -> InterpResult<'tcx, io::Result<()>>
180 where
181 Self: Sized,
182 {
183 throw_unsup_format!("cannot close {}", self.name());
184 }
185
186 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
187 throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
188 }
189
190 fn is_tty(&self, _communicate_allowed: bool) -> bool {
191 false
194 }
195
196 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
197 panic!("Not a unix file descriptor: {}", self.name());
198 }
199}
200
201impl FileDescription for io::Stdin {
202 fn name(&self) -> &'static str {
203 "stdin"
204 }
205
206 fn read<'tcx>(
207 self: FileDescriptionRef<Self>,
208 communicate_allowed: bool,
209 ptr: Pointer,
210 len: usize,
211 ecx: &mut MiriInterpCx<'tcx>,
212 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
213 ) -> InterpResult<'tcx> {
214 if !communicate_allowed {
215 helpers::isolation_abort_error("`read` from stdin")?;
217 }
218
219 let result = ecx.read_from_host(&*self, len, ptr)?;
220 finish.call(ecx, result)
221 }
222
223 fn is_tty(&self, communicate_allowed: bool) -> bool {
224 communicate_allowed && self.is_terminal()
225 }
226}
227
228impl FileDescription for io::Stdout {
229 fn name(&self) -> &'static str {
230 "stdout"
231 }
232
233 fn write<'tcx>(
234 self: FileDescriptionRef<Self>,
235 _communicate_allowed: bool,
236 ptr: Pointer,
237 len: usize,
238 ecx: &mut MiriInterpCx<'tcx>,
239 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
240 ) -> InterpResult<'tcx> {
241 let result = ecx.write_to_host(&*self, len, ptr)?;
243 io::stdout().flush().unwrap();
249
250 finish.call(ecx, result)
251 }
252
253 fn is_tty(&self, communicate_allowed: bool) -> bool {
254 communicate_allowed && self.is_terminal()
255 }
256}
257
258impl FileDescription for io::Stderr {
259 fn name(&self) -> &'static str {
260 "stderr"
261 }
262
263 fn write<'tcx>(
264 self: FileDescriptionRef<Self>,
265 _communicate_allowed: bool,
266 ptr: Pointer,
267 len: usize,
268 ecx: &mut MiriInterpCx<'tcx>,
269 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
270 ) -> InterpResult<'tcx> {
271 let result = ecx.write_to_host(&*self, len, ptr)?;
273 finish.call(ecx, result)
275 }
276
277 fn is_tty(&self, communicate_allowed: bool) -> bool {
278 communicate_allowed && self.is_terminal()
279 }
280}
281
282#[derive(Debug)]
283pub struct FileHandle {
284 pub(crate) file: File,
285 pub(crate) writable: bool,
286}
287
288impl FileDescription for FileHandle {
289 fn name(&self) -> &'static str {
290 "file"
291 }
292
293 fn read<'tcx>(
294 self: FileDescriptionRef<Self>,
295 communicate_allowed: bool,
296 ptr: Pointer,
297 len: usize,
298 ecx: &mut MiriInterpCx<'tcx>,
299 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
300 ) -> InterpResult<'tcx> {
301 assert!(communicate_allowed, "isolation should have prevented even opening a file");
302
303 let result = ecx.read_from_host(&self.file, len, ptr)?;
304 finish.call(ecx, result)
305 }
306
307 fn write<'tcx>(
308 self: FileDescriptionRef<Self>,
309 communicate_allowed: bool,
310 ptr: Pointer,
311 len: usize,
312 ecx: &mut MiriInterpCx<'tcx>,
313 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
314 ) -> InterpResult<'tcx> {
315 assert!(communicate_allowed, "isolation should have prevented even opening a file");
316
317 let result = ecx.write_to_host(&self.file, len, ptr)?;
318 finish.call(ecx, result)
319 }
320
321 fn seek<'tcx>(
322 &self,
323 communicate_allowed: bool,
324 offset: SeekFrom,
325 ) -> InterpResult<'tcx, io::Result<u64>> {
326 assert!(communicate_allowed, "isolation should have prevented even opening a file");
327 interp_ok((&mut &self.file).seek(offset))
328 }
329
330 fn close<'tcx>(
331 self,
332 communicate_allowed: bool,
333 _ecx: &mut MiriInterpCx<'tcx>,
334 ) -> InterpResult<'tcx, io::Result<()>> {
335 assert!(communicate_allowed, "isolation should have prevented even opening a file");
336 if self.writable {
338 let result = self.file.sync_all();
341 drop(self.file);
343 interp_ok(result)
344 } else {
345 drop(self.file);
352 interp_ok(Ok(()))
353 }
354 }
355
356 fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
357 interp_ok(self.file.metadata())
358 }
359
360 fn is_tty(&self, communicate_allowed: bool) -> bool {
361 communicate_allowed && self.file.is_terminal()
362 }
363
364 fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
365 assert!(
366 ecx.target_os_is_unix(),
367 "unix file operations are only available for unix targets"
368 );
369 self
370 }
371}
372
373#[derive(Debug)]
375pub struct NullOutput;
376
377impl FileDescription for NullOutput {
378 fn name(&self) -> &'static str {
379 "stderr and stdout"
380 }
381
382 fn write<'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 finish.call(ecx, Ok(len))
392 }
393}
394
395pub type FdNum = i32;
397
398#[derive(Debug)]
400pub struct FdTable {
401 pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
402 next_file_description_id: FdId,
404}
405
406impl VisitProvenance for FdTable {
407 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
408 }
410}
411
412impl FdTable {
413 fn new() -> Self {
414 FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
415 }
416 pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
417 let mut fds = FdTable::new();
418 fds.insert_new(io::stdin());
419 if mute_stdout_stderr {
420 assert_eq!(fds.insert_new(NullOutput), 1);
421 assert_eq!(fds.insert_new(NullOutput), 2);
422 } else {
423 assert_eq!(fds.insert_new(io::stdout()), 1);
424 assert_eq!(fds.insert_new(io::stderr()), 2);
425 }
426 fds
427 }
428
429 pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
430 let file_handle =
431 FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
432 self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
433 file_handle
434 }
435
436 pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
438 let fd_ref = self.new_ref(fd);
439 self.insert(fd_ref)
440 }
441
442 pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
443 self.insert_with_min_num(fd_ref, 0)
444 }
445
446 pub fn insert_with_min_num(
448 &mut self,
449 file_handle: DynFileDescriptionRef,
450 min_fd_num: FdNum,
451 ) -> FdNum {
452 let candidate_new_fd =
457 self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
458 if *fd_num != counter {
459 Some(counter)
462 } else {
463 None
465 }
466 });
467 let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
468 self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
471 });
472
473 self.fds.try_insert(new_fd_num, file_handle).unwrap();
474 new_fd_num
475 }
476
477 pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
478 let fd = self.fds.get(&fd_num)?;
479 Some(fd.clone())
480 }
481
482 pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
483 self.fds.remove(&fd_num)
484 }
485
486 pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
487 self.fds.contains_key(&fd_num)
488 }
489}
490
491impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
492pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
493 fn read_from_host(
496 &mut self,
497 mut file: impl io::Read,
498 len: usize,
499 ptr: Pointer,
500 ) -> InterpResult<'tcx, Result<usize, IoError>> {
501 let this = self.eval_context_mut();
502
503 let mut bytes = vec![0; len];
504 let result = file.read(&mut bytes);
505 match result {
506 Ok(read_size) => {
507 this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
511 interp_ok(Ok(read_size))
512 }
513 Err(e) => interp_ok(Err(IoError::HostError(e))),
514 }
515 }
516
517 fn write_to_host(
519 &mut self,
520 mut file: impl io::Write,
521 len: usize,
522 ptr: Pointer,
523 ) -> InterpResult<'tcx, Result<usize, IoError>> {
524 let this = self.eval_context_mut();
525
526 let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
527 let result = file.write(bytes);
528 interp_ok(result.map_err(IoError::HostError))
529 }
530}