Skip to content

Add shims for file handling #962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Oct 1, 2019
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
3 changes: 3 additions & 0 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -110,6 +112,7 @@ impl<'tcx> Evaluator<'tcx> {
last_error: 0,
tls: TlsData::default(),
communicate,
file_handler: Default::default(),
}
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
Expand Down Expand Up @@ -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.
Expand Down
189 changes: 189 additions & 0 deletions src/shims/io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
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)
},
)
}

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())
}
}

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())
}
}

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())
}
}
}
}
1 change: 1 addition & 0 deletions src/shims/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod env;
pub mod foreign_items;
pub mod intrinsics;
pub mod tls;
pub mod io;

use rustc::{mir, ty};

Expand Down
1 change: 1 addition & 0 deletions tests/hello.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello, World!
13 changes: 13 additions & 0 deletions tests/run-pass/file_read.rs
Original file line number Diff line number Diff line change
@@ -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);
}