use std::any::Any;
use std::collections::BTreeMap;
use std::io::{self, ErrorKind, IsTerminal, Read, SeekFrom, Write};
use std::ops::Deref;
use std::rc::{Rc, Weak};
use rustc_abi::Size;
use crate::helpers::check_min_arg_count;
use crate::shims::unix::linux::epoll::EpollReadyEvents;
use crate::shims::unix::*;
use crate::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum FlockOp {
SharedLock { nonblocking: bool },
ExclusiveLock { nonblocking: bool },
Unlock,
}
pub trait FileDescription: std::fmt::Debug + Any {
fn name(&self) -> &'static str;
fn read<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
_ptr: Pointer,
_len: usize,
_dest: &MPlaceTy<'tcx>,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
throw_unsup_format!("cannot read from {}", self.name());
}
fn write<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
_ptr: Pointer,
_len: usize,
_dest: &MPlaceTy<'tcx>,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
throw_unsup_format!("cannot write to {}", self.name());
}
fn pread<'tcx>(
&self,
_communicate_allowed: bool,
_offset: u64,
_ptr: Pointer,
_len: usize,
_dest: &MPlaceTy<'tcx>,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
throw_unsup_format!("cannot pread from {}", self.name());
}
fn pwrite<'tcx>(
&self,
_communicate_allowed: bool,
_ptr: Pointer,
_len: usize,
_offset: u64,
_dest: &MPlaceTy<'tcx>,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
throw_unsup_format!("cannot pwrite 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: Box<Self>,
_communicate_allowed: bool,
_ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
throw_unsup_format!("cannot close {}", self.name());
}
fn flock<'tcx>(
&self,
_communicate_allowed: bool,
_op: FlockOp,
) -> InterpResult<'tcx, io::Result<()>> {
throw_unsup_format!("cannot flock {}", self.name());
}
fn is_tty(&self, _communicate_allowed: bool) -> bool {
false
}
fn get_epoll_ready_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadyEvents> {
throw_unsup_format!("{}: epoll does not support this file description", self.name());
}
}
impl dyn FileDescription {
#[inline(always)]
pub fn downcast<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
}
impl FileDescription for io::Stdin {
fn name(&self) -> &'static str {
"stdin"
}
fn read<'tcx>(
&self,
_self_ref: &FileDescriptionRef,
communicate_allowed: bool,
ptr: Pointer,
len: usize,
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
let mut bytes = vec![0; len];
if !communicate_allowed {
helpers::isolation_abort_error("`read` from stdin")?;
}
let result = Read::read(&mut { self }, &mut bytes);
match result {
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
}
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,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
let result = Write::write(&mut { self }, bytes);
io::stdout().flush().unwrap();
match result {
Ok(write_size) => ecx.return_write_success(write_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
}
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,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
ptr: Pointer,
len: usize,
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
let result = Write::write(&mut { self }, bytes);
match result {
Ok(write_size) => ecx.return_write_success(write_size, dest),
Err(e) => ecx.set_last_error_and_return(e, dest),
}
}
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,
_self_ref: &FileDescriptionRef,
_communicate_allowed: bool,
_ptr: Pointer,
len: usize,
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
ecx.return_write_success(len, dest)
}
}
#[derive(Clone, Debug)]
pub struct FileDescWithId<T: FileDescription + ?Sized> {
id: FdId,
file_description: Box<T>,
}
#[derive(Clone, Debug)]
pub struct FileDescriptionRef(Rc<FileDescWithId<dyn FileDescription>>);
impl Deref for FileDescriptionRef {
type Target = dyn FileDescription;
fn deref(&self) -> &Self::Target {
&*self.0.file_description
}
}
impl FileDescriptionRef {
fn new(fd: impl FileDescription, id: FdId) -> Self {
FileDescriptionRef(Rc::new(FileDescWithId { id, file_description: Box::new(fd) }))
}
pub fn close<'tcx>(
self,
communicate_allowed: bool,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx, io::Result<()>> {
let id = self.get_id();
match Rc::into_inner(self.0) {
Some(fd) => {
ecx.machine.epoll_interests.remove(id);
fd.file_description.close(communicate_allowed, ecx)
}
None => interp_ok(Ok(())),
}
}
pub fn downgrade(&self) -> WeakFileDescriptionRef {
WeakFileDescriptionRef { weak_ref: Rc::downgrade(&self.0) }
}
pub fn get_id(&self) -> FdId {
self.0.id
}
}
#[derive(Clone, Debug, Default)]
pub struct WeakFileDescriptionRef {
weak_ref: Weak<FileDescWithId<dyn FileDescription>>,
}
impl WeakFileDescriptionRef {
pub fn upgrade(&self) -> Option<FileDescriptionRef> {
if let Some(file_desc_with_id) = self.weak_ref.upgrade() {
return Some(FileDescriptionRef(file_desc_with_id));
}
None
}
}
impl VisitProvenance for WeakFileDescriptionRef {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
}
}
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct FdId(usize);
#[derive(Debug)]
pub struct FdTable {
pub fds: BTreeMap<i32, FileDescriptionRef>,
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(&mut self, fd: impl FileDescription) -> FileDescriptionRef {
let file_handle = FileDescriptionRef::new(fd, self.next_file_description_id);
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: FileDescriptionRef) -> i32 {
self.insert_with_min_num(fd_ref, 0)
}
fn insert_with_min_num(&mut self, file_handle: FileDescriptionRef, 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<FileDescriptionRef> {
let fd = self.fds.get(&fd_num)?;
Some(fd.clone())
}
pub fn remove(&mut self, fd_num: i32) -> Option<FileDescriptionRef> {
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 dup(&mut self, old_fd_num: i32) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let Some(fd) = this.machine.fds.get(old_fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
interp_ok(Scalar::from_i32(this.machine.fds.insert(fd)))
}
fn dup2(&mut self, old_fd_num: i32, new_fd_num: i32) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let Some(fd) = this.machine.fds.get(old_fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
if new_fd_num != old_fd_num {
if let Some(old_new_fd) = this.machine.fds.fds.insert(new_fd_num, fd) {
old_new_fd.close(this.machine.communicate(), this)?.ok();
}
}
interp_ok(Scalar::from_i32(new_fd_num))
}
fn flock(&mut self, fd_num: i32, op: i32) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
let lock_sh = this.eval_libc_i32("LOCK_SH");
let lock_ex = this.eval_libc_i32("LOCK_EX");
let lock_nb = this.eval_libc_i32("LOCK_NB");
let lock_un = this.eval_libc_i32("LOCK_UN");
use FlockOp::*;
let parsed_op = if op == lock_sh {
SharedLock { nonblocking: false }
} else if op == lock_sh | lock_nb {
SharedLock { nonblocking: true }
} else if op == lock_ex {
ExclusiveLock { nonblocking: false }
} else if op == lock_ex | lock_nb {
ExclusiveLock { nonblocking: true }
} else if op == lock_un {
Unlock
} else {
throw_unsup_format!("unsupported flags {:#x}", op);
};
let result = fd.flock(this.machine.communicate(), parsed_op)?;
drop(fd);
let result = result.map(|()| 0i32);
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
}
fn fcntl(&mut self, args: &[OpTy<'tcx>]) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let [fd_num, cmd] = check_min_arg_count("fcntl", args)?;
let fd_num = this.read_scalar(fd_num)?.to_i32()?;
let cmd = this.read_scalar(cmd)?.to_i32()?;
let f_getfd = this.eval_libc_i32("F_GETFD");
let f_dupfd = this.eval_libc_i32("F_DUPFD");
let f_dupfd_cloexec = this.eval_libc_i32("F_DUPFD_CLOEXEC");
match cmd {
cmd if cmd == f_getfd => {
if !this.machine.fds.is_fd_num(fd_num) {
this.set_last_error_and_return_i32(LibcError("EBADF"))
} else {
interp_ok(this.eval_libc("FD_CLOEXEC"))
}
}
cmd if cmd == f_dupfd || cmd == f_dupfd_cloexec => {
let cmd_name = if cmd == f_dupfd {
"fcntl(fd, F_DUPFD, ...)"
} else {
"fcntl(fd, F_DUPFD_CLOEXEC, ...)"
};
let [_, _, start] = check_min_arg_count(cmd_name, args)?;
let start = this.read_scalar(start)?.to_i32()?;
if let Some(fd) = this.machine.fds.get(fd_num) {
interp_ok(Scalar::from_i32(this.machine.fds.insert_with_min_num(fd, start)))
} else {
this.set_last_error_and_return_i32(LibcError("EBADF"))
}
}
cmd if this.tcx.sess.target.os == "macos"
&& cmd == this.eval_libc_i32("F_FULLFSYNC") =>
{
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fcntl`", reject_with)?;
return this.set_last_error_and_return_i32(ErrorKind::PermissionDenied);
}
this.ffullsync_fd(fd_num)
}
cmd => {
throw_unsup_format!("fcntl: unsupported command {cmd:#x}");
}
}
}
fn close(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();
let fd_num = this.read_scalar(fd_op)?.to_i32()?;
let Some(fd) = this.machine.fds.remove(fd_num) else {
return this.set_last_error_and_return_i32(LibcError("EBADF"));
};
let result = fd.close(this.machine.communicate(), this)?;
let result = result.map(|()| 0i32);
interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
}
fn read(
&mut self,
fd_num: i32,
buf: Pointer,
count: u64,
offset: Option<i128>,
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
trace!("Reading from FD {}, size {}", fd_num, count);
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let count = usize::try_from(count).unwrap(); let communicate = this.machine.communicate();
let Some(fd) = this.machine.fds.get(fd_num) else {
trace!("read: FD not found");
return this.set_last_error_and_return(LibcError("EBADF"), dest);
};
trace!("read: FD mapped to {fd:?}");
match offset {
None => fd.read(&fd, communicate, buf, count, dest, this)?,
Some(offset) => {
let Ok(offset) = u64::try_from(offset) else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
};
fd.pread(communicate, offset, buf, count, dest, this)?
}
};
interp_ok(())
}
fn write(
&mut self,
fd_num: i32,
buf: Pointer,
count: u64,
offset: Option<i128>,
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let count = usize::try_from(count).unwrap(); let communicate = this.machine.communicate();
let Some(fd) = this.machine.fds.get(fd_num) else {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
};
match offset {
None => fd.write(&fd, communicate, buf, count, dest, this)?,
Some(offset) => {
let Ok(offset) = u64::try_from(offset) else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
};
fd.pwrite(communicate, buf, count, offset, dest, this)?
}
};
interp_ok(())
}
fn return_read_success(
&mut self,
buf: Pointer,
bytes: &[u8],
actual_read_size: usize,
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.write_bytes_ptr(buf, bytes[..actual_read_size].iter().copied())?;
this.write_int(u64::try_from(actual_read_size).unwrap(), dest)?;
interp_ok(())
}
fn return_write_success(
&mut self,
actual_write_size: usize,
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
this.write_int(u64::try_from(actual_write_size).unwrap(), dest)?;
interp_ok(())
}
}