Skip to content

Commit 599b329

Browse files
committed
Auto merge of rust-lang#3852 - tiif:rwrefactor, r=RalfJung
Refactor fd read/write This PR passed the responsibility of reading to user supplied buffer and dest place to each implementation of ``FileDescription::read/write/pread/pwrite``. This is part of rust-lang#3665.
2 parents 8243a8c + d70fd88 commit 599b329

File tree

5 files changed

+249
-153
lines changed

5 files changed

+249
-153
lines changed

src/tools/miri/src/shims/unix/fd.rs

+127-65
Original file line numberDiff line numberDiff line change
@@ -25,49 +25,64 @@ pub(crate) enum FlockOp {
2525
pub trait FileDescription: std::fmt::Debug + Any {
2626
fn name(&self) -> &'static str;
2727

28-
/// Reads as much as possible into the given buffer, and returns the number of bytes read.
28+
/// Reads as much as possible into the given buffer `ptr`.
29+
/// `len` indicates how many bytes we should try to read.
30+
/// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error.
2931
fn read<'tcx>(
3032
&self,
3133
_self_ref: &FileDescriptionRef,
3234
_communicate_allowed: bool,
33-
_bytes: &mut [u8],
35+
_ptr: Pointer,
36+
_len: usize,
37+
_dest: &MPlaceTy<'tcx>,
3438
_ecx: &mut MiriInterpCx<'tcx>,
35-
) -> InterpResult<'tcx, io::Result<usize>> {
39+
) -> InterpResult<'tcx> {
3640
throw_unsup_format!("cannot read from {}", self.name());
3741
}
3842

39-
/// Writes as much as possible from the given buffer, and returns the number of bytes written.
43+
/// Writes as much as possible from the given buffer `ptr`.
44+
/// `len` indicates how many bytes we should try to write.
45+
/// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error.
4046
fn write<'tcx>(
4147
&self,
4248
_self_ref: &FileDescriptionRef,
4349
_communicate_allowed: bool,
44-
_bytes: &[u8],
50+
_ptr: Pointer,
51+
_len: usize,
52+
_dest: &MPlaceTy<'tcx>,
4553
_ecx: &mut MiriInterpCx<'tcx>,
46-
) -> InterpResult<'tcx, io::Result<usize>> {
54+
) -> InterpResult<'tcx> {
4755
throw_unsup_format!("cannot write to {}", self.name());
4856
}
4957

50-
/// Reads as much as possible into the given buffer from a given offset,
51-
/// and returns the number of bytes read.
58+
/// Reads as much as possible into the given buffer `ptr` from a given offset.
59+
/// `len` indicates how many bytes we should try to read.
60+
/// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error.
5261
fn pread<'tcx>(
5362
&self,
5463
_communicate_allowed: bool,
55-
_bytes: &mut [u8],
5664
_offset: u64,
65+
_ptr: Pointer,
66+
_len: usize,
67+
_dest: &MPlaceTy<'tcx>,
5768
_ecx: &mut MiriInterpCx<'tcx>,
58-
) -> InterpResult<'tcx, io::Result<usize>> {
69+
) -> InterpResult<'tcx> {
5970
throw_unsup_format!("cannot pread from {}", self.name());
6071
}
6172

62-
/// Writes as much as possible from the given buffer starting at a given offset,
63-
/// and returns the number of bytes written.
73+
/// Writes as much as possible from the given buffer `ptr` starting at a given offset.
74+
/// `ptr` is the pointer to the user supplied read buffer.
75+
/// `len` indicates how many bytes we should try to write.
76+
/// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error.
6477
fn pwrite<'tcx>(
6578
&self,
6679
_communicate_allowed: bool,
67-
_bytes: &[u8],
80+
_ptr: Pointer,
81+
_len: usize,
6882
_offset: u64,
83+
_dest: &MPlaceTy<'tcx>,
6984
_ecx: &mut MiriInterpCx<'tcx>,
70-
) -> InterpResult<'tcx, io::Result<usize>> {
85+
) -> InterpResult<'tcx> {
7186
throw_unsup_format!("cannot pwrite to {}", self.name());
7287
}
7388

@@ -125,14 +140,18 @@ impl FileDescription for io::Stdin {
125140
&self,
126141
_self_ref: &FileDescriptionRef,
127142
communicate_allowed: bool,
128-
bytes: &mut [u8],
129-
_ecx: &mut MiriInterpCx<'tcx>,
130-
) -> InterpResult<'tcx, io::Result<usize>> {
143+
ptr: Pointer,
144+
len: usize,
145+
dest: &MPlaceTy<'tcx>,
146+
ecx: &mut MiriInterpCx<'tcx>,
147+
) -> InterpResult<'tcx> {
148+
let mut bytes = vec![0; len];
131149
if !communicate_allowed {
132150
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
133151
helpers::isolation_abort_error("`read` from stdin")?;
134152
}
135-
Ok(Read::read(&mut { self }, bytes))
153+
let result = Read::read(&mut { self }, &mut bytes);
154+
ecx.return_read_bytes_and_count(ptr, &bytes, result, dest)
136155
}
137156

138157
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -149,9 +168,12 @@ impl FileDescription for io::Stdout {
149168
&self,
150169
_self_ref: &FileDescriptionRef,
151170
_communicate_allowed: bool,
152-
bytes: &[u8],
153-
_ecx: &mut MiriInterpCx<'tcx>,
154-
) -> InterpResult<'tcx, io::Result<usize>> {
171+
ptr: Pointer,
172+
len: usize,
173+
dest: &MPlaceTy<'tcx>,
174+
ecx: &mut MiriInterpCx<'tcx>,
175+
) -> InterpResult<'tcx> {
176+
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
155177
// We allow writing to stderr even with isolation enabled.
156178
let result = Write::write(&mut { self }, bytes);
157179
// Stdout is buffered, flush to make sure it appears on the
@@ -160,8 +182,7 @@ impl FileDescription for io::Stdout {
160182
// the host -- there is no good in adding extra buffering
161183
// here.
162184
io::stdout().flush().unwrap();
163-
164-
Ok(result)
185+
ecx.return_written_byte_count_or_error(result, dest)
165186
}
166187

167188
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -178,12 +199,16 @@ impl FileDescription for io::Stderr {
178199
&self,
179200
_self_ref: &FileDescriptionRef,
180201
_communicate_allowed: bool,
181-
bytes: &[u8],
182-
_ecx: &mut MiriInterpCx<'tcx>,
183-
) -> InterpResult<'tcx, io::Result<usize>> {
202+
ptr: Pointer,
203+
len: usize,
204+
dest: &MPlaceTy<'tcx>,
205+
ecx: &mut MiriInterpCx<'tcx>,
206+
) -> InterpResult<'tcx> {
207+
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
184208
// We allow writing to stderr even with isolation enabled.
185209
// No need to flush, stderr is not buffered.
186-
Ok(Write::write(&mut { self }, bytes))
210+
let result = Write::write(&mut { self }, bytes);
211+
ecx.return_written_byte_count_or_error(result, dest)
187212
}
188213

189214
fn is_tty(&self, communicate_allowed: bool) -> bool {
@@ -204,11 +229,14 @@ impl FileDescription for NullOutput {
204229
&self,
205230
_self_ref: &FileDescriptionRef,
206231
_communicate_allowed: bool,
207-
bytes: &[u8],
208-
_ecx: &mut MiriInterpCx<'tcx>,
209-
) -> InterpResult<'tcx, io::Result<usize>> {
232+
_ptr: Pointer,
233+
len: usize,
234+
dest: &MPlaceTy<'tcx>,
235+
ecx: &mut MiriInterpCx<'tcx>,
236+
) -> InterpResult<'tcx> {
210237
// We just don't write anything, but report to the user that we did.
211-
Ok(Ok(bytes.len()))
238+
let result = Ok(len);
239+
ecx.return_written_byte_count_or_error(result, dest)
212240
}
213241
}
214242

@@ -535,7 +563,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
535563
buf: Pointer,
536564
count: u64,
537565
offset: Option<i128>,
538-
) -> InterpResult<'tcx, Scalar> {
566+
dest: &MPlaceTy<'tcx>,
567+
) -> InterpResult<'tcx> {
539568
let this = self.eval_context_mut();
540569

541570
// Isolation check is done via `FileDescription` trait.
@@ -550,48 +579,35 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
550579
let count = count
551580
.min(u64::try_from(this.target_isize_max()).unwrap())
552581
.min(u64::try_from(isize::MAX).unwrap());
582+
let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
553583
let communicate = this.machine.communicate();
554584

555585
// We temporarily dup the FD to be able to retain mutable access to `this`.
556586
let Some(fd) = this.machine.fds.get(fd_num) else {
557587
trace!("read: FD not found");
558-
return Ok(Scalar::from_target_isize(this.fd_not_found()?, this));
588+
let res: i32 = this.fd_not_found()?;
589+
this.write_int(res, dest)?;
590+
return Ok(());
559591
};
560592

561593
trace!("read: FD mapped to {fd:?}");
562594
// We want to read at most `count` bytes. We are sure that `count` is not negative
563595
// because it was a target's `usize`. Also we are sure that its smaller than
564596
// `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),
597+
598+
match offset {
599+
None => fd.read(&fd, communicate, buf, count, dest, this)?,
568600
Some(offset) => {
569601
let Ok(offset) = u64::try_from(offset) else {
570602
let einval = this.eval_libc("EINVAL");
571603
this.set_last_error(einval)?;
572-
return Ok(Scalar::from_target_isize(-1, this));
604+
this.write_int(-1, dest)?;
605+
return Ok(());
573606
};
574-
fd.pread(communicate, &mut bytes, offset, this)
607+
fd.pread(communicate, offset, buf, count, dest, this)?
575608
}
576609
};
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-
}
610+
Ok(())
595611
}
596612

597613
fn write(
@@ -600,7 +616,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
600616
buf: Pointer,
601617
count: u64,
602618
offset: Option<i128>,
603-
) -> InterpResult<'tcx, Scalar> {
619+
dest: &MPlaceTy<'tcx>,
620+
) -> InterpResult<'tcx> {
604621
let this = self.eval_context_mut();
605622

606623
// Isolation check is done via `FileDescription` trait.
@@ -613,27 +630,72 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
613630
let count = count
614631
.min(u64::try_from(this.target_isize_max()).unwrap())
615632
.min(u64::try_from(isize::MAX).unwrap());
633+
let count = usize::try_from(count).unwrap(); // now it fits in a `usize`
616634
let communicate = this.machine.communicate();
617635

618-
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?.to_owned();
619636
// We temporarily dup the FD to be able to retain mutable access to `this`.
620637
let Some(fd) = this.machine.fds.get(fd_num) else {
621-
return Ok(Scalar::from_target_isize(this.fd_not_found()?, this));
638+
let res: i32 = this.fd_not_found()?;
639+
this.write_int(res, dest)?;
640+
return Ok(());
622641
};
623642

624-
let result = match offset {
625-
None => fd.write(&fd, communicate, &bytes, this),
643+
match offset {
644+
None => fd.write(&fd, communicate, buf, count, dest, this)?,
626645
Some(offset) => {
627646
let Ok(offset) = u64::try_from(offset) else {
628647
let einval = this.eval_libc("EINVAL");
629648
this.set_last_error(einval)?;
630-
return Ok(Scalar::from_target_isize(-1, this));
649+
this.write_int(-1, dest)?;
650+
return Ok(());
631651
};
632-
fd.pwrite(communicate, &bytes, offset, this)
652+
fd.pwrite(communicate, buf, count, offset, dest, this)?
633653
}
634654
};
655+
Ok(())
656+
}
635657

636-
let result = result?.map(|c| i64::try_from(c).unwrap());
637-
Ok(Scalar::from_target_isize(this.try_unwrap_io_result(result)?, this))
658+
/// Helper to implement `FileDescription::read`:
659+
/// `result` should be the return value of some underlying `read` call that used `bytes` as its output buffer.
660+
/// The length of `bytes` must not exceed either the host's or the target's `isize`.
661+
/// If `Result` indicates success, `bytes` is written to `buf` and the size is written to `dest`.
662+
/// Otherwise, `-1` is written to `dest` and the last libc error is set appropriately.
663+
fn return_read_bytes_and_count(
664+
&mut self,
665+
buf: Pointer,
666+
bytes: &[u8],
667+
result: io::Result<usize>,
668+
dest: &MPlaceTy<'tcx>,
669+
) -> InterpResult<'tcx> {
670+
let this = self.eval_context_mut();
671+
match result {
672+
Ok(read_bytes) => {
673+
// If reading to `bytes` did not fail, we write those bytes to the buffer.
674+
// Crucially, if fewer than `bytes.len()` bytes were read, only write
675+
// that much into the output buffer!
676+
this.write_bytes_ptr(buf, bytes[..read_bytes].iter().copied())?;
677+
// The actual read size is always less than what got originally requested so this cannot fail.
678+
this.write_int(u64::try_from(read_bytes).unwrap(), dest)?;
679+
return Ok(());
680+
}
681+
Err(e) => {
682+
this.set_last_error_from_io_error(e)?;
683+
this.write_int(-1, dest)?;
684+
return Ok(());
685+
}
686+
}
687+
}
688+
689+
/// This function writes the number of written bytes (given in `result`) to `dest`, or sets the
690+
/// last libc error and writes -1 to dest.
691+
fn return_written_byte_count_or_error(
692+
&mut self,
693+
result: io::Result<usize>,
694+
dest: &MPlaceTy<'tcx>,
695+
) -> InterpResult<'tcx> {
696+
let this = self.eval_context_mut();
697+
let result = this.try_unwrap_io_result(result.map(|c| i64::try_from(c).unwrap()))?;
698+
this.write_int(result, dest)?;
699+
Ok(())
638700
}
639701
}

src/tools/miri/src/shims/unix/foreign_items.rs

+6-15
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)