use std::any::Any;
use std::collections::BTreeMap;
use std::io::{IsTerminal, SeekFrom, Write};
use std::marker::CoercePointee;
use std::ops::Deref;
use std::rc::{Rc, Weak};
use std::{fs, io};
use rustc_abi::Size;
use crate::shims::unix::UnixFileDescription;
use crate::*;
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct FdId(usize);
#[derive(Debug, Clone)]
struct FdIdWith<T: ?Sized> {
id: FdId,
inner: T,
}
#[repr(transparent)]
#[derive(CoercePointee, Debug)]
pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
impl<T: ?Sized> Clone for FileDescriptionRef<T> {
fn clone(&self) -> Self {
FileDescriptionRef(self.0.clone())
}
}
impl<T: ?Sized> Deref for FileDescriptionRef<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0.inner
}
}
impl<T: ?Sized> FileDescriptionRef<T> {
pub fn id(&self) -> FdId {
self.0.id
}
}
#[derive(Debug)]
pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
impl<T: ?Sized> Clone for WeakFileDescriptionRef<T> {
fn clone(&self) -> Self {
WeakFileDescriptionRef(self.0.clone())
}
}
impl<T: ?Sized> FileDescriptionRef<T> {
pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
WeakFileDescriptionRef(Rc::downgrade(&this.0))
}
}
impl<T: ?Sized> WeakFileDescriptionRef<T> {
pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
self.0.upgrade().map(FileDescriptionRef)
}
}
impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
}
}
pub trait FileDescriptionExt: 'static {
fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
fn close_ref<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>>;
}
impl<T: FileDescription + 'static> FileDescriptionExt for T {
fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
self.0
}
fn close_ref<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
match Rc::into_inner(self.0) {
Some(fd) => {
ecx.machine.epoll_interests.remove(fd.id);
fd.inner.close(communicate_allowed, ecx)
}
None => {
interp_ok(Ok(()))
}
}
}
}
pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
impl FileDescriptionRef<dyn FileDescription> {
pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
Some(FileDescriptionRef(inner))
}
}
pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
fn name(&self) -> &'static str;
fn read<'tcx>(
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
_ptr: Pointer,
_len: usize,
_ecx: &mut MiriInterpCx<'tcx>,
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
throw_unsup_format!("cannot read from {}", self.name());
}
fn write<'tcx>(
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
_ptr: Pointer,
_len: usize,
_ecx: &mut MiriInterpCx<'tcx>,
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
throw_unsup_format!("cannot write to {}", self.name());
}
fn seek<'tcx>(
&self,
_communicate_allowed: bool,
_offset: SeekFrom,
) -> InterpResult<'tcx, io::Result<u64>> {
throw_unsup_format!("cannot seek on {}", self.name());
}
fn close<'tcx>(
self,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>>
where
Self: Sized,
{
throw_unsup_format!("cannot close {}", self.name());
}
fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<fs::Metadata>> {
throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors");
}
fn is_tty(&self, _communicate_allowed: bool) -> bool {
false
}
fn as_unix(&self) -> &dyn UnixFileDescription {
panic!("Not a unix file descriptor: {}", self.name());
}
}
impl FileDescription for io::Stdin {
fn name(&self) -> &'static str {
"stdin"
}
fn read<'tcx>(
self: FileDescriptionRef<Self>,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
if !communicate_allowed {
helpers::isolation_abort_error("`read` from stdin")?;
}
let result = ecx.read_from_host(&*self, len, ptr)?;
finish.call(ecx, result)
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescription for io::Stdout {
fn name(&self) -> &'static str {
"stdout"
}
fn write<'tcx>(
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
let result = ecx.write_to_host(&*self, len, ptr)?;
io::stdout().flush().unwrap();
finish.call(ecx, result)
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescription for io::Stderr {
fn name(&self) -> &'static str {
"stderr"
}
fn write<'tcx>(
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
let result = ecx.write_to_host(&*self, len, ptr)?;
finish.call(ecx, result)
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
#[derive(Debug)]
pub struct NullOutput;
impl FileDescription for NullOutput {
fn name(&self) -> &'static str {
"stderr and stdout"
}
fn write<'tcx>(
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
_ptr: Pointer,
len: usize,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
finish.call(ecx, Ok(len))
}
}
#[derive(Debug)]
pub struct FdTable {
pub fds: BTreeMap<i32, DynFileDescriptionRef>,
next_file_description_id: FdId,
}
impl VisitProvenance for FdTable {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
}
}
impl FdTable {
fn new() -> Self {
FdTable { fds: BTreeMap::new(), next_file_description_id: FdId(0) }
}
pub(crate) fn init(mute_stdout_stderr: bool) -> FdTable {
let mut fds = FdTable::new();
fds.insert_new(io::stdin());
if mute_stdout_stderr {
assert_eq!(fds.insert_new(NullOutput), 1);
assert_eq!(fds.insert_new(NullOutput), 2);
} else {
assert_eq!(fds.insert_new(io::stdout()), 1);
assert_eq!(fds.insert_new(io::stderr()), 2);
}
fds
}
pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
let file_handle =
FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
file_handle
}
pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
let fd_ref = self.new_ref(fd);
self.insert(fd_ref)
}
pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
self.insert_with_min_num(fd_ref, 0)
}
pub fn insert_with_min_num(
&mut self,
file_handle: DynFileDescriptionRef,
min_fd_num: i32,
) -> i32 {
let candidate_new_fd =
self.fds.range(min_fd_num..).zip(min_fd_num..).find_map(|((fd_num, _fd), counter)| {
if *fd_num != counter {
Some(counter)
} else {
None
}
});
let new_fd_num = candidate_new_fd.unwrap_or_else(|| {
self.fds.last_key_value().map(|(fd_num, _)| fd_num.strict_add(1)).unwrap_or(min_fd_num)
});
self.fds.try_insert(new_fd_num, file_handle).unwrap();
new_fd_num
}
pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
let fd = self.fds.get(&fd_num)?;
Some(fd.clone())
}
pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
self.fds.remove(&fd_num)
}
pub fn is_fd_num(&self, fd_num: i32) -> bool {
self.fds.contains_key(&fd_num)
}
}
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
fn read_from_host(
&mut self,
mut file: impl io::Read,
len: usize,
ptr: Pointer,
) -> InterpResult<'tcx, Result<usize, IoError>> {
let this = self.eval_context_mut();
let mut bytes = vec![0; len];
let result = file.read(&mut bytes);
match result {
Ok(read_size) => {
this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
interp_ok(Ok(read_size))
}
Err(e) => interp_ok(Err(IoError::HostError(e))),
}
}
fn write_to_host(
&mut self,
mut file: impl io::Write,
len: usize,
ptr: Pointer,
) -> InterpResult<'tcx, Result<usize, IoError>> {
let this = self.eval_context_mut();
let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
let result = file.write(bytes);
interp_ok(result.map_err(IoError::HostError))
}
}