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::{
|
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);
|
||||||
|
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 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)?;
|
||||||
|
@ -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.handle_not_found()?
|
this.fd_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,11 +1121,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("`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(|| {
|
||||||
@ -1554,9 +1148,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
|||||||
-1
|
-1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.handle_not_found()?
|
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
|
}
|
||||||
|
|
||||||
|
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 } =
|
let FileHandle { file, writable } =
|
||||||
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
|
file_descriptor.downcast_ref::<FileHandle>().ok_or_else(|| {
|
||||||
err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
|
err_unsup_format!("`fsync` is only supported on file-backed file descriptors")
|
||||||
})?;
|
})?;
|
||||||
let io_result = maybe_sync_file(file, *writable, File::sync_all);
|
let io_result = maybe_sync_file(file, *writable, File::sync_all);
|
||||||
this.try_unwrap_io_result(io_result)
|
this.try_unwrap_io_result(io_result)
|
||||||
} else {
|
|
||||||
this.handle_not_found()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()?);
|
||||||
|
};
|
||||||
|
// Only regular files support synchronization.
|
||||||
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!("`fdatasync` is only supported on file-backed file descriptors")
|
||||||
"`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,11 +1242,13 @@ 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()?));
|
||||||
|
};
|
||||||
|
// Only regular files support synchronization.
|
||||||
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!(
|
||||||
@ -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);
|
let io_result = maybe_sync_file(file, *writable, File::sync_data);
|
||||||
Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
|
Ok(Scalar::from_i32(this.try_unwrap_io_result(io_result)?))
|
||||||
} else {
|
|
||||||
Ok(Scalar::from_i32(this.handle_not_found()?))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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::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")
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
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