Skip to content

Commit de515da

Browse files
committed
Refactor FileDescription::read/write
1 parent 519f941 commit de515da

File tree

5 files changed

+206
-113
lines changed

5 files changed

+206
-113
lines changed

src/shims/unix/fd.rs

Lines changed: 98 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ pub trait FileDescription: std::fmt::Debug + Any {
2626
fn name(&self) -> &'static str;
2727

2828
/// Reads as much as possible into the given buffer, and returns the number of bytes read.
29+
/// `ptr` is the pointer to user supplied read buffer.
2930
fn read<'tcx>(
3031
&self,
3132
_self_ref: &FileDescriptionRef,
3233
_communicate_allowed: bool,
33-
_bytes: &mut [u8],
34+
_ptr: Pointer,
35+
_len: u64,
36+
_dest: &MPlaceTy<'tcx>,
3437
_ecx: &mut MiriInterpCx<'tcx>,
35-
) -> InterpResult<'tcx, io::Result<usize>> {
38+
) -> InterpResult<'tcx> {
3639
throw_unsup_format!("cannot read from {}", self.name());
3740
}
3841

@@ -42,8 +45,9 @@ pub trait FileDescription: std::fmt::Debug + Any {
4245
_self_ref: &FileDescriptionRef,
4346
_communicate_allowed: bool,
4447
_bytes: &[u8],
48+
_dest: &MPlaceTy<'tcx>,
4549
_ecx: &mut MiriInterpCx<'tcx>,
46-
) -> InterpResult<'tcx, io::Result<usize>> {
50+
) -> InterpResult<'tcx> {
4751
throw_unsup_format!("cannot write to {}", self.name());
4852
}
4953

@@ -52,10 +56,12 @@ pub trait FileDescription: std::fmt::Debug + Any {
5256
fn pread<'tcx>(
5357
&self,
5458
_communicate_allowed: bool,
55-
_bytes: &mut [u8],
5659
_offset: u64,
60+
_ptr: Pointer,
61+
_len: u64,
62+
_dest: &MPlaceTy<'tcx>,
5763
_ecx: &mut MiriInterpCx<'tcx>,
58-
) -> InterpResult<'tcx, io::Result<usize>> {
64+
) -> InterpResult<'tcx> {
5965
throw_unsup_format!("cannot pread from {}", self.name());
6066
}
6167

@@ -66,8 +72,9 @@ pub trait FileDescription: std::fmt::Debug + Any {
6672
_communicate_allowed: bool,
6773
_bytes: &[u8],
6874
_offset: u64,
75+
_dest: &MPlaceTy<'tcx>,
6976
_ecx: &mut MiriInterpCx<'tcx>,
70-
) -> InterpResult<'tcx, io::Result<usize>> {
77+
) -> InterpResult<'tcx> {
7178
throw_unsup_format!("cannot pwrite to {}", self.name());
7279
}
7380

@@ -125,14 +132,18 @@ impl FileDescription for io::Stdin {
125132
&self,
126133
_self_ref: &FileDescriptionRef,
127134
communicate_allowed: bool,
128-
bytes: &mut [u8],
129-
_ecx: &mut MiriInterpCx<'tcx>,
130-
) -> InterpResult<'tcx, io::Result<usize>> {
135+
ptr: Pointer,
136+
len: u64,
137+
dest: &MPlaceTy<'tcx>,
138+
ecx: &mut MiriInterpCx<'tcx>,
139+
) -> InterpResult<'tcx> {
140+
let mut bytes = vec![0; usize::try_from(len).unwrap()];
131141
if !communicate_allowed {
132142
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
133143
helpers::isolation_abort_error("`read` from stdin")?;
134144
}
135-
Ok(Read::read(&mut { self }, bytes))
145+
let result = Read::read(&mut { self }, &mut bytes);
146+
ecx.return_read_bytes_and_count(ptr, bytes, result, dest)
136147
}
137148

138149
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -150,8 +161,9 @@ impl FileDescription for io::Stdout {
150161
_self_ref: &FileDescriptionRef,
151162
_communicate_allowed: bool,
152163
bytes: &[u8],
153-
_ecx: &mut MiriInterpCx<'tcx>,
154-
) -> InterpResult<'tcx, io::Result<usize>> {
164+
dest: &MPlaceTy<'tcx>,
165+
ecx: &mut MiriInterpCx<'tcx>,
166+
) -> InterpResult<'tcx> {
155167
// We allow writing to stderr even with isolation enabled.
156168
let result = Write::write(&mut { self }, bytes);
157169
// Stdout is buffered, flush to make sure it appears on the
@@ -160,8 +172,7 @@ impl FileDescription for io::Stdout {
160172
// the host -- there is no good in adding extra buffering
161173
// here.
162174
io::stdout().flush().unwrap();
163-
164-
Ok(result)
175+
ecx.return_written_byte_count_or_error(result, dest)
165176
}
166177

167178
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -179,11 +190,13 @@ impl FileDescription for io::Stderr {
179190
_self_ref: &FileDescriptionRef,
180191
_communicate_allowed: bool,
181192
bytes: &[u8],
182-
_ecx: &mut MiriInterpCx<'tcx>,
183-
) -> InterpResult<'tcx, io::Result<usize>> {
193+
dest: &MPlaceTy<'tcx>,
194+
ecx: &mut MiriInterpCx<'tcx>,
195+
) -> InterpResult<'tcx> {
184196
// We allow writing to stderr even with isolation enabled.
185197
// No need to flush, stderr is not buffered.
186-
Ok(Write::write(&mut { self }, bytes))
198+
let result = Write::write(&mut { self }, bytes);
199+
ecx.return_written_byte_count_or_error(result, dest)
187200
}
188201

189202
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -205,10 +218,12 @@ impl FileDescription for NullOutput {
205218
_self_ref: &FileDescriptionRef,
206219
_communicate_allowed: bool,
207220
bytes: &[u8],
208-
_ecx: &mut MiriInterpCx<'tcx>,
209-
) -> InterpResult<'tcx, io::Result<usize>> {
221+
dest: &MPlaceTy<'tcx>,
222+
ecx: &mut MiriInterpCx<'tcx>,
223+
) -> InterpResult<'tcx> {
210224
// We just don't write anything, but report to the user that we did.
211-
Ok(Ok(bytes.len()))
225+
let result = Ok(bytes.len());
226+
ecx.return_written_byte_count_or_error(result, dest)
212227
}
213228
}
214229

@@ -535,7 +550,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
535550
buf: Pointer,
536551
count: u64,
537552
offset: Option<i128>,
538-
) -> InterpResult<'tcx, Scalar> {
553+
dest: &MPlaceTy<'tcx>,
554+
) -> InterpResult<'tcx> {
539555
let this = self.eval_context_mut();
540556

541557
// Isolation check is done via `FileDescription` trait.
@@ -555,43 +571,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
555571
// We temporarily dup the FD to be able to retain mutable access to `this`.
556572
let Some(fd) = this.machine.fds.get(fd_num) else {
557573
trace!("read: FD not found");
558-
return Ok(Scalar::from_target_isize(this.fd_not_found()?, this));
574+
let res: i32 = this.fd_not_found()?;
575+
this.write_int(res, dest)?;
576+
return Ok(());
559577
};
560578

561579
trace!("read: FD mapped to {fd:?}");
562580
// We want to read at most `count` bytes. We are sure that `count` is not negative
563581
// because it was a target's `usize`. Also we are sure that its smaller than
564582
// `usize::MAX` because it is bounded by the host's `isize`.
565-
let mut bytes = vec![0; usize::try_from(count).unwrap()];
566-
let result = match offset {
567-
None => fd.read(&fd, communicate, &mut bytes, this),
583+
584+
match offset {
585+
None => fd.read(&fd, communicate, buf, count, dest, this)?,
568586
Some(offset) => {
569587
let Ok(offset) = u64::try_from(offset) else {
570588
let einval = this.eval_libc("EINVAL");
571589
this.set_last_error(einval)?;
572-
return Ok(Scalar::from_target_isize(-1, this));
590+
this.write_int(-1, dest)?;
591+
return Ok(());
573592
};
574-
fd.pread(communicate, &mut bytes, offset, this)
593+
fd.pread(communicate, offset, buf, count, dest, this)?
575594
}
576595
};
577-
578-
// `File::read` never returns a value larger than `count`, so this cannot fail.
579-
match result?.map(|c| i64::try_from(c).unwrap()) {
580-
Ok(read_bytes) => {
581-
// If reading to `bytes` did not fail, we write those bytes to the buffer.
582-
// Crucially, if fewer than `bytes.len()` bytes were read, only write
583-
// that much into the output buffer!
584-
this.write_bytes_ptr(
585-
buf,
586-
bytes[..usize::try_from(read_bytes).unwrap()].iter().copied(),
587-
)?;
588-
Ok(Scalar::from_target_isize(read_bytes, this))
589-
}
590-
Err(e) => {
591-
this.set_last_error_from_io_error(e)?;
592-
Ok(Scalar::from_target_isize(-1, this))
593-
}
594-
}
596+
Ok(())
595597
}
596598

597599
fn write(
@@ -600,7 +602,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
600602
buf: Pointer,
601603
count: u64,
602604
offset: Option<i128>,
603-
) -> InterpResult<'tcx, Scalar> {
605+
dest: &MPlaceTy<'tcx>,
606+
) -> InterpResult<'tcx> {
604607
let this = self.eval_context_mut();
605608

606609
// Isolation check is done via `FileDescription` trait.
@@ -618,22 +621,64 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
618621
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?.to_owned();
619622
// We temporarily dup the FD to be able to retain mutable access to `this`.
620623
let Some(fd) = this.machine.fds.get(fd_num) else {
621-
return Ok(Scalar::from_target_isize(this.fd_not_found()?, this));
624+
let res: i32 = this.fd_not_found()?;
625+
this.write_int(res, dest)?;
626+
return Ok(());
622627
};
623628

624-
let result = match offset {
625-
None => fd.write(&fd, communicate, &bytes, this),
629+
match offset {
630+
None => fd.write(&fd, communicate, &bytes, dest, this)?,
626631
Some(offset) => {
627632
let Ok(offset) = u64::try_from(offset) else {
628633
let einval = this.eval_libc("EINVAL");
629634
this.set_last_error(einval)?;
630-
return Ok(Scalar::from_target_isize(-1, this));
635+
this.write_int(-1, dest)?;
636+
return Ok(());
631637
};
632-
fd.pwrite(communicate, &bytes, offset, this)
638+
fd.pwrite(communicate, &bytes, offset, dest, this)?
633639
}
634640
};
641+
Ok(())
642+
}
635643

636-
let result = result?.map(|c| i64::try_from(c).unwrap());
637-
Ok(Scalar::from_target_isize(this.try_unwrap_io_result(result)?, this))
644+
/// This function either writes to the user supplied buffer and to dest place, or sets the
645+
/// last libc error and writes -1 to dest.
646+
fn return_read_bytes_and_count(
647+
&mut self,
648+
buf: Pointer,
649+
bytes: Vec<u8>,
650+
result: io::Result<usize>,
651+
dest: &MPlaceTy<'tcx>,
652+
) -> InterpResult<'tcx> {
653+
let this = self.eval_context_mut();
654+
match result {
655+
Ok(read_bytes) => {
656+
// If reading to `bytes` did not fail, we write those bytes to the buffer.
657+
// Crucially, if fewer than `bytes.len()` bytes were read, only write
658+
// that much into the output buffer!
659+
this.write_bytes_ptr(buf, bytes[..read_bytes].iter().copied())?;
660+
// The actual read size is always lesser than `count` so this cannot fail.
661+
this.write_int(u64::try_from(read_bytes).unwrap(), dest)?;
662+
return Ok(());
663+
}
664+
Err(e) => {
665+
this.set_last_error_from_io_error(e)?;
666+
this.write_int(-1, dest)?;
667+
return Ok(());
668+
}
669+
}
670+
}
671+
672+
/// This function writes the number of written bytes to dest place, or sets the
673+
/// last libc error and writes -1 to dest.
674+
fn return_written_byte_count_or_error(
675+
&mut self,
676+
result: io::Result<usize>,
677+
dest: &MPlaceTy<'tcx>,
678+
) -> InterpResult<'tcx> {
679+
let this = self.eval_context_mut();
680+
let result = this.try_unwrap_io_result(result.map(|c| i64::try_from(c).unwrap()))?;
681+
this.write_int(result, dest)?;
682+
Ok(())
638683
}
639684
}

src/shims/unix/foreign_items.rs

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
9292
let fd = this.read_scalar(fd)?.to_i32()?;
9393
let buf = this.read_pointer(buf)?;
9494
let count = this.read_target_usize(count)?;
95-
let result = this.read(fd, buf, count, None)?;
96-
this.write_scalar(result, dest)?;
95+
this.read(fd, buf, count, None, dest)?;
9796
}
9897
"write" => {
9998
let [fd, buf, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
10099
let fd = this.read_scalar(fd)?.to_i32()?;
101100
let buf = this.read_pointer(buf)?;
102101
let count = this.read_target_usize(n)?;
103102
trace!("Called write({:?}, {:?}, {:?})", fd, buf, count);
104-
let result = this.write(fd, buf, count, None)?;
105-
// Now, `result` is the value we return back to the program.
106-
this.write_scalar(result, dest)?;
103+
this.write(fd, buf, count, None, dest)?;
107104
}
108105
"pread" => {
109106
let [fd, buf, count, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
110107
let fd = this.read_scalar(fd)?.to_i32()?;
111108
let buf = this.read_pointer(buf)?;
112109
let count = this.read_target_usize(count)?;
113110
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?;
114-
let result = this.read(fd, buf, count, Some(offset))?;
115-
this.write_scalar(result, dest)?;
111+
this.read(fd, buf, count, Some(offset), dest)?;
116112
}
117113
"pwrite" => {
118114
let [fd, buf, n, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
@@ -121,18 +117,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
121117
let count = this.read_target_usize(n)?;
122118
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?;
123119
trace!("Called pwrite({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
124-
let result = this.write(fd, buf, count, Some(offset))?;
125-
// Now, `result` is the value we return back to the program.
126-
this.write_scalar(result, dest)?;
120+
this.write(fd, buf, count, Some(offset), dest)?;
127121
}
128122
"pread64" => {
129123
let [fd, buf, count, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
130124
let fd = this.read_scalar(fd)?.to_i32()?;
131125
let buf = this.read_pointer(buf)?;
132126
let count = this.read_target_usize(count)?;
133127
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?;
134-
let result = this.read(fd, buf, count, Some(offset))?;
135-
this.write_scalar(result, dest)?;
128+
this.read(fd, buf, count, Some(offset), dest)?;
136129
}
137130
"pwrite64" => {
138131
let [fd, buf, n, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
@@ -141,9 +134,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
141134
let count = this.read_target_usize(n)?;
142135
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?;
143136
trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
144-
let result = this.write(fd, buf, count, Some(offset))?;
145-
// Now, `result` is the value we return back to the program.
146-
this.write_scalar(result, dest)?;
137+
this.write(fd, buf, count, Some(offset), dest)?;
147138
}
148139
"close" => {
149140
let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;

0 commit comments

Comments
 (0)