Skip to content

Commit 5afb453

Browse files
authored
Merge pull request #4174 from RalfJung/read-write-callback
files: make read/write take callback to store result
2 parents 70fd1ee + 16d331b commit 5afb453

File tree

5 files changed

+158
-138
lines changed

5 files changed

+158
-138
lines changed

src/shims/files.rs

+46-55
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::any::Any;
22
use std::collections::BTreeMap;
3-
use std::io::{IsTerminal, Read, SeekFrom, Write};
3+
use std::io::{IsTerminal, SeekFrom, Write};
44
use std::marker::CoercePointee;
55
use std::ops::Deref;
66
use std::rc::{Rc, Weak};
@@ -140,8 +140,8 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
140140
_communicate_allowed: bool,
141141
_ptr: Pointer,
142142
_len: usize,
143-
_dest: &MPlaceTy<'tcx>,
144143
_ecx: &mut MiriInterpCx<'tcx>,
144+
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
145145
) -> InterpResult<'tcx> {
146146
throw_unsup_format!("cannot read from {}", self.name());
147147
}
@@ -154,8 +154,8 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
154154
_communicate_allowed: bool,
155155
_ptr: Pointer,
156156
_len: usize,
157-
_dest: &MPlaceTy<'tcx>,
158157
_ecx: &mut MiriInterpCx<'tcx>,
158+
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
159159
) -> InterpResult<'tcx> {
160160
throw_unsup_format!("cannot write to {}", self.name());
161161
}
@@ -207,19 +207,16 @@ impl FileDescription for io::Stdin {
207207
communicate_allowed: bool,
208208
ptr: Pointer,
209209
len: usize,
210-
dest: &MPlaceTy<'tcx>,
211210
ecx: &mut MiriInterpCx<'tcx>,
211+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
212212
) -> InterpResult<'tcx> {
213-
let mut bytes = vec![0; len];
214213
if !communicate_allowed {
215214
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
216215
helpers::isolation_abort_error("`read` from stdin")?;
217216
}
218-
let result = Read::read(&mut &*self, &mut bytes);
219-
match result {
220-
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
221-
Err(e) => ecx.set_last_error_and_return(e, dest),
222-
}
217+
218+
let result = ecx.read_from_host(&*self, len, ptr)?;
219+
finish.call(ecx, result)
223220
}
224221

225222
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -237,22 +234,19 @@ impl FileDescription for io::Stdout {
237234
_communicate_allowed: bool,
238235
ptr: Pointer,
239236
len: usize,
240-
dest: &MPlaceTy<'tcx>,
241237
ecx: &mut MiriInterpCx<'tcx>,
238+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
242239
) -> InterpResult<'tcx> {
243-
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
244-
// We allow writing to stderr even with isolation enabled.
245-
let result = Write::write(&mut &*self, bytes);
240+
// We allow writing to stdout even with isolation enabled.
241+
let result = ecx.write_to_host(&*self, len, ptr)?;
246242
// Stdout is buffered, flush to make sure it appears on the
247243
// screen. This is the write() syscall of the interpreted
248244
// program, we want it to correspond to a write() syscall on
249245
// the host -- there is no good in adding extra buffering
250246
// here.
251247
io::stdout().flush().unwrap();
252-
match result {
253-
Ok(write_size) => ecx.return_write_success(write_size, dest),
254-
Err(e) => ecx.set_last_error_and_return(e, dest),
255-
}
248+
249+
finish.call(ecx, result)
256250
}
257251

258252
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -270,17 +264,13 @@ impl FileDescription for io::Stderr {
270264
_communicate_allowed: bool,
271265
ptr: Pointer,
272266
len: usize,
273-
dest: &MPlaceTy<'tcx>,
274267
ecx: &mut MiriInterpCx<'tcx>,
268+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
275269
) -> InterpResult<'tcx> {
276-
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
277270
// We allow writing to stderr even with isolation enabled.
271+
let result = ecx.write_to_host(&*self, len, ptr)?;
278272
// No need to flush, stderr is not buffered.
279-
let result = Write::write(&mut &*self, bytes);
280-
match result {
281-
Ok(write_size) => ecx.return_write_success(write_size, dest),
282-
Err(e) => ecx.set_last_error_and_return(e, dest),
283-
}
273+
finish.call(ecx, result)
284274
}
285275

286276
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -302,11 +292,11 @@ impl FileDescription for NullOutput {
302292
_communicate_allowed: bool,
303293
_ptr: Pointer,
304294
len: usize,
305-
dest: &MPlaceTy<'tcx>,
306295
ecx: &mut MiriInterpCx<'tcx>,
296+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
307297
) -> InterpResult<'tcx> {
308298
// We just don't write anything, but report to the user that we did.
309-
ecx.return_write_success(len, dest)
299+
finish.call(ecx, Ok(len))
310300
}
311301
}
312302

@@ -405,40 +395,41 @@ impl FdTable {
405395

406396
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
407397
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
408-
/// Helper to implement `FileDescription::read`:
409-
/// This is only used when `read` is successful.
410-
/// `actual_read_size` should be the return value of some underlying `read` call that used
411-
/// `bytes` as its output buffer.
412-
/// The length of `bytes` must not exceed either the host's or the target's `isize`.
413-
/// `bytes` is written to `buf` and the size is written to `dest`.
414-
fn return_read_success(
398+
/// Read data from a host `Read` type, store the result into machine memory,
399+
/// and return whether that worked.
400+
fn read_from_host(
415401
&mut self,
416-
buf: Pointer,
417-
bytes: &[u8],
418-
actual_read_size: usize,
419-
dest: &MPlaceTy<'tcx>,
420-
) -> InterpResult<'tcx> {
402+
mut file: impl io::Read,
403+
len: usize,
404+
ptr: Pointer,
405+
) -> InterpResult<'tcx, Result<usize, IoError>> {
421406
let this = self.eval_context_mut();
422-
// If reading to `bytes` did not fail, we write those bytes to the buffer.
423-
// Crucially, if fewer than `bytes.len()` bytes were read, only write
424-
// that much into the output buffer!
425-
this.write_bytes_ptr(buf, bytes[..actual_read_size].iter().copied())?;
426407

427-
// The actual read size is always less than what got originally requested so this cannot fail.
428-
this.write_int(u64::try_from(actual_read_size).unwrap(), dest)?;
429-
interp_ok(())
408+
let mut bytes = vec![0; len];
409+
let result = file.read(&mut bytes);
410+
match result {
411+
Ok(read_size) => {
412+
// If reading to `bytes` did not fail, we write those bytes to the buffer.
413+
// Crucially, if fewer than `bytes.len()` bytes were read, only write
414+
// that much into the output buffer!
415+
this.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
416+
interp_ok(Ok(read_size))
417+
}
418+
Err(e) => interp_ok(Err(IoError::HostError(e))),
419+
}
430420
}
431421

432-
/// Helper to implement `FileDescription::write`:
433-
/// This function is only used when `write` is successful, and writes `actual_write_size` to `dest`
434-
fn return_write_success(
422+
/// Write data to a host `Write` type, withthe bytes taken from machine memory.
423+
fn write_to_host(
435424
&mut self,
436-
actual_write_size: usize,
437-
dest: &MPlaceTy<'tcx>,
438-
) -> InterpResult<'tcx> {
425+
mut file: impl io::Write,
426+
len: usize,
427+
ptr: Pointer,
428+
) -> InterpResult<'tcx, Result<usize, IoError>> {
439429
let this = self.eval_context_mut();
440-
// The actual write size is always less than what got originally requested so this cannot fail.
441-
this.write_int(u64::try_from(actual_write_size).unwrap(), dest)?;
442-
interp_ok(())
430+
431+
let bytes = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
432+
let result = file.write(bytes);
433+
interp_ok(result.map_err(IoError::HostError))
443434
}
444435
}

src/shims/unix/fd.rs

+47-7
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ pub trait UnixFileDescription: FileDescription {
3030
_offset: u64,
3131
_ptr: Pointer,
3232
_len: usize,
33-
_dest: &MPlaceTy<'tcx>,
3433
_ecx: &mut MiriInterpCx<'tcx>,
34+
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
3535
) -> InterpResult<'tcx> {
3636
throw_unsup_format!("cannot pread from {}", self.name());
3737
}
@@ -46,8 +46,8 @@ pub trait UnixFileDescription: FileDescription {
4646
_ptr: Pointer,
4747
_len: usize,
4848
_offset: u64,
49-
_dest: &MPlaceTy<'tcx>,
5049
_ecx: &mut MiriInterpCx<'tcx>,
50+
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
5151
) -> InterpResult<'tcx> {
5252
throw_unsup_format!("cannot pwrite to {}", self.name());
5353
}
@@ -236,7 +236,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
236236
let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
237237
let communicate = this.machine.communicate();
238238

239-
// We temporarily dup the FD to be able to retain mutable access to `this`.
239+
// Get the FD.
240240
let Some(fd) = this.machine.fds.get(fd_num) else {
241241
trace!("read: FD not found");
242242
return this.set_last_error_and_return(LibcError("EBADF"), dest);
@@ -247,13 +247,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
247247
// because it was a target's `usize`. Also we are sure that its smaller than
248248
// `usize::MAX` because it is bounded by the host's `isize`.
249249

250+
let finish = {
251+
let dest = dest.clone();
252+
callback!(
253+
@capture<'tcx> {
254+
count: usize,
255+
dest: MPlaceTy<'tcx>,
256+
}
257+
|this, result: Result<usize, IoError>| {
258+
match result {
259+
Ok(read_size) => {
260+
assert!(read_size <= count);
261+
// This must fit since `count` fits.
262+
this.write_int(u64::try_from(read_size).unwrap(), &dest)
263+
}
264+
Err(e) => {
265+
this.set_last_error_and_return(e, &dest)
266+
}
267+
}}
268+
)
269+
};
250270
match offset {
251-
None => fd.read(communicate, buf, count, dest, this)?,
271+
None => fd.read(communicate, buf, count, this, finish)?,
252272
Some(offset) => {
253273
let Ok(offset) = u64::try_from(offset) else {
254274
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
255275
};
256-
fd.as_unix().pread(communicate, offset, buf, count, dest, this)?
276+
fd.as_unix().pread(communicate, offset, buf, count, this, finish)?
257277
}
258278
};
259279
interp_ok(())
@@ -287,13 +307,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
287307
return this.set_last_error_and_return(LibcError("EBADF"), dest);
288308
};
289309

310+
let finish = {
311+
let dest = dest.clone();
312+
callback!(
313+
@capture<'tcx> {
314+
count: usize,
315+
dest: MPlaceTy<'tcx>,
316+
}
317+
|this, result: Result<usize, IoError>| {
318+
match result {
319+
Ok(write_size) => {
320+
assert!(write_size <= count);
321+
// This must fit since `count` fits.
322+
this.write_int(u64::try_from(write_size).unwrap(), &dest)
323+
}
324+
Err(e) => {
325+
this.set_last_error_and_return(e, &dest)
326+
}
327+
}}
328+
)
329+
};
290330
match offset {
291-
None => fd.write(communicate, buf, count, dest, this)?,
331+
None => fd.write(communicate, buf, count, this, finish)?,
292332
Some(offset) => {
293333
let Ok(offset) = u64::try_from(offset) else {
294334
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
295335
};
296-
fd.as_unix().pwrite(communicate, buf, count, offset, dest, this)?
336+
fd.as_unix().pwrite(communicate, buf, count, offset, this, finish)?
297337
}
298338
};
299339
interp_ok(())

src/shims/unix/fs.rs

+22-25
Original file line numberDiff line numberDiff line change
@@ -35,33 +35,27 @@ impl FileDescription for FileHandle {
3535
communicate_allowed: bool,
3636
ptr: Pointer,
3737
len: usize,
38-
dest: &MPlaceTy<'tcx>,
3938
ecx: &mut MiriInterpCx<'tcx>,
39+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
4040
) -> InterpResult<'tcx> {
4141
assert!(communicate_allowed, "isolation should have prevented even opening a file");
42-
let mut bytes = vec![0; len];
43-
let result = (&mut &self.file).read(&mut bytes);
44-
match result {
45-
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
46-
Err(e) => ecx.set_last_error_and_return(e, dest),
47-
}
42+
43+
let result = ecx.read_from_host(&self.file, len, ptr)?;
44+
finish.call(ecx, result)
4845
}
4946

5047
fn write<'tcx>(
5148
self: FileDescriptionRef<Self>,
5249
communicate_allowed: bool,
5350
ptr: Pointer,
5451
len: usize,
55-
dest: &MPlaceTy<'tcx>,
5652
ecx: &mut MiriInterpCx<'tcx>,
53+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
5754
) -> InterpResult<'tcx> {
5855
assert!(communicate_allowed, "isolation should have prevented even opening a file");
59-
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
60-
let result = (&mut &self.file).write(bytes);
61-
match result {
62-
Ok(write_size) => ecx.return_write_success(write_size, dest),
63-
Err(e) => ecx.set_last_error_and_return(e, dest),
64-
}
56+
57+
let result = ecx.write_to_host(&self.file, len, ptr)?;
58+
finish.call(ecx, result)
6559
}
6660

6761
fn seek<'tcx>(
@@ -119,8 +113,8 @@ impl UnixFileDescription for FileHandle {
119113
offset: u64,
120114
ptr: Pointer,
121115
len: usize,
122-
dest: &MPlaceTy<'tcx>,
123116
ecx: &mut MiriInterpCx<'tcx>,
117+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
124118
) -> InterpResult<'tcx> {
125119
assert!(communicate_allowed, "isolation should have prevented even opening a file");
126120
let mut bytes = vec![0; len];
@@ -137,11 +131,17 @@ impl UnixFileDescription for FileHandle {
137131
.expect("failed to restore file position, this shouldn't be possible");
138132
res
139133
};
140-
let result = f();
141-
match result {
142-
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
143-
Err(e) => ecx.set_last_error_and_return(e, dest),
144-
}
134+
let result = match f() {
135+
Ok(read_size) => {
136+
// If reading to `bytes` did not fail, we write those bytes to the buffer.
137+
// Crucially, if fewer than `bytes.len()` bytes were read, only write
138+
// that much into the output buffer!
139+
ecx.write_bytes_ptr(ptr, bytes[..read_size].iter().copied())?;
140+
Ok(read_size)
141+
}
142+
Err(e) => Err(IoError::HostError(e)),
143+
};
144+
finish.call(ecx, result)
145145
}
146146

147147
fn pwrite<'tcx>(
@@ -150,8 +150,8 @@ impl UnixFileDescription for FileHandle {
150150
ptr: Pointer,
151151
len: usize,
152152
offset: u64,
153-
dest: &MPlaceTy<'tcx>,
154153
ecx: &mut MiriInterpCx<'tcx>,
154+
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
155155
) -> InterpResult<'tcx> {
156156
assert!(communicate_allowed, "isolation should have prevented even opening a file");
157157
// Emulates pwrite using seek + write + seek to restore cursor position.
@@ -169,10 +169,7 @@ impl UnixFileDescription for FileHandle {
169169
res
170170
};
171171
let result = f();
172-
match result {
173-
Ok(write_size) => ecx.return_write_success(write_size, dest),
174-
Err(e) => ecx.set_last_error_and_return(e, dest),
175-
}
172+
finish.call(ecx, result.map_err(IoError::HostError))
176173
}
177174

178175
fn flock<'tcx>(

0 commit comments

Comments
 (0)