shims/unix: split general FD management from FS access; make a place for sockets

This commit is contained in:
Ralf Jung 2024-04-03 08:56:29 +02:00
parent 4397b6b00c
commit 229d41731a
12 changed files with 663 additions and 633 deletions

View File

@ -31,7 +31,7 @@ use rustc_target::spec::abi::Abi;
use crate::{ use crate::{
concurrency::{data_race, weak_memory}, concurrency::{data_race, weak_memory},
shims::unix::FileHandler, shims::unix::FdTable,
*, *,
}; };
@ -463,9 +463,9 @@ pub struct MiriMachine<'mir, 'tcx> {
pub(crate) validate: bool, pub(crate) validate: bool,
/// The table of file descriptors. /// The table of file descriptors.
pub(crate) file_handler: shims::unix::FileHandler, pub(crate) fds: shims::unix::FdTable,
/// The table of directory descriptors. /// The table of directory descriptors.
pub(crate) dir_handler: shims::unix::DirHandler, pub(crate) dirs: shims::unix::DirTable,
/// This machine's monotone clock. /// This machine's monotone clock.
pub(crate) clock: Clock, pub(crate) clock: Clock,
@ -640,8 +640,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
tls: TlsData::default(), tls: TlsData::default(),
isolated_op: config.isolated_op, isolated_op: config.isolated_op,
validate: config.validate, validate: config.validate,
file_handler: FileHandler::new(config.mute_stdout_stderr), fds: FdTable::new(config.mute_stdout_stderr),
dir_handler: Default::default(), dirs: Default::default(),
layouts, layouts,
threads: ThreadManager::default(), threads: ThreadManager::default(),
static_roots: Vec::new(), static_roots: Vec::new(),
@ -774,11 +774,11 @@ impl VisitProvenance for MiriMachine<'_, '_> {
argv, argv,
cmd_line, cmd_line,
extern_statics, extern_statics,
dir_handler, dirs,
borrow_tracker, borrow_tracker,
data_race, data_race,
alloc_addresses, alloc_addresses,
file_handler, fds,
tcx: _, tcx: _,
isolated_op: _, isolated_op: _,
validate: _, validate: _,
@ -817,8 +817,8 @@ impl VisitProvenance for MiriMachine<'_, '_> {
threads.visit_provenance(visit); threads.visit_provenance(visit);
tls.visit_provenance(visit); tls.visit_provenance(visit);
env_vars.visit_provenance(visit); env_vars.visit_provenance(visit);
dir_handler.visit_provenance(visit); dirs.visit_provenance(visit);
file_handler.visit_provenance(visit); fds.visit_provenance(visit);
data_race.visit_provenance(visit); data_race.visit_provenance(visit);
borrow_tracker.visit_provenance(visit); borrow_tracker.visit_provenance(visit);
alloc_addresses.visit_provenance(visit); alloc_addresses.visit_provenance(visit);

View File

@ -0,0 +1,429 @@
//! General management of file descriptors, and support for
//! standard file descriptors (stdin/stdout/stderr).
use std::any::Any;
use std::collections::BTreeMap;
use std::io::{self, ErrorKind, IsTerminal, Read, SeekFrom, Write};
use rustc_middle::ty::TyCtxt;
use rustc_target::abi::Size;
use crate::shims::unix::*;
use crate::*;
/// Represents an open file descriptor.
pub trait FileDescriptor: std::fmt::Debug + Any {
fn name(&self) -> &'static str;
fn read<'tcx>(
&mut self,
_communicate_allowed: bool,
_bytes: &mut [u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
throw_unsup_format!("cannot read from {}", self.name());
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
_bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
throw_unsup_format!("cannot write to {}", self.name());
}
fn seek<'tcx>(
&mut 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,
) -> InterpResult<'tcx, io::Result<i32>> {
throw_unsup_format!("cannot close {}", self.name());
}
/// Return a new file descriptor *that refers to the same underlying object*.
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
fn is_tty(&self, _communicate_allowed: bool) -> bool {
false
}
}
impl dyn FileDescriptor {
#[inline(always)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
#[inline(always)]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
(self as &mut dyn Any).downcast_mut()
}
}
impl FileDescriptor for io::Stdin {
fn name(&self) -> &'static str {
"stdin"
}
fn read<'tcx>(
&mut self,
communicate_allowed: bool,
bytes: &mut [u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
if !communicate_allowed {
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
helpers::isolation_abort_error("`read` from stdin")?;
}
Ok(Read::read(self, bytes))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stdin()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescriptor for io::Stdout {
fn name(&self) -> &'static str {
"stdout"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
let result = Write::write(&mut { self }, bytes);
// Stdout is buffered, flush to make sure it appears on the
// screen. This is the write() syscall of the interpreted
// program, we want it to correspond to a write() syscall on
// the host -- there is no good in adding extra buffering
// here.
io::stdout().flush().unwrap();
Ok(result)
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stdout()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescriptor for io::Stderr {
fn name(&self) -> &'static str {
"stderr"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
// No need to flush, stderr is not buffered.
Ok(Write::write(&mut { self }, bytes))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stderr()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
/// Like /dev/null
#[derive(Debug)]
pub struct NullOutput;
impl FileDescriptor for NullOutput {
fn name(&self) -> &'static str {
"stderr and stdout"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We just don't write anything, but report to the user that we did.
Ok(Ok(bytes.len()))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(NullOutput))
}
}
/// The file descriptor table
#[derive(Debug)]
pub struct FdTable {
pub fds: BTreeMap<i32, Box<dyn FileDescriptor>>,
}
impl VisitProvenance for FdTable {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
// All our FileDescriptor do not have any tags.
}
}
impl FdTable {
pub(crate) fn new(mute_stdout_stderr: bool) -> FdTable {
let mut fds: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
fds.insert(0i32, Box::new(io::stdin()));
if mute_stdout_stderr {
fds.insert(1i32, Box::new(NullOutput));
fds.insert(2i32, Box::new(NullOutput));
} else {
fds.insert(1i32, Box::new(io::stdout()));
fds.insert(2i32, Box::new(io::stderr()));
}
FdTable { fds }
}
pub fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
self.insert_fd_with_min_fd(file_handle, 0)
}
/// Insert a new FD that is at least `min_fd`.
pub fn insert_fd_with_min_fd(
&mut self,
file_handle: Box<dyn FileDescriptor>,
min_fd: i32,
) -> i32 {
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
// between used FDs, the find_map combinator will return it. If the first such unused FD
// is after all other used FDs, the find_map combinator will return None, and we will use
// the FD following the greatest FD thus far.
let candidate_new_fd =
self.fds.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
if *fd != counter {
// There was a gap in the fds stored, return the first unused one
// (note that this relies on BTreeMap iterating in key order)
Some(counter)
} else {
// This fd is used, keep going
None
}
});
let new_fd = candidate_new_fd.unwrap_or_else(|| {
// find_map ran out of BTreeMap entries before finding a free fd, use one plus the
// maximum fd in the map
self.fds.last_key_value().map(|(fd, _)| fd.checked_add(1).unwrap()).unwrap_or(min_fd)
});
self.fds.try_insert(new_fd, file_handle).unwrap();
new_fd
}
pub fn get(&self, fd: i32) -> Option<&dyn FileDescriptor> {
Some(&**self.fds.get(&fd)?)
}
pub fn get_mut(&mut self, fd: i32) -> Option<&mut dyn FileDescriptor> {
Some(&mut **self.fds.get_mut(&fd)?)
}
pub fn remove(&mut self, fd: i32) -> Option<Box<dyn FileDescriptor>> {
self.fds.remove(&fd)
}
pub fn is_fd(&self, fd: i32) -> bool {
self.fds.contains_key(&fd)
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
if args.len() < 2 {
throw_ub_format!(
"incorrect number of arguments for fcntl: got {}, expected at least 2",
args.len()
);
}
let fd = this.read_scalar(&args[0])?.to_i32()?;
let cmd = this.read_scalar(&args[1])?.to_i32()?;
// We only support getting the flags for a descriptor.
if cmd == this.eval_libc_i32("F_GETFD") {
// Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
// `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
// always sets this flag when opening a file. However we still need to check that the
// file itself is open.
if this.machine.fds.is_fd(fd) {
Ok(this.eval_libc_i32("FD_CLOEXEC"))
} else {
this.fd_not_found()
}
} else if cmd == this.eval_libc_i32("F_DUPFD")
|| cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")
{
// Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
// because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
// differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
// thus they can share the same implementation here.
if args.len() < 3 {
throw_ub_format!(
"incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
args.len()
);
}
let start = this.read_scalar(&args[2])?.to_i32()?;
match this.machine.fds.get_mut(fd) {
Some(file_descriptor) => {
let dup_result = file_descriptor.dup();
match dup_result {
Ok(dup_fd) => Ok(this.machine.fds.insert_fd_with_min_fd(dup_fd, start)),
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(-1)
}
}
}
None => this.fd_not_found(),
}
} else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC") {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fcntl`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
return Ok(-1);
}
this.ffullsync_fd(fd)
} else {
throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
}
}
fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let fd = this.read_scalar(fd_op)?.to_i32()?;
Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.remove(fd) {
let result = file_descriptor.close(this.machine.communicate())?;
this.try_unwrap_io_result(result)?
} else {
this.fd_not_found()?
}))
}
/// Function used when a file descriptor does not exist. It returns `Ok(-1)`and sets
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
/// types (like `read`, that returns an `i64`).
fn fd_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
let this = self.eval_context_mut();
let ebadf = this.eval_libc("EBADF");
this.set_last_error(ebadf)?;
Ok((-1).into())
}
fn read(
&mut self,
fd: i32,
buf: Pointer<Option<Provenance>>,
count: u64,
) -> InterpResult<'tcx, i64> {
let this = self.eval_context_mut();
// Isolation check is done via `FileDescriptor` trait.
trace!("Reading from FD {}, size {}", fd, count);
// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
// We cap the number of read bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let communicate = this.machine.communicate();
if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
trace!("read: FD mapped to {:?}", file_descriptor);
// We want to read at most `count` bytes. We are sure that `count` is not negative
// because it was a target's `usize`. Also we are sure that its smaller than
// `usize::MAX` because it is bounded by the host's `isize`.
let mut bytes = vec![0; usize::try_from(count).unwrap()];
// `File::read` never returns a value larger than `count`,
// so this cannot fail.
let result = file_descriptor
.read(communicate, &mut bytes, *this.tcx)?
.map(|c| i64::try_from(c).unwrap());
match result {
Ok(read_bytes) => {
// If reading to `bytes` did not fail, we write those bytes to the buffer.
this.write_bytes_ptr(buf, bytes)?;
Ok(read_bytes)
}
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(-1)
}
}
} else {
trace!("read: FD not found");
this.fd_not_found()
}
}
fn write(
&mut self,
fd: i32,
buf: Pointer<Option<Provenance>>,
count: u64,
) -> InterpResult<'tcx, i64> {
let this = self.eval_context_mut();
// Isolation check is done via `FileDescriptor` trait.
// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
// We cap the number of written bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let communicate = this.machine.communicate();
if let Some(file_descriptor) = this.machine.fds.get(fd) {
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
let result = file_descriptor
.write(communicate, bytes, *this.tcx)?
.map(|c| i64::try_from(c).unwrap());
this.try_unwrap_io_result(result)
} else {
this.fd_not_found()
}
}
}

View File

@ -8,8 +8,10 @@ use rustc_target::spec::abi::Abi;
use crate::*; use crate::*;
use shims::foreign_items::EmulateForeignItemResult; use shims::foreign_items::EmulateForeignItemResult;
use shims::unix::fd::EvalContextExt as _;
use shims::unix::fs::EvalContextExt as _; use shims::unix::fs::EvalContextExt as _;
use shims::unix::mem::EvalContextExt as _; use shims::unix::mem::EvalContextExt as _;
use shims::unix::socket::EvalContextExt as _;
use shims::unix::sync::EvalContextExt as _; use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _; use shims::unix::thread::EvalContextExt as _;
@ -51,7 +53,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern.
#[rustfmt::skip] #[rustfmt::skip]
match link_name.as_str() { match link_name.as_str() {
// Environment related shims // Environment variables
"getenv" => { "getenv" => {
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.getenv(name)?; let result = this.getenv(name)?;
@ -79,25 +81,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.write_scalar(Scalar::from_i32(result), dest)?; this.write_scalar(Scalar::from_i32(result), dest)?;
} }
// File related shims // File descriptors
"open" | "open64" => {
// `open` is variadic, the third argument is only present when the second argument has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
let result = this.open(args)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"close" => {
let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.close(fd)?;
this.write_scalar(result, dest)?;
}
"fcntl" => {
// `fcntl` is variadic. The argument count is checked based on the first argument
// in `this.fcntl()`, so we do not use `check_shim` here.
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
let result = this.fcntl(args)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"read" => { "read" => {
let [fd, buf, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [fd, buf, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let fd = this.read_scalar(fd)?.to_i32()?; let fd = this.read_scalar(fd)?.to_i32()?;
@ -116,6 +100,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Now, `result` is the value we return back to the program. // Now, `result` is the value we return back to the program.
this.write_scalar(Scalar::from_target_isize(result, this), dest)?; this.write_scalar(Scalar::from_target_isize(result, this), dest)?;
} }
"close" => {
let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.close(fd)?;
this.write_scalar(result, dest)?;
}
"fcntl" => {
// `fcntl` is variadic. The argument count is checked based on the first argument
// in `this.fcntl()`, so we do not use `check_shim` here.
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
let result = this.fcntl(args)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
// File and file system access
"open" | "open64" => {
// `open` is variadic, the third argument is only present when the second argument has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
let result = this.open(args)?;
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"unlink" => { "unlink" => {
let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.unlink(path)?; let result = this.unlink(path)?;
@ -219,7 +223,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
this.write_scalar(Scalar::from_i32(result), dest)?; this.write_scalar(Scalar::from_i32(result), dest)?;
} }
// Time related shims // Sockets
"socketpair" => {
let [domain, type_, protocol, sv] =
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.socketpair(domain, type_, protocol, sv)?;
this.write_scalar(result, dest)?;
}
// Time
"gettimeofday" => { "gettimeofday" => {
let [tv, tz] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let [tv, tz] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let result = this.gettimeofday(tv, tz)?; let result = this.gettimeofday(tv, tz)?;
@ -598,7 +611,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if bufsize > 256 { if bufsize > 256 {
let err = this.eval_libc("EIO"); let err = this.eval_libc("EIO");
this.set_last_error(err)?; this.set_last_error(err)?;
this.write_scalar(Scalar::from_i32(-1), dest)? this.write_scalar(Scalar::from_i32(-1), dest)?;
} else { } else {
this.gen_random(buf, bufsize)?; this.gen_random(buf, bufsize)?;
this.write_scalar(Scalar::from_i32(0), dest)?; this.write_scalar(Scalar::from_i32(0), dest)?;

View File

@ -1,6 +1,6 @@
use std::any::Any; //! File and file system access
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fs::{ use std::fs::{
read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
}; };
@ -13,70 +13,16 @@ use rustc_middle::ty::TyCtxt;
use rustc_target::abi::Size; use rustc_target::abi::Size;
use crate::shims::os_str::bytes_to_os_str; use crate::shims::os_str::bytes_to_os_str;
use crate::shims::unix::*;
use crate::*; use crate::*;
use shims::time::system_time_to_duration; use shims::time::system_time_to_duration;
#[derive(Debug)] #[derive(Debug)]
pub struct FileHandle { struct FileHandle {
file: File, file: File,
writable: bool, writable: bool,
} }
pub trait FileDescriptor: std::fmt::Debug + Any {
fn name(&self) -> &'static str;
fn read<'tcx>(
&mut self,
_communicate_allowed: bool,
_bytes: &mut [u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
throw_unsup_format!("cannot read from {}", self.name());
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
_bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
throw_unsup_format!("cannot write to {}", self.name());
}
fn seek<'tcx>(
&mut 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,
) -> InterpResult<'tcx, io::Result<i32>> {
throw_unsup_format!("cannot close {}", self.name());
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>>;
fn is_tty(&self, _communicate_allowed: bool) -> bool {
false
}
}
impl dyn FileDescriptor {
#[inline(always)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref()
}
#[inline(always)]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
(self as &mut dyn Any).downcast_mut()
}
}
impl FileDescriptor for FileHandle { impl FileDescriptor for FileHandle {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"FILE" "FILE"
@ -147,172 +93,6 @@ impl FileDescriptor for FileHandle {
} }
} }
impl FileDescriptor for io::Stdin {
fn name(&self) -> &'static str {
"stdin"
}
fn read<'tcx>(
&mut self,
communicate_allowed: bool,
bytes: &mut [u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
if !communicate_allowed {
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
helpers::isolation_abort_error("`read` from stdin")?;
}
Ok(Read::read(self, bytes))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stdin()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescriptor for io::Stdout {
fn name(&self) -> &'static str {
"stdout"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
let result = Write::write(&mut { self }, bytes);
// Stdout is buffered, flush to make sure it appears on the
// screen. This is the write() syscall of the interpreted
// program, we want it to correspond to a write() syscall on
// the host -- there is no good in adding extra buffering
// here.
io::stdout().flush().unwrap();
Ok(result)
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stdout()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
impl FileDescriptor for io::Stderr {
fn name(&self) -> &'static str {
"stderr"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We allow writing to stderr even with isolation enabled.
// No need to flush, stderr is not buffered.
Ok(Write::write(&mut { self }, bytes))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(io::stderr()))
}
fn is_tty(&self, communicate_allowed: bool) -> bool {
communicate_allowed && self.is_terminal()
}
}
#[derive(Debug)]
struct NullOutput;
impl FileDescriptor for NullOutput {
fn name(&self) -> &'static str {
"stderr and stdout"
}
fn write<'tcx>(
&self,
_communicate_allowed: bool,
bytes: &[u8],
_tcx: TyCtxt<'tcx>,
) -> InterpResult<'tcx, io::Result<usize>> {
// We just don't write anything, but report to the user that we did.
Ok(Ok(bytes.len()))
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(NullOutput))
}
}
#[derive(Debug)]
pub struct FileHandler {
pub handles: BTreeMap<i32, Box<dyn FileDescriptor>>,
}
impl VisitProvenance for FileHandler {
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
// All our FileDescriptor do not have any tags.
}
}
impl FileHandler {
pub(crate) fn new(mute_stdout_stderr: bool) -> FileHandler {
let mut handles: BTreeMap<_, Box<dyn FileDescriptor>> = BTreeMap::new();
handles.insert(0i32, Box::new(io::stdin()));
if mute_stdout_stderr {
handles.insert(1i32, Box::new(NullOutput));
handles.insert(2i32, Box::new(NullOutput));
} else {
handles.insert(1i32, Box::new(io::stdout()));
handles.insert(2i32, Box::new(io::stderr()));
}
FileHandler { handles }
}
pub fn insert_fd(&mut self, file_handle: Box<dyn FileDescriptor>) -> i32 {
self.insert_fd_with_min_fd(file_handle, 0)
}
fn insert_fd_with_min_fd(&mut self, file_handle: Box<dyn FileDescriptor>, min_fd: i32) -> i32 {
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
// between used FDs, the find_map combinator will return it. If the first such unused FD
// is after all other used FDs, the find_map combinator will return None, and we will use
// the FD following the greatest FD thus far.
let candidate_new_fd =
self.handles.range(min_fd..).zip(min_fd..).find_map(|((fd, _fh), counter)| {
if *fd != counter {
// There was a gap in the fds stored, return the first unused one
// (note that this relies on BTreeMap iterating in key order)
Some(counter)
} else {
// This fd is used, keep going
None
}
});
let new_fd = candidate_new_fd.unwrap_or_else(|| {
// find_map ran out of BTreeMap entries before finding a free fd, use one plus the
// maximum fd in the map
self.handles
.last_key_value()
.map(|(fd, _)| fd.checked_add(1).unwrap())
.unwrap_or(min_fd)
});
self.handles.try_insert(new_fd, file_handle).unwrap();
new_fd
}
}
impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn macos_stat_write_buf( fn macos_stat_write_buf(
@ -411,10 +191,11 @@ trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx
/// An open directory, tracked by DirHandler. /// An open directory, tracked by DirHandler.
#[derive(Debug)] #[derive(Debug)]
pub struct OpenDir { struct OpenDir {
/// The directory reader on the host. /// The directory reader on the host.
read_dir: ReadDir, read_dir: ReadDir,
/// The most recent entry returned by readdir() /// The most recent entry returned by readdir().
/// Will be freed by the next call.
entry: Pointer<Option<Provenance>>, entry: Pointer<Option<Provenance>>,
} }
@ -425,8 +206,11 @@ impl OpenDir {
} }
} }
/// The table of open directories.
/// Curiously, Unix/POSIX does not unify this into the "file descriptor" concept... everything
/// is a file, except a directory is not?
#[derive(Debug)] #[derive(Debug)]
pub struct DirHandler { pub struct DirTable {
/// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir, /// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
/// and closedir. /// and closedir.
/// ///
@ -441,7 +225,7 @@ pub struct DirHandler {
next_id: u64, next_id: u64,
} }
impl DirHandler { impl DirTable {
#[allow(clippy::arithmetic_side_effects)] #[allow(clippy::arithmetic_side_effects)]
fn insert_new(&mut self, read_dir: ReadDir) -> u64 { fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
let id = self.next_id; let id = self.next_id;
@ -451,9 +235,9 @@ impl DirHandler {
} }
} }
impl Default for DirHandler { impl Default for DirTable {
fn default() -> DirHandler { fn default() -> DirTable {
DirHandler { DirTable {
streams: FxHashMap::default(), streams: FxHashMap::default(),
// Skip 0 as an ID, because it looks like a null pointer to libc // Skip 0 as an ID, because it looks like a null pointer to libc
next_id: 1, next_id: 1,
@ -461,9 +245,9 @@ impl Default for DirHandler {
} }
} }
impl VisitProvenance for DirHandler { impl VisitProvenance for DirTable {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) { fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let DirHandler { streams, next_id: _ } = self; let DirTable { streams, next_id: _ } = self;
for dir in streams.values() { for dir in streams.values() {
dir.entry.visit_provenance(visit); dir.entry.visit_provenance(visit);
@ -615,200 +399,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
} }
let fd = options.open(path).map(|file| { let fd = options.open(path).map(|file| {
let fh = &mut this.machine.file_handler; let fh = &mut this.machine.fds;
fh.insert_fd(Box::new(FileHandle { file, writable })) fh.insert_fd(Box::new(FileHandle { file, writable }))
}); });
this.try_unwrap_io_result(fd) this.try_unwrap_io_result(fd)
} }
fn fcntl(&mut self, args: &[OpTy<'tcx, Provenance>]) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
if args.len() < 2 {
throw_ub_format!(
"incorrect number of arguments for fcntl: got {}, expected at least 2",
args.len()
);
}
let fd = this.read_scalar(&args[0])?.to_i32()?;
let cmd = this.read_scalar(&args[1])?.to_i32()?;
// We only support getting the flags for a descriptor.
if cmd == this.eval_libc_i32("F_GETFD") {
// Currently this is the only flag that `F_GETFD` returns. It is OK to just return the
// `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
// always sets this flag when opening a file. However we still need to check that the
// file itself is open.
if this.machine.file_handler.handles.contains_key(&fd) {
Ok(this.eval_libc_i32("FD_CLOEXEC"))
} else {
this.handle_not_found()
}
} else if cmd == this.eval_libc_i32("F_DUPFD")
|| cmd == this.eval_libc_i32("F_DUPFD_CLOEXEC")
{
// Note that we always assume the FD_CLOEXEC flag is set for every open file, in part
// because exec() isn't supported. The F_DUPFD and F_DUPFD_CLOEXEC commands only
// differ in whether the FD_CLOEXEC flag is pre-set on the new file descriptor,
// thus they can share the same implementation here.
if args.len() < 3 {
throw_ub_format!(
"incorrect number of arguments for fcntl with cmd=`F_DUPFD`/`F_DUPFD_CLOEXEC`: got {}, expected at least 3",
args.len()
);
}
let start = this.read_scalar(&args[2])?.to_i32()?;
let fh = &mut this.machine.file_handler;
match fh.handles.get_mut(&fd) {
Some(file_descriptor) => {
let dup_result = file_descriptor.dup();
match dup_result {
Ok(dup_fd) => Ok(fh.insert_fd_with_min_fd(dup_fd, start)),
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(-1)
}
}
}
None => this.handle_not_found(),
}
} else if this.tcx.sess.target.os == "macos" && cmd == this.eval_libc_i32("F_FULLFSYNC") {
// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fcntl`", reject_with)?;
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
return Ok(-1);
}
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
// FIXME: Support fullfsync for all FDs
let FileHandle { file, writable } =
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!(
"`F_FULLFSYNC` is only supported on file-backed file descriptors"
)
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_all);
this.try_unwrap_io_result(io_result)
} else {
this.handle_not_found()
}
} else {
throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
}
}
fn close(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let fd = this.read_scalar(fd_op)?.to_i32()?;
Ok(Scalar::from_i32(
if let Some(file_descriptor) = this.machine.file_handler.handles.remove(&fd) {
let result = file_descriptor.close(this.machine.communicate())?;
this.try_unwrap_io_result(result)?
} else {
this.handle_not_found()?
},
))
}
/// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
/// types (like `read`, that returns an `i64`).
fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
let this = self.eval_context_mut();
let ebadf = this.eval_libc("EBADF");
this.set_last_error(ebadf)?;
Ok((-1).into())
}
fn read(
&mut self,
fd: i32,
buf: Pointer<Option<Provenance>>,
count: u64,
) -> InterpResult<'tcx, i64> {
let this = self.eval_context_mut();
// Isolation check is done via `FileDescriptor` trait.
trace!("Reading from FD {}, size {}", fd, count);
// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
// We cap the number of read bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let communicate = this.machine.communicate();
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
trace!("read: FD mapped to {:?}", file_descriptor);
// We want to read at most `count` bytes. We are sure that `count` is not negative
// because it was a target's `usize`. Also we are sure that its smaller than
// `usize::MAX` because it is bounded by the host's `isize`.
let mut bytes = vec![0; usize::try_from(count).unwrap()];
// `File::read` never returns a value larger than `count`,
// so this cannot fail.
let result = file_descriptor
.read(communicate, &mut bytes, *this.tcx)?
.map(|c| i64::try_from(c).unwrap());
match result {
Ok(read_bytes) => {
// If reading to `bytes` did not fail, we write those bytes to the buffer.
this.write_bytes_ptr(buf, bytes)?;
Ok(read_bytes)
}
Err(e) => {
this.set_last_error_from_io_error(e.kind())?;
Ok(-1)
}
}
} else {
trace!("read: FD not found");
this.handle_not_found()
}
}
fn write(
&mut self,
fd: i32,
buf: Pointer<Option<Provenance>>,
count: u64,
) -> InterpResult<'tcx, i64> {
let this = self.eval_context_mut();
// Isolation check is done via `FileDescriptor` trait.
// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
// We cap the number of written bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
let count = count
.min(u64::try_from(this.target_isize_max()).unwrap())
.min(u64::try_from(isize::MAX).unwrap());
let communicate = this.machine.communicate();
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) {
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?;
let result = file_descriptor
.write(communicate, bytes, *this.tcx)?
.map(|c| i64::try_from(c).unwrap());
this.try_unwrap_io_result(result)
} else {
this.handle_not_found()
}
}
fn lseek64( fn lseek64(
&mut self, &mut self,
fd: i32, fd: i32,
@ -832,16 +429,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
}; };
let communicate = this.machine.communicate(); let communicate = this.machine.communicate();
Ok(Scalar::from_i64( Ok(Scalar::from_i64(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) { let result = file_descriptor
let result = file_descriptor .seek(communicate, seek_from)?
.seek(communicate, seek_from)? .map(|offset| i64::try_from(offset).unwrap());
.map(|offset| i64::try_from(offset).unwrap()); this.try_unwrap_io_result(result)?
this.try_unwrap_io_result(result)? } else {
} else { this.fd_not_found()?
this.handle_not_found()? }))
},
))
} }
fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { fn unlink(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
@ -970,7 +565,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fstat`", reject_with)?; this.reject_in_isolation("`fstat`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return Ok(Scalar::from_i32(this.handle_not_found()?)); return Ok(Scalar::from_i32(this.fd_not_found()?));
} }
let metadata = match FileMetadata::from_fd(this, fd)? { let metadata = match FileMetadata::from_fd(this, fd)? {
@ -1269,7 +864,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
match result { match result {
Ok(dir_iter) => { Ok(dir_iter) => {
let id = this.machine.dir_handler.insert_new(dir_iter); let id = this.machine.dirs.insert_new(dir_iter);
// The libc API for opendir says that this method returns a pointer to an opaque // The libc API for opendir says that this method returns a pointer to an opaque
// structure, but we are returning an ID number. Thus, pass it as a scalar of // structure, but we are returning an ID number. Thus, pass it as a scalar of
@ -1301,7 +896,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
return Ok(Scalar::null_ptr(this)); return Ok(Scalar::null_ptr(this));
} }
let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| { let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir") err_unsup_format!("the DIR pointer passed to readdir64 did not come from opendir")
})?; })?;
@ -1366,7 +961,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
} }
}; };
let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).unwrap(); let open_dir = this.machine.dirs.streams.get_mut(&dirp).unwrap();
let old_entry = std::mem::replace(&mut open_dir.entry, entry); let old_entry = std::mem::replace(&mut open_dir.entry, entry);
this.free(old_entry, MiriMemoryKind::Runtime)?; this.free(old_entry, MiriMemoryKind::Runtime)?;
@ -1391,10 +986,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`readdir_r`", reject_with)?; this.reject_in_isolation("`readdir_r`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return Ok(Scalar::from_i32(this.handle_not_found()?)); return Ok(Scalar::from_i32(this.fd_not_found()?));
} }
let open_dir = this.machine.dir_handler.streams.get_mut(&dirp).ok_or_else(|| { let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| {
err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir") err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir")
})?; })?;
Ok(Scalar::from_i32(match open_dir.read_dir.next() { Ok(Scalar::from_i32(match open_dir.read_dir.next() {
@ -1507,15 +1102,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`closedir`", reject_with)?; this.reject_in_isolation("`closedir`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return this.handle_not_found(); return this.fd_not_found();
} }
if let Some(open_dir) = this.machine.dir_handler.streams.remove(&dirp) { if let Some(open_dir) = this.machine.dirs.streams.remove(&dirp) {
this.free(open_dir.entry, MiriMemoryKind::Runtime)?; this.free(open_dir.entry, MiriMemoryKind::Runtime)?;
drop(open_dir); drop(open_dir);
Ok(0) Ok(0)
} else { } else {
this.handle_not_found() this.fd_not_found()
} }
} }
@ -1526,37 +1121,35 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`ftruncate64`", reject_with)?; this.reject_in_isolation("`ftruncate64`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return Ok(Scalar::from_i32(this.handle_not_found()?)); return Ok(Scalar::from_i32(this.fd_not_found()?));
} }
Ok(Scalar::from_i32( Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) { // FIXME: Support ftruncate64 for all FDs
// FIXME: Support ftruncate64 for all FDs let FileHandle { file, writable } =
let FileHandle { file, writable } = file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| { err_unsup_format!(
err_unsup_format!( "`ftruncate64` is only supported on file-backed file descriptors"
"`ftruncate64` is only supported on file-backed file descriptors" )
) })?;
})?; if *writable {
if *writable { if let Ok(length) = length.try_into() {
if let Ok(length) = length.try_into() { let result = file.set_len(length);
let result = file.set_len(length); this.try_unwrap_io_result(result.map(|_| 0i32))?
this.try_unwrap_io_result(result.map(|_| 0i32))?
} else {
let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?;
-1
}
} else { } else {
// The file is not writable
let einval = this.eval_libc("EINVAL"); let einval = this.eval_libc("EINVAL");
this.set_last_error(einval)?; this.set_last_error(einval)?;
-1 -1
} }
} else { } else {
this.handle_not_found()? // The file is not writable
}, let einval = this.eval_libc("EINVAL");
)) this.set_last_error(einval)?;
-1
}
} else {
this.fd_not_found()?
}))
} }
fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { fn fsync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
@ -1573,20 +1166,24 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fsync`", reject_with)?; this.reject_in_isolation("`fsync`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return this.handle_not_found(); return this.fd_not_found();
} }
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { return self.ffullsync_fd(fd);
// FIXME: Support fsync for all FDs }
let FileHandle { file, writable } =
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| { fn ffullsync_fd(&mut self, fd: i32) -> InterpResult<'tcx, i32> {
err_unsup_format!("`fsync` is only supported on file-backed file descriptors") let this = self.eval_context_mut();
})?; let Some(file_descriptor) = this.machine.fds.get(fd) else {
let io_result = maybe_sync_file(file, *writable, File::sync_all); return Ok(this.fd_not_found()?);
this.try_unwrap_io_result(io_result) };
} else { // Only regular files support synchronization.
this.handle_not_found() let FileHandle { file, writable } =
} file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
})?;
let io_result = maybe_sync_file(file, *writable, File::sync_all);
this.try_unwrap_io_result(io_result)
} }
fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { fn fdatasync(&mut self, fd_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
@ -1598,22 +1195,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`fdatasync`", reject_with)?; this.reject_in_isolation("`fdatasync`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return this.handle_not_found(); return this.fd_not_found();
} }
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { let Some(file_descriptor) = this.machine.fds.get(fd) else {
// FIXME: Support fdatasync for all FDs return Ok(this.fd_not_found()?);
let FileHandle { file, writable } = };
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| { // Only regular files support synchronization.
err_unsup_format!( let FileHandle { file, writable } =
"`fdatasync` is only supported on file-backed file descriptors" file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
) err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
})?; })?;
let io_result = maybe_sync_file(file, *writable, File::sync_data); let io_result = maybe_sync_file(file, *writable, File::sync_data);
this.try_unwrap_io_result(io_result) this.try_unwrap_io_result(io_result)
} else {
this.handle_not_found()
}
} }
fn sync_file_range( fn sync_file_range(
@ -1648,22 +1242,21 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`sync_file_range`", reject_with)?; this.reject_in_isolation("`sync_file_range`", reject_with)?;
// Set error code as "EBADF" (bad fd) // Set error code as "EBADF" (bad fd)
return Ok(Scalar::from_i32(this.handle_not_found()?)); return Ok(Scalar::from_i32(this.fd_not_found()?));
} }
if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { let Some(file_descriptor) = this.machine.fds.get(fd) else {
// FIXME: Support sync_data_range for all FDs return Ok(Scalar::from_i32(this.fd_not_found()?));
let FileHandle { file, writable } = };
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| { // Only regular files support synchronization.
err_unsup_format!( let FileHandle { file, writable } =
"`sync_data_range` is only supported on file-backed file descriptors" file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
) err_unsup_format!(
})?; "`sync_data_range` is only supported on file-backed file descriptors"
let io_result = maybe_sync_file(file, *writable, File::sync_data); )
Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?)) })?;
} else { let io_result = maybe_sync_file(file, *writable, File::sync_data);
Ok(Scalar::from_i32(this.handle_not_found()?)) Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
}
} }
fn readlink( fn readlink(
@ -1720,7 +1313,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// "returns 1 if fd is an open file descriptor referring to a terminal; // "returns 1 if fd is an open file descriptor referring to a terminal;
// otherwise 0 is returned, and errno is set to indicate the error" // otherwise 0 is returned, and errno is set to indicate the error"
let fd = this.read_scalar(miri_fd)?.to_i32()?; let fd = this.read_scalar(miri_fd)?.to_i32()?;
let error = if let Some(fd) = this.machine.file_handler.handles.get(&fd) { let error = if let Some(fd) = this.machine.fds.get(fd) {
if fd.is_tty(this.machine.communicate()) { if fd.is_tty(this.machine.communicate()) {
return Ok(Scalar::from_i32(1)); return Ok(Scalar::from_i32(1));
} else { } else {
@ -1897,7 +1490,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
match file { match file {
Ok(f) => { Ok(f) => {
let fh = &mut this.machine.file_handler; let fh = &mut this.machine.fds;
let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true })); let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
return Ok(fd); return Ok(fd);
} }
@ -1963,7 +1556,7 @@ impl FileMetadata {
ecx: &mut MiriInterpCx<'_, 'tcx>, ecx: &mut MiriInterpCx<'_, 'tcx>,
fd: i32, fd: i32,
) -> InterpResult<'tcx, Option<FileMetadata>> { ) -> InterpResult<'tcx, Option<FileMetadata>> {
let option = ecx.machine.file_handler.handles.get(&fd); let option = ecx.machine.fds.get(fd);
let file = match option { let file = match option {
Some(file_descriptor) => Some(file_descriptor) =>
&file_descriptor &file_descriptor
@ -1974,7 +1567,7 @@ impl FileMetadata {
) )
})? })?
.file, .file,
None => return ecx.handle_not_found().map(|_: i32| None), None => return ecx.fd_not_found().map(|_: i32| None),
}; };
let metadata = file.metadata(); let metadata = file.metadata();

View File

@ -1,17 +1,12 @@
use std::cell::Cell; use std::cell::Cell;
use rustc_middle::ty::ScalarInt; use crate::shims::unix::*;
use crate::*; use crate::*;
use epoll::{Epoll, EpollEvent}; use epoll::{Epoll, EpollEvent};
use event::Event; use event::Event;
use socketpair::SocketPair;
use shims::unix::fs::EvalContextExt as _;
pub mod epoll; pub mod epoll;
pub mod event; pub mod event;
pub mod socketpair;
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
@ -35,7 +30,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
throw_unsup_format!("epoll_create1 flags {flags} are not implemented"); throw_unsup_format!("epoll_create1 flags {flags} are not implemented");
} }
let fd = this.machine.file_handler.insert_fd(Box::new(Epoll::default())); let fd = this.machine.fds.insert_fd(Box::new(Epoll::default()));
Ok(Scalar::from_i32(fd)) Ok(Scalar::from_i32(fd))
} }
@ -79,7 +74,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let data = this.read_scalar(&data)?; let data = this.read_scalar(&data)?;
let event = EpollEvent { events, data }; let event = EpollEvent { events, data };
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { if let Some(epfd) = this.machine.fds.get_mut(epfd) {
let epfd = epfd let epfd = epfd
.downcast_mut::<Epoll>() .downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
@ -87,10 +82,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
epfd.file_descriptors.insert(fd, event); epfd.file_descriptors.insert(fd, event);
Ok(Scalar::from_i32(0)) Ok(Scalar::from_i32(0))
} else { } else {
Ok(Scalar::from_i32(this.handle_not_found()?)) Ok(Scalar::from_i32(this.fd_not_found()?))
} }
} else if op == epoll_ctl_del { } else if op == epoll_ctl_del {
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { if let Some(epfd) = this.machine.fds.get_mut(epfd) {
let epfd = epfd let epfd = epfd
.downcast_mut::<Epoll>() .downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?;
@ -98,7 +93,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
epfd.file_descriptors.remove(&fd); epfd.file_descriptors.remove(&fd);
Ok(Scalar::from_i32(0)) Ok(Scalar::from_i32(0))
} else { } else {
Ok(Scalar::from_i32(this.handle_not_found()?)) Ok(Scalar::from_i32(this.fd_not_found()?))
} }
} else { } else {
let einval = this.eval_libc("EINVAL"); let einval = this.eval_libc("EINVAL");
@ -150,7 +145,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let _maxevents = this.read_scalar(maxevents)?.to_i32()?; let _maxevents = this.read_scalar(maxevents)?.to_i32()?;
let _timeout = this.read_scalar(timeout)?.to_i32()?; let _timeout = this.read_scalar(timeout)?.to_i32()?;
if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { if let Some(epfd) = this.machine.fds.get_mut(epfd) {
let _epfd = epfd let _epfd = epfd
.downcast_mut::<Epoll>() .downcast_mut::<Epoll>()
.ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?;
@ -158,7 +153,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// FIXME return number of events ready when scheme for marking events ready exists // FIXME return number of events ready when scheme for marking events ready exists
throw_unsup_format!("returning ready events from epoll_wait is not yet implemented"); throw_unsup_format!("returning ready events from epoll_wait is not yet implemented");
} else { } else {
Ok(Scalar::from_i32(this.handle_not_found()?)) Ok(Scalar::from_i32(this.fd_not_found()?))
} }
} }
@ -203,51 +198,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
throw_unsup_format!("EFD_SEMAPHORE is unsupported"); throw_unsup_format!("EFD_SEMAPHORE is unsupported");
} }
let fh = &mut this.machine.file_handler; let fd = this.machine.fds.insert_fd(Box::new(Event { val: Cell::new(val.into()) }));
let fd = fh.insert_fd(Box::new(Event { val: Cell::new(val.into()) }));
Ok(Scalar::from_i32(fd)) Ok(Scalar::from_i32(fd))
} }
/// Currently this function creates new `SocketPair`s without specifying the domain, type, or
/// protocol of the new socket and these are stored in the socket values `sv` argument.
///
/// This function creates an unnamed pair of connected sockets in the specified domain, of the
/// specified type, and using the optionally specified protocol.
///
/// The `domain` argument specified a communication domain; this selects the protocol family
/// used for communication. The socket `type` specifies the communication semantics.
/// The `protocol` specifies a particular protocol to use with the socket. Normally there's
/// only a single protocol supported for a particular socket type within a given protocol
/// family, in which case `protocol` can be specified as 0. It is possible that many protocols
/// exist and in that case, a particular protocol must be specified.
///
/// For more information on the arguments see the socket manpage:
/// <https://linux.die.net/man/2/socket>
///
/// <https://linux.die.net/man/2/socketpair>
fn socketpair(
&mut self,
domain: &OpTy<'tcx, Provenance>,
type_: &OpTy<'tcx, Provenance>,
protocol: &OpTy<'tcx, Provenance>,
sv: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let _domain = this.read_scalar(domain)?.to_i32()?;
let _type_ = this.read_scalar(type_)?.to_i32()?;
let _protocol = this.read_scalar(protocol)?.to_i32()?;
let sv = this.deref_pointer(sv)?;
let fh = &mut this.machine.file_handler;
let sv0 = fh.insert_fd(Box::new(SocketPair));
let sv0 = ScalarInt::try_from_int(sv0, sv.layout.size).unwrap();
let sv1 = fh.insert_fd(Box::new(SocketPair));
let sv1 = ScalarInt::try_from_int(sv1, sv.layout.size).unwrap();
this.write_scalar(sv0, &sv)?;
this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
Ok(Scalar::from_i32(0))
}
} }

View File

@ -1,6 +1,6 @@
use crate::*; use crate::*;
use crate::shims::unix::fs::FileDescriptor; use crate::shims::unix::FileDescriptor;
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use std::io; use std::io;

View File

@ -1,4 +1,4 @@
use crate::shims::unix::fs::FileDescriptor; use crate::shims::unix::FileDescriptor;
use rustc_const_eval::interpret::InterpResult; use rustc_const_eval::interpret::InterpResult;
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;

View File

@ -1,28 +0,0 @@
use crate::*;
use crate::shims::unix::fs::FileDescriptor;
use std::io;
/// Pair of connected sockets.
///
/// We currently don't allow sending any data through this pair, so this can be just a dummy.
#[derive(Debug)]
pub struct SocketPair;
impl FileDescriptor for SocketPair {
fn name(&self) -> &'static str {
"socketpair"
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(SocketPair))
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}

View File

@ -3,15 +3,12 @@ use rustc_target::spec::abi::Abi;
use crate::machine::SIGRTMAX; use crate::machine::SIGRTMAX;
use crate::machine::SIGRTMIN; use crate::machine::SIGRTMIN;
use crate::shims::unix::*;
use crate::*; use crate::*;
use shims::foreign_items::EmulateForeignItemResult; use shims::foreign_items::EmulateForeignItemResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::linux::fd::EvalContextExt as _; use shims::unix::linux::fd::EvalContextExt as _;
use shims::unix::linux::mem::EvalContextExt as _; use shims::unix::linux::mem::EvalContextExt as _;
use shims::unix::linux::sync::futex; use shims::unix::linux::sync::futex;
use shims::unix::mem::EvalContextExt as _;
use shims::unix::sync::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
pub fn is_dyn_sym(name: &str) -> bool { pub fn is_dyn_sym(name: &str) -> bool {
matches!(name, "getrandom") matches!(name, "getrandom")

View File

@ -1,10 +1,9 @@
use rustc_span::Symbol; use rustc_span::Symbol;
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
use crate::shims::unix::*;
use crate::*; use crate::*;
use shims::foreign_items::EmulateForeignItemResult; use shims::foreign_items::EmulateForeignItemResult;
use shims::unix::fs::EvalContextExt as _;
use shims::unix::thread::EvalContextExt as _;
pub fn is_dyn_sym(_name: &str) -> bool { pub fn is_dyn_sym(_name: &str) -> bool {
false false

View File

@ -1,7 +1,9 @@
pub mod foreign_items; pub mod foreign_items;
mod fd;
mod fs; mod fs;
mod mem; mod mem;
mod socket;
mod sync; mod sync;
mod thread; mod thread;
@ -9,7 +11,15 @@ mod freebsd;
mod linux; mod linux;
mod macos; mod macos;
pub use fs::{DirHandler, FileHandler}; pub use fd::{FdTable, FileDescriptor};
pub use fs::DirTable;
// All the unix-specific extension traits
pub use fd::EvalContextExt as _;
pub use fs::EvalContextExt as _;
pub use mem::EvalContextExt as _;
pub use socket::EvalContextExt as _;
pub use sync::EvalContextExt as _;
pub use thread::EvalContextExt as _;
// Make up some constants. // Make up some constants.
const UID: u32 = 1000; const UID: u32 = 1000;

View File

@ -0,0 +1,66 @@
use std::io;
use crate::shims::unix::*;
use crate::*;
/// Pair of connected sockets.
///
/// We currently don't allow sending any data through this pair, so this can be just a dummy.
/// FIXME: show proper errors when trying to send/receive
#[derive(Debug)]
struct SocketPair;
impl FileDescriptor for SocketPair {
fn name(&self) -> &'static str {
"socketpair"
}
fn dup(&mut self) -> io::Result<Box<dyn FileDescriptor>> {
Ok(Box::new(SocketPair))
}
fn close<'tcx>(
self: Box<Self>,
_communicate_allowed: bool,
) -> InterpResult<'tcx, io::Result<i32>> {
Ok(Ok(0))
}
}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Currently this function this function is a stub. Eventually we need to
/// properly implement an FD type for sockets and have this function create
/// two sockets and associated FDs such that writing to one will produce
/// data that can be read from the other.
///
/// For more information on the arguments see the socketpair manpage:
/// <https://linux.die.net/man/2/socketpair>
fn socketpair(
&mut self,
domain: &OpTy<'tcx, Provenance>,
type_: &OpTy<'tcx, Provenance>,
protocol: &OpTy<'tcx, Provenance>,
sv: &OpTy<'tcx, Provenance>,
) -> InterpResult<'tcx, Scalar<Provenance>> {
let this = self.eval_context_mut();
let _domain = this.read_scalar(domain)?.to_i32()?;
let _type_ = this.read_scalar(type_)?.to_i32()?;
let _protocol = this.read_scalar(protocol)?.to_i32()?;
let sv = this.deref_pointer(sv)?;
// FIXME: fail on unsupported inputs
let fds = &mut this.machine.fds;
let sv0 = fds.insert_fd(Box::new(SocketPair));
let sv0 = Scalar::try_from_int(sv0, sv.layout.size).unwrap();
let sv1 = fds.insert_fd(Box::new(SocketPair));
let sv1 = Scalar::try_from_int(sv1, sv.layout.size).unwrap();
this.write_scalar(sv0, &sv)?;
this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
Ok(Scalar::from_i32(0))
}
}