Skip to content

Commit aaff0eb

Browse files
committed
Auto merge of #3743 - newpavlov:pread_pwrite, r=RalfJung
Add `pread` and `pwrite` shims Requested in #2057.
2 parents b8753be + e472d33 commit aaff0eb

File tree

4 files changed

+205
-21
lines changed

4 files changed

+205
-21
lines changed

src/shims/unix/fd.rs

+74-19
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@ pub trait FileDescription: std::fmt::Debug + Any {
3636
throw_unsup_format!("cannot write to {}", self.name());
3737
}
3838

39+
/// Reads as much as possible into the given buffer from a given offset,
40+
/// and returns the number of bytes read.
41+
fn pread<'tcx>(
42+
&mut self,
43+
_communicate_allowed: bool,
44+
_bytes: &mut [u8],
45+
_offset: u64,
46+
_ecx: &mut MiriInterpCx<'tcx>,
47+
) -> InterpResult<'tcx, io::Result<usize>> {
48+
throw_unsup_format!("cannot pread from {}", self.name());
49+
}
50+
51+
/// Writes as much as possible from the given buffer starting at a given offset,
52+
/// and returns the number of bytes written.
53+
fn pwrite<'tcx>(
54+
&mut self,
55+
_communicate_allowed: bool,
56+
_bytes: &[u8],
57+
_offset: u64,
58+
_ecx: &mut MiriInterpCx<'tcx>,
59+
) -> InterpResult<'tcx, io::Result<usize>> {
60+
throw_unsup_format!("cannot pwrite to {}", self.name());
61+
}
62+
3963
/// Seeks to the given offset (which can be relative to the beginning, end, or current position).
4064
/// Returns the new position from the start of the stream.
4165
fn seek<'tcx>(
@@ -380,7 +404,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
380404
Ok((-1).into())
381405
}
382406

383-
fn read(&mut self, fd: i32, buf: Pointer, count: u64) -> InterpResult<'tcx, i64> {
407+
/// Read data from `fd` into buffer specified by `buf` and `count`.
408+
///
409+
/// If `offset` is `None`, reads data from current cursor position associated with `fd`
410+
/// and updates cursor position on completion. Otherwise, reads from the specified offset
411+
/// and keeps the cursor unchanged.
412+
fn read(
413+
&mut self,
414+
fd: i32,
415+
buf: Pointer,
416+
count: u64,
417+
offset: Option<i128>,
418+
) -> InterpResult<'tcx, i64> {
384419
let this = self.eval_context_mut();
385420

386421
// Isolation check is done via `FileDescriptor` trait.
@@ -398,25 +433,31 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
398433
let communicate = this.machine.communicate();
399434

400435
// We temporarily dup the FD to be able to retain mutable access to `this`.
401-
let Some(file_descriptor) = this.machine.fds.dup(fd) else {
436+
let Some(fd) = this.machine.fds.dup(fd) else {
402437
trace!("read: FD not found");
403438
return this.fd_not_found();
404439
};
405440

406-
trace!("read: FD mapped to {:?}", file_descriptor);
441+
trace!("read: FD mapped to {fd:?}");
407442
// We want to read at most `count` bytes. We are sure that `count` is not negative
408443
// because it was a target's `usize`. Also we are sure that its smaller than
409444
// `usize::MAX` because it is bounded by the host's `isize`.
410445
let mut bytes = vec![0; usize::try_from(count).unwrap()];
411-
// `File::read` never returns a value larger than `count`,
412-
// so this cannot fail.
413-
let result = file_descriptor
414-
.borrow_mut()
415-
.read(communicate, &mut bytes, this)?
416-
.map(|c| i64::try_from(c).unwrap());
417-
drop(file_descriptor);
418-
419-
match result {
446+
let result = match offset {
447+
None => fd.borrow_mut().read(communicate, &mut bytes, this),
448+
Some(offset) => {
449+
let Ok(offset) = u64::try_from(offset) else {
450+
let einval = this.eval_libc("EINVAL");
451+
this.set_last_error(einval)?;
452+
return Ok(-1);
453+
};
454+
fd.borrow_mut().pread(communicate, &mut bytes, offset, this)
455+
}
456+
};
457+
drop(fd);
458+
459+
// `File::read` never returns a value larger than `count`, so this cannot fail.
460+
match result?.map(|c| i64::try_from(c).unwrap()) {
420461
Ok(read_bytes) => {
421462
// If reading to `bytes` did not fail, we write those bytes to the buffer.
422463
// Crucially, if fewer than `bytes.len()` bytes were read, only write
@@ -434,7 +475,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
434475
}
435476
}
436477

437-
fn write(&mut self, fd: i32, buf: Pointer, count: u64) -> InterpResult<'tcx, i64> {
478+
fn write(
479+
&mut self,
480+
fd: i32,
481+
buf: Pointer,
482+
count: u64,
483+
offset: Option<i128>,
484+
) -> InterpResult<'tcx, i64> {
438485
let this = self.eval_context_mut();
439486

440487
// Isolation check is done via `FileDescriptor` trait.
@@ -451,16 +498,24 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
451498

452499
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?.to_owned();
453500
// We temporarily dup the FD to be able to retain mutable access to `this`.
454-
let Some(file_descriptor) = this.machine.fds.dup(fd) else {
501+
let Some(fd) = this.machine.fds.dup(fd) else {
455502
return this.fd_not_found();
456503
};
457504

458-
let result = file_descriptor
459-
.borrow_mut()
460-
.write(communicate, &bytes, this)?
461-
.map(|c| i64::try_from(c).unwrap());
462-
drop(file_descriptor);
505+
let result = match offset {
506+
None => fd.borrow_mut().write(communicate, &bytes, this),
507+
Some(offset) => {
508+
let Ok(offset) = u64::try_from(offset) else {
509+
let einval = this.eval_libc("EINVAL");
510+
this.set_last_error(einval)?;
511+
return Ok(-1);
512+
};
513+
fd.borrow_mut().pwrite(communicate, &bytes, offset, this)
514+
}
515+
};
516+
drop(fd);
463517

518+
let result = result?.map(|c| i64::try_from(c).unwrap());
464519
this.try_unwrap_io_result(result)
465520
}
466521
}

src/shims/unix/foreign_items.rs

+42-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ 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)?;
95+
let result = this.read(fd, buf, count, None)?;
9696
this.write_scalar(Scalar::from_target_isize(result, this), dest)?;
9797
}
9898
"write" => {
@@ -101,7 +101,47 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
101101
let buf = this.read_pointer(buf)?;
102102
let count = this.read_target_usize(n)?;
103103
trace!("Called write({:?}, {:?}, {:?})", fd, buf, count);
104-
let result = this.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(Scalar::from_target_isize(result, this), dest)?;
107+
}
108+
"pread" => {
109+
let [fd, buf, count, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
110+
let fd = this.read_scalar(fd)?.to_i32()?;
111+
let buf = this.read_pointer(buf)?;
112+
let count = this.read_target_usize(count)?;
113+
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(Scalar::from_target_isize(result, this), dest)?;
116+
}
117+
"pwrite" => {
118+
let [fd, buf, n, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
119+
let fd = this.read_scalar(fd)?.to_i32()?;
120+
let buf = this.read_pointer(buf)?;
121+
let count = this.read_target_usize(n)?;
122+
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?;
123+
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(Scalar::from_target_isize(result, this), dest)?;
127+
}
128+
"pread64" => {
129+
let [fd, buf, count, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
130+
let fd = this.read_scalar(fd)?.to_i32()?;
131+
let buf = this.read_pointer(buf)?;
132+
let count = this.read_target_usize(count)?;
133+
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(Scalar::from_target_isize(result, this), dest)?;
136+
}
137+
"pwrite64" => {
138+
let [fd, buf, n, offset] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
139+
let fd = this.read_scalar(fd)?.to_i32()?;
140+
let buf = this.read_pointer(buf)?;
141+
let count = this.read_target_usize(n)?;
142+
let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off64_t").size)?;
143+
trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
144+
let result = this.write(fd, buf, count, Some(offset))?;
105145
// Now, `result` is the value we return back to the program.
106146
this.write_scalar(Scalar::from_target_isize(result, this), dest)?;
107147
}

src/shims/unix/fs.rs

+48
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,54 @@ impl FileDescription for FileHandle {
4949
Ok(self.file.write(bytes))
5050
}
5151

52+
fn pread<'tcx>(
53+
&mut self,
54+
communicate_allowed: bool,
55+
bytes: &mut [u8],
56+
offset: u64,
57+
_ecx: &mut MiriInterpCx<'tcx>,
58+
) -> InterpResult<'tcx, io::Result<usize>> {
59+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
60+
// Emulates pread using seek + read + seek to restore cursor position.
61+
// Correctness of this emulation relies on sequential nature of Miri execution.
62+
// The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`.
63+
let mut f = || {
64+
let cursor_pos = self.file.stream_position()?;
65+
self.file.seek(SeekFrom::Start(offset))?;
66+
let res = self.file.read(bytes);
67+
// Attempt to restore cursor position even if the read has failed
68+
self.file
69+
.seek(SeekFrom::Start(cursor_pos))
70+
.expect("failed to restore file position, this shouldn't be possible");
71+
res
72+
};
73+
Ok(f())
74+
}
75+
76+
fn pwrite<'tcx>(
77+
&mut self,
78+
communicate_allowed: bool,
79+
bytes: &[u8],
80+
offset: u64,
81+
_ecx: &mut MiriInterpCx<'tcx>,
82+
) -> InterpResult<'tcx, io::Result<usize>> {
83+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
84+
// Emulates pwrite using seek + write + seek to restore cursor position.
85+
// Correctness of this emulation relies on sequential nature of Miri execution.
86+
// The closure is used to emulate `try` block, since we "bubble" `io::Error` using `?`.
87+
let mut f = || {
88+
let cursor_pos = self.file.stream_position()?;
89+
self.file.seek(SeekFrom::Start(offset))?;
90+
let res = self.file.write(bytes);
91+
// Attempt to restore cursor position even if the write has failed
92+
self.file
93+
.seek(SeekFrom::Start(cursor_pos))
94+
.expect("failed to restore file position, this shouldn't be possible");
95+
res
96+
};
97+
Ok(f())
98+
}
99+
52100
fn seek<'tcx>(
53101
&mut self,
54102
communicate_allowed: bool,

tests/pass/shims/fs.rs

+41
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ fn main() {
3030
test_directory();
3131
test_canonicalize();
3232
test_from_raw_os_error();
33+
#[cfg(unix)]
34+
test_pread_pwrite();
3335
}
3436

3537
fn test_path_conversion() {
@@ -303,3 +305,42 @@ fn test_from_raw_os_error() {
303305
// Make sure we can also format this.
304306
let _ = format!("{error:?}");
305307
}
308+
309+
#[cfg(unix)]
310+
fn test_pread_pwrite() {
311+
use std::os::unix::fs::FileExt;
312+
313+
let bytes = b"hello world";
314+
let path = utils::prepare_with_content("miri_test_fs_pread_pwrite.txt", bytes);
315+
let mut f = OpenOptions::new().read(true).write(true).open(path).unwrap();
316+
317+
let mut buf1 = [0u8; 3];
318+
f.seek(SeekFrom::Start(5)).unwrap();
319+
320+
// Check that we get expected result after seek
321+
f.read_exact(&mut buf1).unwrap();
322+
assert_eq!(&buf1, b" wo");
323+
f.seek(SeekFrom::Start(5)).unwrap();
324+
325+
// Check pread
326+
f.read_exact_at(&mut buf1, 2).unwrap();
327+
assert_eq!(&buf1, b"llo");
328+
f.read_exact_at(&mut buf1, 6).unwrap();
329+
assert_eq!(&buf1, b"wor");
330+
331+
// Ensure that cursor position is not changed
332+
f.read_exact(&mut buf1).unwrap();
333+
assert_eq!(&buf1, b" wo");
334+
f.seek(SeekFrom::Start(5)).unwrap();
335+
336+
// Check pwrite
337+
f.write_all_at(b" mo", 6).unwrap();
338+
339+
let mut buf2 = [0u8; 11];
340+
f.read_exact_at(&mut buf2, 0).unwrap();
341+
assert_eq!(&buf2, b"hello mold");
342+
343+
// Ensure that cursor position is not changed
344+
f.read_exact(&mut buf1).unwrap();
345+
assert_eq!(&buf1, b" m");
346+
}

0 commit comments

Comments
 (0)