Skip to content

Commit 1037f69

Browse files
committed
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
2 parents 07ac102 + d0509d7 commit 1037f69

File tree

7 files changed

+260
-0
lines changed

7 files changed

+260
-0
lines changed

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub use crate::shims::intrinsics::EvalContextExt as IntrinsicsEvalContextExt;
3434
pub use crate::shims::tls::{EvalContextExt as TlsEvalContextExt, TlsData};
3535
pub use crate::shims::dlsym::{Dlsym, EvalContextExt as DlsymEvalContextExt};
3636
pub use crate::shims::env::{EnvVars, EvalContextExt as EnvEvalContextExt};
37+
pub use crate::shims::io::{FileHandler, EvalContextExt as FileEvalContextExt};
3738
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
3839
pub use crate::range_map::RangeMap;
3940
pub use crate::helpers::{EvalContextExt as HelpersEvalContextExt};

src/machine.rs

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ pub struct Evaluator<'tcx> {
9696
/// If enabled, the `env_vars` field is populated with the host env vars during initialization
9797
/// and random number generation is delegated to the host.
9898
pub(crate) communicate: bool,
99+
100+
pub(crate) file_handler: FileHandler,
99101
}
100102

101103
impl<'tcx> Evaluator<'tcx> {
@@ -110,6 +112,7 @@ impl<'tcx> Evaluator<'tcx> {
110112
last_error: 0,
111113
tls: TlsData::default(),
112114
communicate,
115+
file_handler: Default::default(),
113116
}
114117
}
115118
}

src/shims/foreign_items.rs

+28
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
446446
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
447447
}
448448

449+
"open" | "open64" => {
450+
let result = this.open(args[0], args[1])?;
451+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
452+
}
453+
454+
"fcntl" => {
455+
let result = this.fcntl(args[0], args[1], args.get(2).cloned())?;
456+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
457+
}
458+
459+
"close" | "close$NOCANCEL" => {
460+
let result = this.close(args[0])?;
461+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
462+
}
463+
464+
"read" => {
465+
let result = this.read(args[0], args[1], args[2])?;
466+
this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?;
467+
}
468+
449469
"write" => {
450470
let fd = this.read_scalar(args[0])?.to_i32()?;
451471
let buf = this.read_scalar(args[1])?.not_undef()?;
@@ -929,6 +949,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
929949
}
930950
return Ok(None);
931951
}
952+
953+
fn eval_libc_i32(&mut self, name: &str) -> InterpResult<'tcx, i32> {
954+
self
955+
.eval_context_mut()
956+
.eval_path_scalar(&["libc", name])?
957+
.ok_or_else(|| err_unsup_format!("Path libc::{} cannot be resolved.", name).into())
958+
.and_then(|scalar| scalar.to_i32())
959+
}
932960
}
933961

934962
// Shims the linux 'getrandom()' syscall.

src/shims/io.rs

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
use std::collections::HashMap;
2+
use std::fs::File;
3+
use std::io::Read;
4+
5+
use rustc::ty::layout::Size;
6+
7+
use crate::stacked_borrows::Tag;
8+
use crate::*;
9+
10+
pub struct FileHandle {
11+
file: File,
12+
flag: i32,
13+
}
14+
15+
pub struct FileHandler {
16+
handles: HashMap<i32, FileHandle>,
17+
low: i32,
18+
}
19+
20+
impl Default for FileHandler {
21+
fn default() -> Self {
22+
FileHandler {
23+
handles: Default::default(),
24+
// 0, 1 and 2 are reserved for stdin, stdout and stderr
25+
low: 3,
26+
}
27+
}
28+
}
29+
30+
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
31+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
32+
fn open(
33+
&mut self,
34+
path_op: OpTy<'tcx, Tag>,
35+
flag_op: OpTy<'tcx, Tag>,
36+
) -> InterpResult<'tcx, i32> {
37+
let this = self.eval_context_mut();
38+
39+
if !this.machine.communicate {
40+
throw_unsup_format!("`open` not available when isolation is enabled")
41+
}
42+
43+
let flag = this.read_scalar(flag_op)?.to_i32()?;
44+
45+
if flag != this.eval_libc_i32("O_RDONLY")? && flag != this.eval_libc_i32("O_CLOEXEC")? {
46+
throw_unsup_format!("Unsupported flag {:#x}", flag);
47+
}
48+
49+
let path_bytes = this
50+
.memory()
51+
.read_c_str(this.read_scalar(path_op)?.not_undef()?)?;
52+
let path = std::str::from_utf8(path_bytes)
53+
.map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", path_bytes))?;
54+
let fd = File::open(path).map(|file| {
55+
let mut fh = &mut this.machine.file_handler;
56+
fh.low += 1;
57+
fh.handles.insert(fh.low, FileHandle { file, flag });
58+
fh.low
59+
});
60+
61+
this.consume_result(fd)
62+
}
63+
64+
fn fcntl(
65+
&mut self,
66+
fd_op: OpTy<'tcx, Tag>,
67+
cmd_op: OpTy<'tcx, Tag>,
68+
arg_op: Option<OpTy<'tcx, Tag>>,
69+
) -> InterpResult<'tcx, i32> {
70+
let this = self.eval_context_mut();
71+
72+
if !this.machine.communicate {
73+
throw_unsup_format!("`open` not available when isolation is enabled")
74+
}
75+
76+
let fd = this.read_scalar(fd_op)?.to_i32()?;
77+
let cmd = this.read_scalar(cmd_op)?.to_i32()?;
78+
79+
if cmd == this.eval_libc_i32("F_SETFD")? {
80+
// This does not affect the file itself. Certain flags might require changing the file
81+
// or the way it is accessed somehow.
82+
let flag = this.read_scalar(arg_op.unwrap())?.to_i32()?;
83+
// The only usage of this in stdlib at the moment is to enable the `FD_CLOEXEC` flag.
84+
let fd_cloexec = this.eval_libc_i32("FD_CLOEXEC")?;
85+
if let Some(FileHandle { flag: old_flag, .. }) =
86+
this.machine.file_handler.handles.get_mut(&fd)
87+
{
88+
if flag ^ *old_flag == fd_cloexec {
89+
*old_flag = flag;
90+
} else {
91+
throw_unsup_format!("Unsupported arg {:#x} for `F_SETFD`", flag);
92+
}
93+
}
94+
Ok(0)
95+
} else if cmd == this.eval_libc_i32("F_GETFD")? {
96+
this.get_handle_and(fd, |handle| Ok(handle.flag))
97+
} else {
98+
throw_unsup_format!("Unsupported command {:#x}", cmd);
99+
}
100+
}
101+
102+
fn close(&mut self, fd_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
103+
let this = self.eval_context_mut();
104+
105+
if !this.machine.communicate {
106+
throw_unsup_format!("`open` not available when isolation is enabled")
107+
}
108+
109+
let fd = this.read_scalar(fd_op)?.to_i32()?;
110+
111+
this.remove_handle_and(
112+
fd,
113+
|handle, this| this.consume_result(handle.file.sync_all().map(|_| 0i32)),
114+
)
115+
}
116+
117+
fn read(
118+
&mut self,
119+
fd_op: OpTy<'tcx, Tag>,
120+
buf_op: OpTy<'tcx, Tag>,
121+
count_op: OpTy<'tcx, Tag>,
122+
) -> InterpResult<'tcx, i64> {
123+
let this = self.eval_context_mut();
124+
125+
if !this.machine.communicate {
126+
throw_unsup_format!("`open` not available when isolation is enabled")
127+
}
128+
129+
let tcx = &{ this.tcx.tcx };
130+
131+
let fd = this.read_scalar(fd_op)?.to_i32()?;
132+
let buf = this.force_ptr(this.read_scalar(buf_op)?.not_undef()?)?;
133+
let count = this.read_scalar(count_op)?.to_usize(&*this.tcx)?;
134+
135+
// Remove the file handle to avoid borrowing issues
136+
this.remove_handle_and(
137+
fd,
138+
|mut handle, this| {
139+
let bytes = handle
140+
.file
141+
.read(this.memory_mut().get_mut(buf.alloc_id)?.get_bytes_mut(
142+
tcx,
143+
buf,
144+
Size::from_bytes(count),
145+
)?)
146+
.map(|bytes| bytes as i64);
147+
// Reinsert the file handle
148+
this.machine.file_handler.handles.insert(fd, handle);
149+
this.consume_result(bytes)
150+
},
151+
)
152+
}
153+
154+
/// Helper function that gets a `FileHandle` immutable reference and allows to manipulate it
155+
/// using `f`.
156+
///
157+
/// If the `fd` file descriptor does not corresponds to a file, this functions returns `Ok(-1)`
158+
/// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor).
159+
///
160+
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
161+
/// functions return different integer types (like `read`, that returns an `i64`)
162+
fn get_handle_and<F, T: From<i32>>(&mut self, fd: i32, f: F) -> InterpResult<'tcx, T>
163+
where
164+
F: Fn(&FileHandle) -> InterpResult<'tcx, T>,
165+
{
166+
let this = self.eval_context_mut();
167+
if let Some(handle) = this.machine.file_handler.handles.get(&fd) {
168+
f(handle)
169+
} else {
170+
this.machine.last_error = this.eval_libc_i32("EBADF")? as u32;
171+
Ok((-1).into())
172+
}
173+
}
174+
175+
/// Helper function that removes a `FileHandle` and allows to manipulate it using the `f`
176+
/// closure. This function is quite useful when you need to modify a `FileHandle` but you need
177+
/// to modify `MiriEvalContext` at the same time, so you can modify the handle and reinsert it
178+
/// using `f`.
179+
///
180+
/// If the `fd` file descriptor does not corresponds to a file, this functions returns `Ok(-1)`
181+
/// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor).
182+
///
183+
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
184+
/// functions return different integer types (like `read`, that returns an `i64`)
185+
fn remove_handle_and<F, T: From<i32>>(&mut self, fd: i32, mut f: F) -> InterpResult<'tcx, T>
186+
where
187+
F: FnMut(FileHandle, &mut MiriEvalContext<'mir, 'tcx>) -> InterpResult<'tcx, T>,
188+
{
189+
let this = self.eval_context_mut();
190+
if let Some(handle) = this.machine.file_handler.handles.remove(&fd) {
191+
f(handle, this)
192+
} else {
193+
this.machine.last_error = this.eval_libc_i32("EBADF")? as u32;
194+
Ok((-1).into())
195+
}
196+
}
197+
198+
/// Helper function that consumes an `std::io::Result<T>` and returns an
199+
/// `InterpResult<'tcx,T>::Ok` instead. It is expected that the result can be converted to an
200+
/// OS error using `std::io::Error::raw_os_error`.
201+
///
202+
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
203+
/// functions return different integer types (like `read`, that returns an `i64`)
204+
fn consume_result<T: From<i32>>(&mut self, result: std::io::Result<T>) -> InterpResult<'tcx, T> {
205+
match result {
206+
Ok(ok) => Ok(ok),
207+
Err(e) => {
208+
self.eval_context_mut().machine.last_error = e.raw_os_error().unwrap() as u32;
209+
Ok((-1).into())
210+
}
211+
}
212+
}
213+
}

src/shims/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod env;
33
pub mod foreign_items;
44
pub mod intrinsics;
55
pub mod tls;
6+
pub mod io;
67

78
use rustc::{mir, ty};
89

tests/hello.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello, World!

tests/run-pass/file_read.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// ignore-windows: File handling is not implemented yet
2+
// compile-flags: -Zmiri-disable-isolation
3+
4+
use std::fs::File;
5+
use std::io::Read;
6+
7+
fn main() {
8+
// FIXME: create the file and delete it when `rm` is implemented.
9+
let mut file = File::open("./tests/hello.txt").unwrap();
10+
let mut contents = String::new();
11+
file.read_to_string(&mut contents).unwrap();
12+
assert_eq!("Hello, World!\n", contents);
13+
}

0 commit comments

Comments
 (0)