Skip to content

Commit 0d66db9

Browse files
committed
Auto merge of rust-lang#3609 - tiif:feat/socketpair, r=RalfJung
Add socketpair shim Fixes rust-lang#3442 Design proposal: https://hackmd.io/`@tiif/Skhc1t0-C`
2 parents ad85a20 + c28e606 commit 0d66db9

File tree

6 files changed

+366
-15
lines changed

6 files changed

+366
-15
lines changed

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

+186-15
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
1+
use std::cell::RefCell;
2+
use std::collections::VecDeque;
13
use std::io;
4+
use std::io::{Error, ErrorKind, Read};
5+
use std::rc::{Rc, Weak};
26

37
use crate::shims::unix::*;
4-
use crate::*;
8+
use crate::{concurrency::VClock, *};
59

610
use self::fd::FileDescriptor;
711

12+
/// The maximum capacity of the socketpair buffer in bytes.
13+
/// This number is arbitrary as the value can always
14+
/// be configured in the real system.
15+
const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 212992;
16+
817
/// Pair of connected sockets.
9-
///
10-
/// We currently don't allow sending any data through this pair, so this can be just a dummy.
1118
#[derive(Debug)]
12-
struct SocketPair;
19+
struct SocketPair {
20+
// By making the write link weak, a `write` can detect when all readers are
21+
// gone, and trigger EPIPE as appropriate.
22+
writebuf: Weak<RefCell<Buffer>>,
23+
readbuf: Rc<RefCell<Buffer>>,
24+
is_nonblock: bool,
25+
}
26+
27+
#[derive(Debug)]
28+
struct Buffer {
29+
buf: VecDeque<u8>,
30+
clock: VClock,
31+
/// Indicates if there is at least one active writer to this buffer.
32+
/// If all writers of this buffer are dropped, buf_has_writer becomes false and we
33+
/// indicate EOF instead of blocking.
34+
buf_has_writer: bool,
35+
}
1336

1437
impl FileDescription for SocketPair {
1538
fn name(&self) -> &'static str {
@@ -20,17 +43,102 @@ impl FileDescription for SocketPair {
2043
self: Box<Self>,
2144
_communicate_allowed: bool,
2245
) -> InterpResult<'tcx, io::Result<()>> {
46+
// This is used to signal socketfd of other side that there is no writer to its readbuf.
47+
// If the upgrade fails, there is no need to update as all read ends have been dropped.
48+
if let Some(writebuf) = self.writebuf.upgrade() {
49+
writebuf.borrow_mut().buf_has_writer = false;
50+
};
2351
Ok(Ok(()))
2452
}
53+
54+
fn read<'tcx>(
55+
&mut self,
56+
_communicate_allowed: bool,
57+
bytes: &mut [u8],
58+
ecx: &mut MiriInterpCx<'tcx>,
59+
) -> InterpResult<'tcx, io::Result<usize>> {
60+
let request_byte_size = bytes.len();
61+
let mut readbuf = self.readbuf.borrow_mut();
62+
63+
// Always succeed on read size 0.
64+
if request_byte_size == 0 {
65+
return Ok(Ok(0));
66+
}
67+
68+
if readbuf.buf.is_empty() {
69+
if !readbuf.buf_has_writer {
70+
// Socketpair with no writer and empty buffer.
71+
// 0 bytes successfully read indicates end-of-file.
72+
return Ok(Ok(0));
73+
} else {
74+
if self.is_nonblock {
75+
// Non-blocking socketpair with writer and empty buffer.
76+
// https://linux.die.net/man/2/read
77+
// EAGAIN or EWOULDBLOCK can be returned for socket,
78+
// POSIX.1-2001 allows either error to be returned for this case.
79+
// Since there is no ErrorKind for EAGAIN, WouldBlock is used.
80+
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
81+
} else {
82+
// Blocking socketpair with writer and empty buffer.
83+
// FIXME: blocking is currently not supported
84+
throw_unsup_format!("socketpair read: blocking isn't supported yet");
85+
}
86+
}
87+
}
88+
89+
// Synchronize with all previous writes to this buffer.
90+
// FIXME: this over-synchronizes; a more precise approach would be to
91+
// only sync with the writes whose data we will read.
92+
ecx.acquire_clock(&readbuf.clock);
93+
// Do full read / partial read based on the space available.
94+
// Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
95+
let actual_read_size = readbuf.buf.read(bytes).unwrap();
96+
return Ok(Ok(actual_read_size));
97+
}
98+
99+
fn write<'tcx>(
100+
&mut self,
101+
_communicate_allowed: bool,
102+
bytes: &[u8],
103+
ecx: &mut MiriInterpCx<'tcx>,
104+
) -> InterpResult<'tcx, io::Result<usize>> {
105+
let write_size = bytes.len();
106+
// Always succeed on write size 0.
107+
// ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
108+
if write_size == 0 {
109+
return Ok(Ok(0));
110+
}
111+
112+
let Some(writebuf) = self.writebuf.upgrade() else {
113+
// If the upgrade from Weak to Rc fails, it indicates that all read ends have been
114+
// closed.
115+
return Ok(Err(Error::from(ErrorKind::BrokenPipe)));
116+
};
117+
let mut writebuf = writebuf.borrow_mut();
118+
let data_size = writebuf.buf.len();
119+
let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.checked_sub(data_size).unwrap();
120+
if available_space == 0 {
121+
if self.is_nonblock {
122+
// Non-blocking socketpair with a full buffer.
123+
return Ok(Err(Error::from(ErrorKind::WouldBlock)));
124+
} else {
125+
// Blocking socketpair with a full buffer.
126+
throw_unsup_format!("socketpair write: blocking isn't supported yet");
127+
}
128+
}
129+
// Remember this clock so `read` can synchronize with us.
130+
if let Some(clock) = &ecx.release_clock() {
131+
writebuf.clock.join(clock);
132+
}
133+
// Do full write / partial write based on the space available.
134+
let actual_write_size = write_size.min(available_space);
135+
writebuf.buf.extend(&bytes[..actual_write_size]);
136+
return Ok(Ok(actual_write_size));
137+
}
25138
}
26139

27140
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
28141
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
29-
/// Currently this function this function is a stub. Eventually we need to
30-
/// properly implement an FD type for sockets and have this function create
31-
/// two sockets and associated FDs such that writing to one will produce
32-
/// data that can be read from the other.
33-
///
34142
/// For more information on the arguments see the socketpair manpage:
35143
/// <https://linux.die.net/man/2/socketpair>
36144
fn socketpair(
@@ -42,17 +150,80 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
42150
) -> InterpResult<'tcx, Scalar> {
43151
let this = self.eval_context_mut();
44152

45-
let _domain = this.read_scalar(domain)?.to_i32()?;
46-
let _type_ = this.read_scalar(type_)?.to_i32()?;
47-
let _protocol = this.read_scalar(protocol)?.to_i32()?;
153+
let domain = this.read_scalar(domain)?.to_i32()?;
154+
let mut type_ = this.read_scalar(type_)?.to_i32()?;
155+
let protocol = this.read_scalar(protocol)?.to_i32()?;
48156
let sv = this.deref_pointer(sv)?;
49157

50-
// FIXME: fail on unsupported inputs
158+
let mut is_sock_nonblock = false;
159+
160+
// Parse and remove the type flags that we support. If type != 0 after removing,
161+
// unsupported flags are used.
162+
if type_ & this.eval_libc_i32("SOCK_STREAM") == this.eval_libc_i32("SOCK_STREAM") {
163+
type_ &= !(this.eval_libc_i32("SOCK_STREAM"));
164+
}
165+
166+
// SOCK_NONBLOCK only exists on Linux.
167+
if this.tcx.sess.target.os == "linux" {
168+
if type_ & this.eval_libc_i32("SOCK_NONBLOCK") == this.eval_libc_i32("SOCK_NONBLOCK") {
169+
is_sock_nonblock = true;
170+
type_ &= !(this.eval_libc_i32("SOCK_NONBLOCK"));
171+
}
172+
if type_ & this.eval_libc_i32("SOCK_CLOEXEC") == this.eval_libc_i32("SOCK_CLOEXEC") {
173+
type_ &= !(this.eval_libc_i32("SOCK_CLOEXEC"));
174+
}
175+
}
176+
177+
// Fail on unsupported input.
178+
// AF_UNIX and AF_LOCAL are synonyms, so we accept both in case
179+
// their values differ.
180+
if domain != this.eval_libc_i32("AF_UNIX") && domain != this.eval_libc_i32("AF_LOCAL") {
181+
throw_unsup_format!(
182+
"socketpair: Unsupported domain {:#x} is used, only AF_UNIX \
183+
and AF_LOCAL are allowed",
184+
domain
185+
);
186+
} else if type_ != 0 {
187+
throw_unsup_format!(
188+
"socketpair: Unsupported type {:#x} is used, only SOCK_STREAM, \
189+
SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
190+
type_
191+
);
192+
} else if protocol != 0 {
193+
throw_unsup_format!(
194+
"socketpair: Unsupported socket protocol {protocol} is used, \
195+
only 0 is allowed",
196+
);
197+
}
198+
199+
let buffer1 = Rc::new(RefCell::new(Buffer {
200+
buf: VecDeque::new(),
201+
clock: VClock::default(),
202+
buf_has_writer: true,
203+
}));
204+
205+
let buffer2 = Rc::new(RefCell::new(Buffer {
206+
buf: VecDeque::new(),
207+
clock: VClock::default(),
208+
buf_has_writer: true,
209+
}));
210+
211+
let socketpair_0 = SocketPair {
212+
writebuf: Rc::downgrade(&buffer1),
213+
readbuf: Rc::clone(&buffer2),
214+
is_nonblock: is_sock_nonblock,
215+
};
216+
217+
let socketpair_1 = SocketPair {
218+
writebuf: Rc::downgrade(&buffer2),
219+
readbuf: Rc::clone(&buffer1),
220+
is_nonblock: is_sock_nonblock,
221+
};
51222

52223
let fds = &mut this.machine.fds;
53-
let sv0 = fds.insert_fd(FileDescriptor::new(SocketPair));
224+
let sv0 = fds.insert_fd(FileDescriptor::new(socketpair_0));
54225
let sv0 = Scalar::try_from_int(sv0, sv.layout.size).unwrap();
55-
let sv1 = fds.insert_fd(FileDescriptor::new(SocketPair));
226+
let sv1 = fds.insert_fd(FileDescriptor::new(socketpair_1));
56227
let sv1 = Scalar::try_from_int(sv1, sv.layout.size).unwrap();
57228

58229
this.write_scalar(sv0, &sv)?;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ignore-target-windows: no libc socketpair on Windows
2+
3+
// This is temporarily here because blocking on fd is not supported yet.
4+
// When blocking is eventually supported, this will be moved to pass-dep/libc/libc-socketpair
5+
6+
fn main() {
7+
let mut fds = [-1, -1];
8+
let _ = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
9+
// The read below will be blocked because the buffer is empty.
10+
let mut buf: [u8; 3] = [0; 3];
11+
let _res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) }; //~ERROR: blocking isn't supported
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: unsupported operation: socketpair read: blocking isn't supported yet
2+
--> $DIR/socketpair_read_blocking.rs:LL:CC
3+
|
4+
LL | let _res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ socketpair read: blocking isn't supported yet
6+
|
7+
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
8+
= note: BACKTRACE:
9+
= note: inside `main` at $DIR/socketpair_read_blocking.rs:LL:CC
10+
11+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
12+
13+
error: aborting due to 1 previous error
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//@ignore-target-windows: no libc socketpair on Windows
2+
// This is temporarily here because blocking on fd is not supported yet.
3+
// When blocking is eventually supported, this will be moved to pass-dep/libc/libc-socketpair
4+
fn main() {
5+
let mut fds = [-1, -1];
6+
let _ = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
7+
// Write size > buffer capacity
8+
// Used up all the space in the buffer.
9+
let arr1: [u8; 212992] = [1; 212992];
10+
let _ = unsafe { libc::write(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
11+
let data = "abc".as_bytes().as_ptr();
12+
// The write below will be blocked as the buffer is full.
13+
let _ = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) }; //~ERROR: blocking isn't supported
14+
let mut buf: [u8; 3] = [0; 3];
15+
let _res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: unsupported operation: socketpair write: blocking isn't supported yet
2+
--> $DIR/socketpair_write_blocking.rs:LL:CC
3+
|
4+
LL | let _ = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ socketpair write: blocking isn't supported yet
6+
|
7+
= help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
8+
= note: BACKTRACE:
9+
= note: inside `main` at $DIR/socketpair_write_blocking.rs:LL:CC
10+
11+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
12+
13+
error: aborting due to 1 previous error
14+

0 commit comments

Comments
 (0)