Skip to content

Commit 5c57c82

Browse files
committed
Add pread and pwrite shims
1 parent 7184789 commit 5c57c82

File tree

4 files changed

+257
-0
lines changed

4 files changed

+257
-0
lines changed

src/shims/unix/fd.rs

+136
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>(
@@ -463,4 +487,116 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
463487

464488
this.try_unwrap_io_result(result)
465489
}
490+
491+
fn pread(
492+
&mut self,
493+
fd: i32,
494+
buf: Pointer,
495+
count: u64,
496+
offset: i128,
497+
) -> InterpResult<'tcx, i64> {
498+
let this = self.eval_context_mut();
499+
500+
// Isolation check is done via `FileDescriptor` trait.
501+
502+
trace!("Reading from FD {}, size {}, offset {}", fd, count, offset);
503+
504+
// Check that the *entire* buffer is actually valid memory.
505+
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
506+
507+
// We cap the number of read bytes to the largest value that we are able to fit in both the
508+
// host's and target's `isize`. This saves us from having to handle overflows later.
509+
let count = count
510+
.min(u64::try_from(this.target_isize_max()).unwrap())
511+
.min(u64::try_from(isize::MAX).unwrap());
512+
let communicate = this.machine.communicate();
513+
514+
// We temporarily dup the FD to be able to retain mutable access to `this`.
515+
let Some(file_descriptor) = this.machine.fds.dup(fd) else {
516+
trace!("pread: FD not found");
517+
return this.fd_not_found();
518+
};
519+
520+
trace!("pread: FD mapped to {:?}", file_descriptor);
521+
// We want to read at most `count` bytes. We are sure that `count` is not negative
522+
// because it was a target's `usize`. Also we are sure that its smaller than
523+
// `usize::MAX` because it is bounded by the host's `isize`.
524+
let mut bytes = vec![0; usize::try_from(count).unwrap()];
525+
let offset = match offset.try_into() {
526+
Ok(offset) => offset,
527+
Err(_) => {
528+
let einval = this.eval_libc("EINVAL");
529+
this.set_last_error(einval)?;
530+
return Ok(-1);
531+
}
532+
};
533+
// `File::pread` never returns a value larger than `count`,
534+
// so this cannot fail.
535+
let result = file_descriptor
536+
.borrow_mut()
537+
.pread(communicate, &mut bytes, offset, this)?
538+
.map(|c| i64::try_from(c).unwrap());
539+
drop(file_descriptor);
540+
541+
match result {
542+
Ok(read_bytes) => {
543+
// If reading to `bytes` did not fail, we write those bytes to the buffer.
544+
// Crucially, if fewer than `bytes.len()` bytes were read, only write
545+
// that much into the output buffer!
546+
this.write_bytes_ptr(
547+
buf,
548+
bytes[..usize::try_from(read_bytes).unwrap()].iter().copied(),
549+
)?;
550+
Ok(read_bytes)
551+
}
552+
Err(e) => {
553+
this.set_last_error_from_io_error(e)?;
554+
Ok(-1)
555+
}
556+
}
557+
}
558+
559+
fn pwrite(
560+
&mut self,
561+
fd: i32,
562+
buf: Pointer,
563+
count: u64,
564+
offset: i128,
565+
) -> InterpResult<'tcx, i64> {
566+
let this = self.eval_context_mut();
567+
568+
// Isolation check is done via `FileDescriptor` trait.
569+
570+
// Check that the *entire* buffer is actually valid memory.
571+
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
572+
573+
// We cap the number of written bytes to the largest value that we are able to fit in both the
574+
// host's and target's `isize`. This saves us from having to handle overflows later.
575+
let count = count
576+
.min(u64::try_from(this.target_isize_max()).unwrap())
577+
.min(u64::try_from(isize::MAX).unwrap());
578+
let communicate = this.machine.communicate();
579+
580+
let bytes = this.read_bytes_ptr_strip_provenance(buf, Size::from_bytes(count))?.to_owned();
581+
let offset = match offset.try_into() {
582+
Ok(offset) => offset,
583+
Err(_) => {
584+
let einval = this.eval_libc("EINVAL");
585+
this.set_last_error(einval)?;
586+
return Ok(-1);
587+
}
588+
};
589+
// We temporarily dup the FD to be able to retain mutable access to `this`.
590+
let Some(file_descriptor) = this.machine.fds.dup(fd) else {
591+
return this.fd_not_found();
592+
};
593+
594+
let result = file_descriptor
595+
.borrow_mut()
596+
.pwrite(communicate, &bytes, offset, this)?
597+
.map(|c| i64::try_from(c).unwrap());
598+
drop(file_descriptor);
599+
600+
this.try_unwrap_io_result(result)
601+
}
466602
}

src/shims/unix/foreign_items.rs

+40
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,46 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
105105
// Now, `result` is the value we return back to the program.
106106
this.write_scalar(Scalar::from_target_isize(result, this), dest)?;
107107
}
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.pread(fd, buf, count, 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.pwrite(fd, buf, count, 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.pread(fd, buf, count, 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.pwrite(fd, buf, count, offset)?;
145+
// Now, `result` is the value we return back to the program.
146+
this.write_scalar(Scalar::from_target_isize(result, this), dest)?;
147+
}
108148
"close" => {
109149
let [fd] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
110150
let result = this.close(fd)?;

src/shims/unix/fs.rs

+59
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,65 @@ 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+
#[cfg(unix)]
61+
{
62+
use std::os::unix::fs::FileExt;
63+
Ok(self.file.read_at(bytes, offset))
64+
}
65+
// Emulate pread using seek + read + seek to restore cursor position.
66+
// Correctness of this emulation relies on sequential nature of Miri execution.
67+
#[cfg(not(unix))]
68+
{
69+
let mut f = || {
70+
use io::{Seek, SeekFrom};
71+
let cursor_pos = self.file.stream_position()?;
72+
self.file.seek(SeekFrom::Start(offset))?;
73+
let n = self.file.read(bytes)?;
74+
self.file.seek(SeekFrom::Start(cursor_pos))?;
75+
Ok(n)
76+
};
77+
Ok(f())
78+
}
79+
}
80+
81+
#[cfg(unix)]
82+
fn pwrite<'tcx>(
83+
&mut self,
84+
communicate_allowed: bool,
85+
bytes: &[u8],
86+
offset: u64,
87+
_ecx: &mut MiriInterpCx<'tcx>,
88+
) -> InterpResult<'tcx, io::Result<usize>> {
89+
assert!(communicate_allowed, "isolation should have prevented even opening a file");
90+
#[cfg(unix)]
91+
{
92+
use std::os::unix::fs::FileExt;
93+
Ok(self.file.write_at(bytes, offset))
94+
}
95+
// Emulate pwrite using seek + write + seek to restore cursor position.
96+
// Correctness of this emulation relies on sequential nature of Miri execution.
97+
#[cfg(not(unix))]
98+
{
99+
let mut f = || {
100+
use io::{Seek, SeekFrom};
101+
let cursor_pos = self.file.stream_position()?;
102+
self.file.seek(SeekFrom::Start(offset))?;
103+
let n = self.file.write(bytes)?;
104+
self.file.seek(SeekFrom::Start(cursor_pos))?;
105+
Ok(n)
106+
};
107+
Ok(f())
108+
}
109+
}
110+
52111
fn seek<'tcx>(
53112
&mut self,
54113
communicate_allowed: bool,

tests/pass-dep/tempfile.rs

+22
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod utils;
99
fn main() {
1010
test_tempfile();
1111
test_tempfile_in();
12+
test_tempfile_ext().unwrap();
1213
}
1314

1415
fn test_tempfile() {
@@ -19,3 +20,24 @@ fn test_tempfile_in() {
1920
let dir_path = utils::tmp();
2021
tempfile::tempfile_in(dir_path).unwrap();
2122
}
23+
24+
fn test_tempfile_ext() -> std::io::Result<()> {
25+
use std::io::Write;
26+
use std::os::unix::fs::FileExt;
27+
28+
let mut f = tempfile::tempfile()?;
29+
f.write_all(b"hello world")?;
30+
let mut buf = [0u8; 3];
31+
f.read_exact_at(&mut buf, 2)?;
32+
assert_eq!(&buf, b"llo");
33+
f.read_exact_at(&mut buf, 6)?;
34+
assert_eq!(&buf, b"wor");
35+
36+
f.write_all_at(b" mo", 6)?;
37+
38+
let mut buf = [0u8; 11];
39+
f.read_exact_at(&mut buf, 0)?;
40+
assert_eq!(&buf, b"hello mold");
41+
42+
Ok(())
43+
}

0 commit comments

Comments
 (0)