shims/unix: split general FD management from FS access; make a place for sockets
This commit is contained in:
parent
4397b6b00c
commit
229d41731a
@ -31,7 +31,7 @@ use rustc_target::spec::abi::Abi;
|
||||
|
||||
use crate::{
|
||||
concurrency::{data_race, weak_memory},
|
||||
shims::unix::FileHandler,
|
||||
shims::unix::FdTable,
|
||||
*,
|
||||
};
|
||||
|
||||
@ -463,9 +463,9 @@ pub struct MiriMachine<'mir, 'tcx> {
|
||||
pub(crate) validate: bool,
|
||||
|
||||
/// The table of file descriptors.
|
||||
pub(crate) file_handler: shims::unix::FileHandler,
|
||||
pub(crate) fds: shims::unix::FdTable,
|
||||
/// The table of directory descriptors.
|
||||
pub(crate) dir_handler: shims::unix::DirHandler,
|
||||
pub(crate) dirs: shims::unix::DirTable,
|
||||
|
||||
/// This machine's monotone clock.
|
||||
pub(crate) clock: Clock,
|
||||
@ -640,8 +640,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
|
||||
tls: TlsData::default(),
|
||||
isolated_op: config.isolated_op,
|
||||
validate: config.validate,
|
||||
file_handler: FileHandler::new(config.mute_stdout_stderr),
|
||||
dir_handler: Default::default(),
|
||||
fds: FdTable::new(config.mute_stdout_stderr),
|
||||
dirs: Default::default(),
|
||||
layouts,
|
||||
threads: ThreadManager::default(),
|
||||
static_roots: Vec::new(),
|
||||
@ -774,11 +774,11 @@ impl VisitProvenance for MiriMachine<'_, '_> {
|
||||
argv,
|
||||
cmd_line,
|
||||
extern_statics,
|
||||
dir_handler,
|
||||
dirs,
|
||||
borrow_tracker,
|
||||
data_race,
|
||||
alloc_addresses,
|
||||
file_handler,
|
||||
fds,
|
||||
tcx: _,
|
||||
isolated_op: _,
|
||||
validate: _,
|
||||
@ -817,8 +817,8 @@ impl VisitProvenance for MiriMachine<'_, '_> {
|
||||
threads.visit_provenance(visit);
|
||||
tls.visit_provenance(visit);
|
||||
env_vars.visit_provenance(visit);
|
||||
dir_handler.visit_provenance(visit);
|
||||
file_handler.visit_provenance(visit);
|
||||
dirs.visit_provenance(visit);
|
||||
fds.visit_provenance(visit);
|
||||
data_race.visit_provenance(visit);
|
||||
borrow_tracker.visit_provenance(visit);
|
||||
alloc_addresses.visit_provenance(visit);
|
||||
|
429
src/tools/miri/src/shims/unix/fd.rs
Normal file
429
src/tools/miri/src/shims/unix/fd.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
@ -8,8 +8,10 @@ use rustc_target::spec::abi::Abi;
|
||||
|
||||
use crate::*;
|
||||
use shims::foreign_items::EmulateForeignItemResult;
|
||||
use shims::unix::fd::EvalContextExt as _;
|
||||
use shims::unix::fs::EvalContextExt as _;
|
||||
use shims::unix::mem::EvalContextExt as _;
|
||||
use shims::unix::socket::EvalContextExt as _;
|
||||
use shims::unix::sync::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.
|
||||
#[rustfmt::skip]
|
||||
match link_name.as_str() {
|
||||
// Environment related shims
|
||||
// Environment variables
|
||||
"getenv" => {
|
||||
let [name] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
|
||||
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)?;
|
||||
}
|
||||
|
||||
// File related shims
|
||||
"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)?;
|
||||
}
|
||||
// File descriptors
|
||||
"read" => {
|
||||
let [fd, buf, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
|
||||
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.
|
||||
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" => {
|
||||
let [path] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
|
||||
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)?;
|
||||
}
|
||||
|
||||
// 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" => {
|
||||
let [tv, tz] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
|
||||
let result = this.gettimeofday(tv, tz)?;
|
||||
@ -598,7 +611,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
if bufsize > 256 {
|
||||
let err = this.eval_libc("EIO");
|
||||
this.set_last_error(err)?;
|
||||
this.write_scalar(Scalar::from_i32(-1), dest)?
|
||||
this.write_scalar(Scalar::from_i32(-1), dest)?;
|
||||
} else {
|
||||
this.gen_random(buf, bufsize)?;
|
||||
this.write_scalar(Scalar::from_i32(0), dest)?;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::any::Any;
|
||||
//! File and file system access
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{
|
||||
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 crate::shims::os_str::bytes_to_os_str;
|
||||
use crate::shims::unix::*;
|
||||
use crate::*;
|
||||
use shims::time::system_time_to_duration;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileHandle {
|
||||
struct FileHandle {
|
||||
file: File,
|
||||
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 {
|
||||
fn name(&self) -> &'static str {
|
||||
"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> {}
|
||||
trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
fn macos_stat_write_buf(
|
||||
@ -411,10 +191,11 @@ trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx
|
||||
|
||||
/// An open directory, tracked by DirHandler.
|
||||
#[derive(Debug)]
|
||||
pub struct OpenDir {
|
||||
struct OpenDir {
|
||||
/// The directory reader on the host.
|
||||
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>>,
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
pub struct DirHandler {
|
||||
pub struct DirTable {
|
||||
/// Directory iterators used to emulate libc "directory streams", as used in opendir, readdir,
|
||||
/// and closedir.
|
||||
///
|
||||
@ -441,7 +225,7 @@ pub struct DirHandler {
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
impl DirHandler {
|
||||
impl DirTable {
|
||||
#[allow(clippy::arithmetic_side_effects)]
|
||||
fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
|
||||
let id = self.next_id;
|
||||
@ -451,9 +235,9 @@ impl DirHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DirHandler {
|
||||
fn default() -> DirHandler {
|
||||
DirHandler {
|
||||
impl Default for DirTable {
|
||||
fn default() -> DirTable {
|
||||
DirTable {
|
||||
streams: FxHashMap::default(),
|
||||
// Skip 0 as an ID, because it looks like a null pointer to libc
|
||||
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<'_>) {
|
||||
let DirHandler { streams, next_id: _ } = self;
|
||||
let DirTable { streams, next_id: _ } = self;
|
||||
|
||||
for dir in streams.values() {
|
||||
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 fh = &mut this.machine.file_handler;
|
||||
let fh = &mut this.machine.fds;
|
||||
fh.insert_fd(Box::new(FileHandle { file, writable }))
|
||||
});
|
||||
|
||||
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(
|
||||
&mut self,
|
||||
fd: i32,
|
||||
@ -832,16 +429,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
};
|
||||
|
||||
let communicate = this.machine.communicate();
|
||||
Ok(Scalar::from_i64(
|
||||
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
|
||||
Ok(Scalar::from_i64(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
|
||||
let result = file_descriptor
|
||||
.seek(communicate, seek_from)?
|
||||
.map(|offset| i64::try_from(offset).unwrap());
|
||||
this.try_unwrap_io_result(result)?
|
||||
} else {
|
||||
this.handle_not_found()?
|
||||
},
|
||||
))
|
||||
this.fd_not_found()?
|
||||
}))
|
||||
}
|
||||
|
||||
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 {
|
||||
this.reject_in_isolation("`fstat`", reject_with)?;
|
||||
// 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)? {
|
||||
@ -1269,7 +864,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
|
||||
match result {
|
||||
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
|
||||
// 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));
|
||||
}
|
||||
|
||||
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")
|
||||
})?;
|
||||
|
||||
@ -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);
|
||||
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 {
|
||||
this.reject_in_isolation("`readdir_r`", reject_with)?;
|
||||
// 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")
|
||||
})?;
|
||||
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 {
|
||||
this.reject_in_isolation("`closedir`", reject_with)?;
|
||||
// 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)?;
|
||||
drop(open_dir);
|
||||
Ok(0)
|
||||
} else {
|
||||
this.handle_not_found()
|
||||
this.fd_not_found()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1526,11 +1121,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`ftruncate64`", reject_with)?;
|
||||
// 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(
|
||||
if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) {
|
||||
Ok(Scalar::from_i32(if let Some(file_descriptor) = this.machine.fds.get_mut(fd) {
|
||||
// FIXME: Support ftruncate64 for all FDs
|
||||
let FileHandle { file, writable } =
|
||||
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
|
||||
@ -1554,9 +1148,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
-1
|
||||
}
|
||||
} else {
|
||||
this.handle_not_found()?
|
||||
},
|
||||
))
|
||||
this.fd_not_found()?
|
||||
}))
|
||||
}
|
||||
|
||||
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 {
|
||||
this.reject_in_isolation("`fsync`", reject_with)?;
|
||||
// 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) {
|
||||
// FIXME: Support fsync for all FDs
|
||||
return self.ffullsync_fd(fd);
|
||||
}
|
||||
|
||||
fn ffullsync_fd(&mut self, fd: i32) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
let Some(file_descriptor) = this.machine.fds.get(fd) else {
|
||||
return Ok(this.fd_not_found()?);
|
||||
};
|
||||
// Only regular files support synchronization.
|
||||
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)
|
||||
} else {
|
||||
this.handle_not_found()
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
this.reject_in_isolation("`fdatasync`", reject_with)?;
|
||||
// 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) {
|
||||
// FIXME: Support fdatasync for all FDs
|
||||
let Some(file_descriptor) = this.machine.fds.get(fd) else {
|
||||
return Ok(this.fd_not_found()?);
|
||||
};
|
||||
// Only regular files support synchronization.
|
||||
let FileHandle { file, writable } =
|
||||
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
|
||||
err_unsup_format!(
|
||||
"`fdatasync` is only supported on file-backed file descriptors"
|
||||
)
|
||||
err_unsup_format!("`fdatasync` is only supported on file-backed file descriptors")
|
||||
})?;
|
||||
let io_result = maybe_sync_file(file, *writable, File::sync_data);
|
||||
this.try_unwrap_io_result(io_result)
|
||||
} else {
|
||||
this.handle_not_found()
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_file_range(
|
||||
@ -1648,11 +1242,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`sync_file_range`", reject_with)?;
|
||||
// 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) {
|
||||
// FIXME: Support sync_data_range for all FDs
|
||||
let Some(file_descriptor) = this.machine.fds.get(fd) else {
|
||||
return Ok(Scalar::from_i32(this.fd_not_found()?));
|
||||
};
|
||||
// Only regular files support synchronization.
|
||||
let FileHandle { file, writable } =
|
||||
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
|
||||
err_unsup_format!(
|
||||
@ -1661,9 +1257,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
})?;
|
||||
let io_result = maybe_sync_file(file, *writable, File::sync_data);
|
||||
Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
|
||||
} else {
|
||||
Ok(Scalar::from_i32(this.handle_not_found()?))
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
// otherwise 0 is returned, and errno is set to indicate the error"
|
||||
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()) {
|
||||
return Ok(Scalar::from_i32(1));
|
||||
} else {
|
||||
@ -1897,7 +1490,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
|
||||
match file {
|
||||
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 }));
|
||||
return Ok(fd);
|
||||
}
|
||||
@ -1963,7 +1556,7 @@ impl FileMetadata {
|
||||
ecx: &mut MiriInterpCx<'_, 'tcx>,
|
||||
fd: i32,
|
||||
) -> InterpResult<'tcx, Option<FileMetadata>> {
|
||||
let option = ecx.machine.file_handler.handles.get(&fd);
|
||||
let option = ecx.machine.fds.get(fd);
|
||||
let file = match option {
|
||||
Some(file_descriptor) =>
|
||||
&file_descriptor
|
||||
@ -1974,7 +1567,7 @@ impl FileMetadata {
|
||||
)
|
||||
})?
|
||||
.file,
|
||||
None => return ecx.handle_not_found().map(|_: i32| None),
|
||||
None => return ecx.fd_not_found().map(|_: i32| None),
|
||||
};
|
||||
let metadata = file.metadata();
|
||||
|
||||
|
@ -1,17 +1,12 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use rustc_middle::ty::ScalarInt;
|
||||
|
||||
use crate::shims::unix::*;
|
||||
use crate::*;
|
||||
use epoll::{Epoll, EpollEvent};
|
||||
use event::Event;
|
||||
use socketpair::SocketPair;
|
||||
|
||||
use shims::unix::fs::EvalContextExt as _;
|
||||
|
||||
pub mod epoll;
|
||||
pub mod event;
|
||||
pub mod socketpair;
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'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");
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@ -79,7 +74,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
let data = this.read_scalar(&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
|
||||
.downcast_mut::<Epoll>()
|
||||
.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);
|
||||
Ok(Scalar::from_i32(0))
|
||||
} else {
|
||||
Ok(Scalar::from_i32(this.handle_not_found()?))
|
||||
Ok(Scalar::from_i32(this.fd_not_found()?))
|
||||
}
|
||||
} 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
|
||||
.downcast_mut::<Epoll>()
|
||||
.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);
|
||||
Ok(Scalar::from_i32(0))
|
||||
} else {
|
||||
Ok(Scalar::from_i32(this.handle_not_found()?))
|
||||
Ok(Scalar::from_i32(this.fd_not_found()?))
|
||||
}
|
||||
} else {
|
||||
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 _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
|
||||
.downcast_mut::<Epoll>()
|
||||
.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
|
||||
throw_unsup_format!("returning ready events from epoll_wait is not yet implemented");
|
||||
} 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");
|
||||
}
|
||||
|
||||
let fh = &mut this.machine.file_handler;
|
||||
let fd = fh.insert_fd(Box::new(Event { val: Cell::new(val.into()) }));
|
||||
let fd = this.machine.fds.insert_fd(Box::new(Event { val: Cell::new(val.into()) }));
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::*;
|
||||
|
||||
use crate::shims::unix::fs::FileDescriptor;
|
||||
use crate::shims::unix::FileDescriptor;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use std::io;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::shims::unix::fs::FileDescriptor;
|
||||
use crate::shims::unix::FileDescriptor;
|
||||
|
||||
use rustc_const_eval::interpret::InterpResult;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -3,15 +3,12 @@ use rustc_target::spec::abi::Abi;
|
||||
|
||||
use crate::machine::SIGRTMAX;
|
||||
use crate::machine::SIGRTMIN;
|
||||
use crate::shims::unix::*;
|
||||
use crate::*;
|
||||
use shims::foreign_items::EmulateForeignItemResult;
|
||||
use shims::unix::fs::EvalContextExt as _;
|
||||
use shims::unix::linux::fd::EvalContextExt as _;
|
||||
use shims::unix::linux::mem::EvalContextExt as _;
|
||||
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 {
|
||||
matches!(name, "getrandom")
|
||||
|
@ -1,10 +1,9 @@
|
||||
use rustc_span::Symbol;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
use crate::shims::unix::*;
|
||||
use crate::*;
|
||||
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 {
|
||||
false
|
||||
|
@ -1,7 +1,9 @@
|
||||
pub mod foreign_items;
|
||||
|
||||
mod fd;
|
||||
mod fs;
|
||||
mod mem;
|
||||
mod socket;
|
||||
mod sync;
|
||||
mod thread;
|
||||
|
||||
@ -9,7 +11,15 @@ mod freebsd;
|
||||
mod linux;
|
||||
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.
|
||||
const UID: u32 = 1000;
|
||||
|
66
src/tools/miri/src/shims/unix/socket.rs
Normal file
66
src/tools/miri/src/shims/unix/socket.rs
Normal 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))
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user