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
20 changes: 20 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" => {
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
179 changes: 179 additions & 0 deletions src/shims/io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;

use crate::stacked_borrows::Tag;
use crate::*;

#[derive(Default)]
pub struct FileHandler {
files: HashMap<i32, File>,
flags: HashMap<i32, i32>,
low: i32,
}

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_path_scalar(&["libc", "O_RDONLY"])?
.unwrap()
.to_i32()?
&& flag
!= this
.eval_path_scalar(&["libc", "O_CLOEXEC"])?
.unwrap()
.to_i32()?
{
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))?;

match File::open(path) {
Ok(file) => {
let mut fh = &mut this.machine.file_handler;
fh.low += 1;
fh.files.insert(fh.low, file);
fh.flags.insert(fh.low, flag);
Ok(fh.low)
}

Err(e) => {
this.machine.last_error = e.raw_os_error().unwrap() as u32;
Ok(-1)
}
}
}

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_path_scalar(&["libc", "F_SETFD"])?
.unwrap()
.to_i32()?
{
let flag = this.read_scalar(arg_op.unwrap())?.to_i32()?;
this.machine.file_handler.flags.insert(fd, flag);
Ok(0)
} else if cmd
== this
.eval_path_scalar(&["libc", "F_GETFD"])?
.unwrap()
.to_i32()?
{
if let Some(flag) = this.machine.file_handler.flags.get(&fd) {
Ok(*flag)
} else {
this.machine.last_error = this
.eval_path_scalar(&["libc", "EBADF"])?
.unwrap()
.to_u32()?;
Ok(-1)
}
} 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()?;

if let Some(file) = this.machine.file_handler.files.remove(&fd) {
match file.sync_all() {
Ok(()) => Ok(0),
Err(e) => {
this.machine.last_error = e.raw_os_error().unwrap() as u32;
Ok(-1)
}
}
} else {
this.machine.last_error = this
.eval_path_scalar(&["libc", "EBADF"])?
.unwrap()
.to_u32()?;
Ok(-1)
}
}

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)?;

if let Some(file) = this.machine.file_handler.files.get_mut(&fd) {
let mut bytes = vec![0; count as usize];
match file.read(&mut bytes) {
Ok(read_bytes) => {
bytes.truncate(read_bytes);

this.memory_mut()
.get_mut(buf.alloc_id)?
.write_bytes(tcx, buf, &bytes)?;

Ok(read_bytes as i64)
}
Err(e) => {
this.machine.last_error = e.raw_os_error().unwrap() as u32;
Ok(-1)
}
}
} else {
this.machine.last_error = this
.eval_path_scalar(&["libc", "EBADF"])?
.unwrap()
.to_u32()?;
Ok(-1)
}
}
}
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 intrinsics;
pub mod tls;
pub mod dlsym;
pub mod env;
pub mod io;

use rustc::{ty, mir};

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!
12 changes: 12 additions & 0 deletions tests/run-pass/file_read.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// ignore-windows: File handling is not implemented yet
// compile-flags: -Zmiri-disable-isolation

use std::fs::File;
use std::io::Read;

fn main() {
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);
}