Skip to content

Commit b0de1e9

Browse files
committed
Auto merge of #973 - christianpoveda:write-shim, r=oli-obk
Enable file writing r? @oli-obk
2 parents 1037f69 + 6c36a8c commit b0de1e9

File tree

4 files changed

+110
-44
lines changed

4 files changed

+110
-44
lines changed

src/shims/foreign_items.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -494,9 +494,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
494494
Err(_) => -1,
495495
}
496496
} else {
497-
eprintln!("Miri: Ignored output to FD {}", fd);
498-
// Pretend it all went well.
499-
n as i64
497+
this.write(args[0], args[1], args[2])?
500498
};
501499
// Now, `result` is the value we return back to the program.
502500
this.write_scalar(

src/shims/io.rs

+91-34
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::collections::HashMap;
2-
use std::fs::File;
3-
use std::io::Read;
2+
use std::fs::{File, OpenOptions};
3+
use std::io::{Read, Write};
44

55
use rustc::ty::layout::Size;
66

@@ -42,16 +42,38 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
4242

4343
let flag = this.read_scalar(flag_op)?.to_i32()?;
4444

45-
if flag != this.eval_libc_i32("O_RDONLY")? && flag != this.eval_libc_i32("O_CLOEXEC")? {
46-
throw_unsup_format!("Unsupported flag {:#x}", flag);
45+
let mut options = OpenOptions::new();
46+
47+
// The first two bits of the flag correspond to the access mode of the file in linux.
48+
let access_mode = flag & 0b11;
49+
50+
if access_mode == this.eval_libc_i32("O_RDONLY")? {
51+
options.read(true);
52+
} else if access_mode == this.eval_libc_i32("O_WRONLY")? {
53+
options.write(true);
54+
} else if access_mode == this.eval_libc_i32("O_RDWR")? {
55+
options.read(true).write(true);
56+
} else {
57+
throw_unsup_format!("Unsupported access mode {:#x}", access_mode);
58+
}
59+
60+
if flag & this.eval_libc_i32("O_APPEND")? != 0 {
61+
options.append(true);
62+
}
63+
if flag & this.eval_libc_i32("O_TRUNC")? != 0 {
64+
options.truncate(true);
65+
}
66+
if flag & this.eval_libc_i32("O_CREAT")? != 0 {
67+
options.create(true);
4768
}
4869

4970
let path_bytes = this
5071
.memory()
5172
.read_c_str(this.read_scalar(path_op)?.not_undef()?)?;
5273
let path = std::str::from_utf8(path_bytes)
5374
.map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", path_bytes))?;
54-
let fd = File::open(path).map(|file| {
75+
76+
let fd = options.open(path).map(|file| {
5577
let mut fh = &mut this.machine.file_handler;
5678
fh.low += 1;
5779
fh.handles.insert(fh.low, FileHandle { file, flag });
@@ -70,7 +92,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
7092
let this = self.eval_context_mut();
7193

7294
if !this.machine.communicate {
73-
throw_unsup_format!("`open` not available when isolation is enabled")
95+
throw_unsup_format!("`fcntl` not available when isolation is enabled")
7496
}
7597

7698
let fd = this.read_scalar(fd_op)?.to_i32()?;
@@ -103,15 +125,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
103125
let this = self.eval_context_mut();
104126

105127
if !this.machine.communicate {
106-
throw_unsup_format!("`open` not available when isolation is enabled")
128+
throw_unsup_format!("`close` not available when isolation is enabled")
107129
}
108130

109131
let fd = this.read_scalar(fd_op)?.to_i32()?;
110132

111-
this.remove_handle_and(
112-
fd,
113-
|handle, this| this.consume_result(handle.file.sync_all().map(|_| 0i32)),
114-
)
133+
this.remove_handle_and(fd, |handle, this| {
134+
this.consume_result(handle.file.sync_all().map(|_| 0i32))
135+
})
115136
}
116137

117138
fn read(
@@ -123,38 +144,71 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
123144
let this = self.eval_context_mut();
124145

125146
if !this.machine.communicate {
126-
throw_unsup_format!("`open` not available when isolation is enabled")
147+
throw_unsup_format!("`read` not available when isolation is enabled")
127148
}
128149

129150
let tcx = &{ this.tcx.tcx };
130151

131-
let fd = this.read_scalar(fd_op)?.to_i32()?;
132-
let buf = this.force_ptr(this.read_scalar(buf_op)?.not_undef()?)?;
133152
let count = this.read_scalar(count_op)?.to_usize(&*this.tcx)?;
153+
// Reading zero bytes should not change `buf`
154+
if count == 0 {
155+
return Ok(0);
156+
}
157+
let fd = this.read_scalar(fd_op)?.to_i32()?;
158+
let buf_scalar = this.read_scalar(buf_op)?.not_undef()?;
134159

135160
// 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-
)
161+
this.remove_handle_and(fd, |mut handle, this| {
162+
// Don't use `?` to avoid returning before reinserting the handle
163+
let bytes = this.force_ptr(buf_scalar).and_then(|buf| {
164+
this.memory_mut()
165+
.get_mut(buf.alloc_id)?
166+
.get_bytes_mut(tcx, buf, Size::from_bytes(count))
167+
.map(|buffer| handle.file.read(buffer))
168+
});
169+
// Reinsert the file handle
170+
this.machine.file_handler.handles.insert(fd, handle);
171+
this.consume_result(bytes?.map(|bytes| bytes as i64))
172+
})
173+
}
174+
175+
fn write(
176+
&mut self,
177+
fd_op: OpTy<'tcx, Tag>,
178+
buf_op: OpTy<'tcx, Tag>,
179+
count_op: OpTy<'tcx, Tag>,
180+
) -> InterpResult<'tcx, i64> {
181+
let this = self.eval_context_mut();
182+
183+
if !this.machine.communicate {
184+
throw_unsup_format!("`write` not available when isolation is enabled")
185+
}
186+
187+
let tcx = &{ this.tcx.tcx };
188+
189+
let count = this.read_scalar(count_op)?.to_usize(&*this.tcx)?;
190+
// Writing zero bytes should not change `buf`
191+
if count == 0 {
192+
return Ok(0);
193+
}
194+
let fd = this.read_scalar(fd_op)?.to_i32()?;
195+
let buf = this.force_ptr(this.read_scalar(buf_op)?.not_undef()?)?;
196+
197+
this.remove_handle_and(fd, |mut handle, this| {
198+
let bytes = this.memory().get(buf.alloc_id).and_then(|alloc| {
199+
alloc
200+
.get_bytes(tcx, buf, Size::from_bytes(count))
201+
.map(|bytes| handle.file.write(bytes).map(|bytes| bytes as i64))
202+
});
203+
this.machine.file_handler.handles.insert(fd, handle);
204+
this.consume_result(bytes?)
205+
})
152206
}
153207

154208
/// Helper function that gets a `FileHandle` immutable reference and allows to manipulate it
155-
/// using `f`.
209+
/// using the `f` closure.
156210
///
157-
/// If the `fd` file descriptor does not corresponds to a file, this functions returns `Ok(-1)`
211+
/// If the `fd` file descriptor does not correspond to a file, this functions returns `Ok(-1)`
158212
/// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor).
159213
///
160214
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
@@ -177,7 +231,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
177231
/// to modify `MiriEvalContext` at the same time, so you can modify the handle and reinsert it
178232
/// using `f`.
179233
///
180-
/// If the `fd` file descriptor does not corresponds to a file, this functions returns `Ok(-1)`
234+
/// If the `fd` file descriptor does not correspond to a file, this functions returns `Ok(-1)`
181235
/// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor).
182236
///
183237
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
@@ -201,7 +255,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
201255
///
202256
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
203257
/// 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> {
258+
fn consume_result<T: From<i32>>(
259+
&mut self,
260+
result: std::io::Result<T>,
261+
) -> InterpResult<'tcx, T> {
205262
match result {
206263
Ok(ok) => Ok(ok),
207264
Err(e) => {

tests/hello.txt

-1
This file was deleted.

tests/run-pass/file_read.rs

+18-6
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@
22
// compile-flags: -Zmiri-disable-isolation
33

44
use std::fs::File;
5-
use std::io::Read;
5+
use std::io::{Read, Write};
66

77
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);
8+
// FIXME: remove the file and delete it when `rm` is implemented.
9+
let path = "./tests/hello.txt";
10+
let bytes = b"Hello, World!\n";
11+
// Test creating, writing and closing a file (closing is tested when `file` is dropped).
12+
let mut file = File::create(path).unwrap();
13+
// Writing 0 bytes should not change the file contents.
14+
file.write(&mut []).unwrap();
15+
16+
file.write(bytes).unwrap();
17+
// Test opening, reading and closing a file.
18+
let mut file = File::open(path).unwrap();
19+
let mut contents = Vec::new();
20+
// Reading 0 bytes should not move the file pointer.
21+
file.read(&mut []).unwrap();
22+
// Reading until EOF should get the whole text.
23+
file.read_to_end(&mut contents).unwrap();
24+
assert_eq!(bytes, contents.as_slice());
1325
}

0 commit comments

Comments
 (0)