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

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

pub struct FileHandler {
files: HashMap<i32, File>,
flags: HashMap<i32, i32>,
low: i32,
}

impl Default for FileHandler {
fn default() -> Self {
FileHandler {
files: Default::default(),
flags: 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))?;

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_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(old_flag) = this.machine.file_handler.flags.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")? {
if let Some(flag) = this.machine.file_handler.flags.get(&fd) {
Ok(*flag)
} else {
this.machine.last_error = this.eval_libc_i32("EBADF")? as 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_libc_i32("EBADF")? as 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_libc_i32("EBADF")? as 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 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);
}