Auto merge of #962 - christianpoveda:file-shim, r=oli-obk
Add shims for file handling This adds the bare minimum to be able to do `File::open` and `File::read`. I also need some feedback about how to handle certain things
This commit is contained in:
commit
1037f69bf6
7 changed files with 260 additions and 0 deletions
|
|
@ -34,6 +34,7 @@ pub use crate::shims::intrinsics::EvalContextExt as IntrinsicsEvalContextExt;
|
|||
pub use crate::shims::tls::{EvalContextExt as TlsEvalContextExt, TlsData};
|
||||
pub use crate::shims::dlsym::{Dlsym, EvalContextExt as DlsymEvalContextExt};
|
||||
pub use crate::shims::env::{EnvVars, EvalContextExt as EnvEvalContextExt};
|
||||
pub use crate::shims::io::{FileHandler, EvalContextExt as FileEvalContextExt};
|
||||
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
|
||||
pub use crate::range_map::RangeMap;
|
||||
pub use crate::helpers::{EvalContextExt as HelpersEvalContextExt};
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ pub struct Evaluator<'tcx> {
|
|||
/// If enabled, the `env_vars` field is populated with the host env vars during initialization
|
||||
/// and random number generation is delegated to the host.
|
||||
pub(crate) communicate: bool,
|
||||
|
||||
pub(crate) file_handler: FileHandler,
|
||||
}
|
||||
|
||||
impl<'tcx> Evaluator<'tcx> {
|
||||
|
|
@ -110,6 +112,7 @@ impl<'tcx> Evaluator<'tcx> {
|
|||
last_error: 0,
|
||||
tls: TlsData::default(),
|
||||
communicate,
|
||||
file_handler: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -446,6 +446,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
|
||||
}
|
||||
|
||||
"open" | "open64" => {
|
||||
let result = this.open(args[0], args[1])?;
|
||||
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
|
||||
}
|
||||
|
||||
"fcntl" => {
|
||||
let result = this.fcntl(args[0], args[1], args.get(2).cloned())?;
|
||||
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
|
||||
}
|
||||
|
||||
"close" | "close$NOCANCEL" => {
|
||||
let result = this.close(args[0])?;
|
||||
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
|
||||
}
|
||||
|
||||
"read" => {
|
||||
let result = this.read(args[0], args[1], args[2])?;
|
||||
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
|
||||
}
|
||||
|
||||
"write" => {
|
||||
let fd = this.read_scalar(args[0])?.to_i32()?;
|
||||
let buf = this.read_scalar(args[1])?.not_undef()?;
|
||||
|
|
@ -929,6 +949,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
|
|||
}
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
fn eval_libc_i32(&mut self, name: &str) -> InterpResult<'tcx, i32> {
|
||||
self
|
||||
.eval_context_mut()
|
||||
.eval_path_scalar(&["libc", name])?
|
||||
.ok_or_else(|| err_unsup_format!("Path libc::{} cannot be resolved.", name).into())
|
||||
.and_then(|scalar| scalar.to_i32())
|
||||
}
|
||||
}
|
||||
|
||||
// Shims the linux 'getrandom()' syscall.
|
||||
|
|
|
|||
213
src/shims/io.rs
Normal file
213
src/shims/io.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
use rustc::ty::layout::Size;
|
||||
|
||||
use crate::stacked_borrows::Tag;
|
||||
use crate::*;
|
||||
|
||||
pub struct FileHandle {
|
||||
file: File,
|
||||
flag: i32,
|
||||
}
|
||||
|
||||
pub struct FileHandler {
|
||||
handles: HashMap<i32, FileHandle>,
|
||||
low: i32,
|
||||
}
|
||||
|
||||
impl Default for FileHandler {
|
||||
fn default() -> Self {
|
||||
FileHandler {
|
||||
handles: Default::default(),
|
||||
// 0, 1 and 2 are reserved for stdin, stdout and stderr
|
||||
low: 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
|
||||
fn open(
|
||||
&mut self,
|
||||
path_op: OpTy<'tcx, Tag>,
|
||||
flag_op: OpTy<'tcx, Tag>,
|
||||
) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
if !this.machine.communicate {
|
||||
throw_unsup_format!("`open` not available when isolation is enabled")
|
||||
}
|
||||
|
||||
let flag = this.read_scalar(flag_op)?.to_i32()?;
|
||||
|
||||
if flag != this.eval_libc_i32("O_RDONLY")? && flag != this.eval_libc_i32("O_CLOEXEC")? {
|
||||
throw_unsup_format!("Unsupported flag {:#x}", flag);
|
||||
}
|
||||
|
||||
let path_bytes = this
|
||||
.memory()
|
||||
.read_c_str(this.read_scalar(path_op)?.not_undef()?)?;
|
||||
let path = std::str::from_utf8(path_bytes)
|
||||
.map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", path_bytes))?;
|
||||
let fd = File::open(path).map(|file| {
|
||||
let mut fh = &mut this.machine.file_handler;
|
||||
fh.low += 1;
|
||||
fh.handles.insert(fh.low, FileHandle { file, flag });
|
||||
fh.low
|
||||
});
|
||||
|
||||
this.consume_result(fd)
|
||||
}
|
||||
|
||||
fn fcntl(
|
||||
&mut self,
|
||||
fd_op: OpTy<'tcx, Tag>,
|
||||
cmd_op: OpTy<'tcx, Tag>,
|
||||
arg_op: Option<OpTy<'tcx, Tag>>,
|
||||
) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
if !this.machine.communicate {
|
||||
throw_unsup_format!("`open` not available when isolation is enabled")
|
||||
}
|
||||
|
||||
let fd = this.read_scalar(fd_op)?.to_i32()?;
|
||||
let cmd = this.read_scalar(cmd_op)?.to_i32()?;
|
||||
|
||||
if cmd == this.eval_libc_i32("F_SETFD")? {
|
||||
// This does not affect the file itself. Certain flags might require changing the file
|
||||
// or the way it is accessed somehow.
|
||||
let flag = this.read_scalar(arg_op.unwrap())?.to_i32()?;
|
||||
// The only usage of this in stdlib at the moment is to enable the `FD_CLOEXEC` flag.
|
||||
let fd_cloexec = this.eval_libc_i32("FD_CLOEXEC")?;
|
||||
if let Some(FileHandle { flag: old_flag, .. }) =
|
||||
this.machine.file_handler.handles.get_mut(&fd)
|
||||
{
|
||||
if flag ^ *old_flag == fd_cloexec {
|
||||
*old_flag = flag;
|
||||
} else {
|
||||
throw_unsup_format!("Unsupported arg {:#x} for `F_SETFD`", flag);
|
||||
}
|
||||
}
|
||||
Ok(0)
|
||||
} else if cmd == this.eval_libc_i32("F_GETFD")? {
|
||||
this.get_handle_and(fd, |handle| Ok(handle.flag))
|
||||
} else {
|
||||
throw_unsup_format!("Unsupported command {:#x}", cmd);
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self, fd_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
if !this.machine.communicate {
|
||||
throw_unsup_format!("`open` not available when isolation is enabled")
|
||||
}
|
||||
|
||||
let fd = this.read_scalar(fd_op)?.to_i32()?;
|
||||
|
||||
this.remove_handle_and(
|
||||
fd,
|
||||
|handle, this| this.consume_result(handle.file.sync_all().map(|_| 0i32)),
|
||||
)
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
fd_op: OpTy<'tcx, Tag>,
|
||||
buf_op: OpTy<'tcx, Tag>,
|
||||
count_op: OpTy<'tcx, Tag>,
|
||||
) -> InterpResult<'tcx, i64> {
|
||||
let this = self.eval_context_mut();
|
||||
|
||||
if !this.machine.communicate {
|
||||
throw_unsup_format!("`open` not available when isolation is enabled")
|
||||
}
|
||||
|
||||
let tcx = &{ this.tcx.tcx };
|
||||
|
||||
let fd = this.read_scalar(fd_op)?.to_i32()?;
|
||||
let buf = this.force_ptr(this.read_scalar(buf_op)?.not_undef()?)?;
|
||||
let count = this.read_scalar(count_op)?.to_usize(&*this.tcx)?;
|
||||
|
||||
// Remove the file handle to avoid borrowing issues
|
||||
this.remove_handle_and(
|
||||
fd,
|
||||
|mut handle, this| {
|
||||
let bytes = handle
|
||||
.file
|
||||
.read(this.memory_mut().get_mut(buf.alloc_id)?.get_bytes_mut(
|
||||
tcx,
|
||||
buf,
|
||||
Size::from_bytes(count),
|
||||
)?)
|
||||
.map(|bytes| bytes as i64);
|
||||
// Reinsert the file handle
|
||||
this.machine.file_handler.handles.insert(fd, handle);
|
||||
this.consume_result(bytes)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper function that gets a `FileHandle` immutable reference and allows to manipulate it
|
||||
/// using `f`.
|
||||
///
|
||||
/// If the `fd` file descriptor does not corresponds to a file, this functions returns `Ok(-1)`
|
||||
/// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor).
|
||||
///
|
||||
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
|
||||
/// functions return different integer types (like `read`, that returns an `i64`)
|
||||
fn get_handle_and<F, T: From<i32>>(&mut self, fd: i32, f: F) -> InterpResult<'tcx, T>
|
||||
where
|
||||
F: Fn(&FileHandle) -> InterpResult<'tcx, T>,
|
||||
{
|
||||
let this = self.eval_context_mut();
|
||||
if let Some(handle) = this.machine.file_handler.handles.get(&fd) {
|
||||
f(handle)
|
||||
} else {
|
||||
this.machine.last_error = this.eval_libc_i32("EBADF")? as u32;
|
||||
Ok((-1).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function that removes a `FileHandle` and allows to manipulate it using the `f`
|
||||
/// closure. This function is quite useful when you need to modify a `FileHandle` but you need
|
||||
/// to modify `MiriEvalContext` at the same time, so you can modify the handle and reinsert it
|
||||
/// using `f`.
|
||||
///
|
||||
/// If the `fd` file descriptor does not corresponds to a file, this functions returns `Ok(-1)`
|
||||
/// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor).
|
||||
///
|
||||
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
|
||||
/// functions return different integer types (like `read`, that returns an `i64`)
|
||||
fn remove_handle_and<F, T: From<i32>>(&mut self, fd: i32, mut f: F) -> InterpResult<'tcx, T>
|
||||
where
|
||||
F: FnMut(FileHandle, &mut MiriEvalContext<'mir, 'tcx>) -> InterpResult<'tcx, T>,
|
||||
{
|
||||
let this = self.eval_context_mut();
|
||||
if let Some(handle) = this.machine.file_handler.handles.remove(&fd) {
|
||||
f(handle, this)
|
||||
} else {
|
||||
this.machine.last_error = this.eval_libc_i32("EBADF")? as u32;
|
||||
Ok((-1).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function that consumes an `std::io::Result<T>` and returns an
|
||||
/// `InterpResult<'tcx,T>::Ok` instead. It is expected that the result can be converted to an
|
||||
/// OS error using `std::io::Error::raw_os_error`.
|
||||
///
|
||||
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
|
||||
/// functions return different integer types (like `read`, that returns an `i64`)
|
||||
fn consume_result<T: From<i32>>(&mut self, result: std::io::Result<T>) -> InterpResult<'tcx, T> {
|
||||
match result {
|
||||
Ok(ok) => Ok(ok),
|
||||
Err(e) => {
|
||||
self.eval_context_mut().machine.last_error = e.raw_os_error().unwrap() as u32;
|
||||
Ok((-1).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ pub mod env;
|
|||
pub mod foreign_items;
|
||||
pub mod intrinsics;
|
||||
pub mod tls;
|
||||
pub mod io;
|
||||
|
||||
use rustc::{mir, ty};
|
||||
|
||||
|
|
|
|||
1
tests/hello.txt
Normal file
1
tests/hello.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Hello, World!
|
||||
13
tests/run-pass/file_read.rs
Normal file
13
tests/run-pass/file_read.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// ignore-windows: File handling is not implemented yet
|
||||
// compile-flags: -Zmiri-disable-isolation
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
|
||||
fn main() {
|
||||
// FIXME: create the file and delete it when `rm` is implemented.
|
||||
let mut file = File::open("./tests/hello.txt").unwrap();
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).unwrap();
|
||||
assert_eq!("Hello, World!\n", contents);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue