1+ use std:: cell:: RefCell ;
2+ use std:: collections:: VecDeque ;
13use std:: io;
4+ use std:: io:: { Error , ErrorKind , Read } ;
5+ use std:: rc:: { Rc , Weak } ;
26
37use crate :: shims:: unix:: * ;
4- use crate :: * ;
8+ use crate :: { concurrency :: VClock , * } ;
59
610use 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
1437impl 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
27140impl < ' tcx > EvalContextExt < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
28141pub 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) ?;
0 commit comments