1
+ use std:: cell:: RefCell ;
2
+ use std:: collections:: VecDeque ;
1
3
use std:: io;
4
+ use std:: io:: { Error , ErrorKind , Read } ;
5
+ use std:: rc:: { Rc , Weak } ;
2
6
3
7
use crate :: shims:: unix:: * ;
4
- use crate :: * ;
8
+ use crate :: { concurrency :: VClock , * } ;
5
9
6
10
use self :: fd:: FileDescriptor ;
7
11
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
+
8
17
/// Pair of connected sockets.
9
- ///
10
- /// We currently don't allow sending any data through this pair, so this can be just a dummy.
11
18
#[ 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
+ }
13
36
14
37
impl FileDescription for SocketPair {
15
38
fn name ( & self ) -> & ' static str {
@@ -20,17 +43,102 @@ impl FileDescription for SocketPair {
20
43
self : Box < Self > ,
21
44
_communicate_allowed : bool ,
22
45
) -> 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
+ } ;
23
51
Ok ( Ok ( ( ) ) )
24
52
}
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
+ }
25
138
}
26
139
27
140
impl < ' tcx > EvalContextExt < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
28
141
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
- ///
34
142
/// For more information on the arguments see the socketpair manpage:
35
143
/// <https://linux.die.net/man/2/socketpair>
36
144
fn socketpair (
@@ -42,17 +150,80 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
42
150
) -> InterpResult < ' tcx , Scalar > {
43
151
let this = self . eval_context_mut ( ) ;
44
152
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 ( ) ?;
48
156
let sv = this. deref_pointer ( sv) ?;
49
157
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
+ } ;
51
222
52
223
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 ) ) ;
54
225
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 ) ) ;
56
227
let sv1 = Scalar :: try_from_int ( sv1, sv. layout . size ) . unwrap ( ) ;
57
228
58
229
this. write_scalar ( sv0, & sv) ?;
0 commit comments