Skip to main content

miri/shims/unix/
virtual_socket.rs

1//! This implements "virtual" sockets, that do not correspond to anything on the host system and
2//! are entirely implemented inside Miri.
3//! This is used to implement `socketpair` and `pipe`.
4
5use std::cell::{Cell, OnceCell, RefCell};
6use std::collections::VecDeque;
7use std::io::{self, ErrorKind, Read};
8use std::rc::Rc;
9
10use rustc_target::spec::Os;
11
12use crate::concurrency::VClock;
13use crate::shims::files::{
14    EvalContextExt as _, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
15};
16use crate::shims::readiness::DelayedReadinessUpdates;
17use crate::shims::unix::UnixFileDescription;
18use crate::shims::unix::socket::UnixSocketFileDescription;
19use crate::*;
20
21/// The maximum capacity of the socketpair buffer in bytes.
22/// This number is arbitrary as the value can always
23/// be configured in the real system.
24const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 0x34000;
25
26#[derive(Debug, PartialEq)]
27enum VirtualSocketType {
28    // Either end of the socketpair fd.
29    Socketpair,
30    // Read end of the pipe.
31    PipeRead,
32    // Write end of the pipe.
33    PipeWrite,
34}
35
36/// One end of a pair of connected virtual sockets.
37#[derive(Debug)]
38struct VirtualSocket {
39    /// The buffer we are reading from, or `None` if this is the writing end of a pipe.
40    /// (In that case, the peer FD will be the reading end of that pipe.)
41    readbuf: Option<RefCell<Buffer>>,
42    /// The `VirtualSocket` file descriptor that is our "peer", and that holds the buffer we are
43    /// writing to. This is a weak reference because the other side may be closed before us; all
44    /// future writes will then trigger EPIPE.
45    peer_fd: OnceCell<WeakFileDescriptionRef<VirtualSocket>>,
46    /// Indicates whether the peer has lost data when the file description is closed.
47    /// This flag is set to `true` if the peer's `readbuf` is non-empty at the time
48    /// of closure.
49    peer_lost_data: Cell<bool>,
50    /// A list of thread ids blocked because the buffer was empty.
51    /// Once another thread writes some bytes, these threads will be unblocked.
52    blocked_read_tid: RefCell<Vec<ThreadId>>,
53    /// A list of thread ids blocked because the buffer was full.
54    /// Once another thread reads some bytes, these threads will be unblocked.
55    blocked_write_tid: RefCell<Vec<ThreadId>>,
56    /// Whether this fd is non-blocking or not.
57    is_nonblock: Cell<bool>,
58    /// Differentiate between different virtual socket fd types.
59    fd_type: VirtualSocketType,
60    /// We need to update the peer_fd readiness when we get dropped, so we keep a reference
61    /// to the readiness update queue
62    delayed_readiness_updates: Rc<DelayedReadinessUpdates>,
63    /// State for being watched by epoll.
64    watched: ReadinessWatched,
65}
66
67#[derive(Debug)]
68struct Buffer {
69    buf: VecDeque<u8>,
70    clock: VClock,
71}
72
73impl Buffer {
74    fn new() -> Self {
75        Buffer { buf: VecDeque::new(), clock: VClock::default() }
76    }
77}
78
79impl VirtualSocket {
80    fn peer_fd(&self) -> &WeakFileDescriptionRef<VirtualSocket> {
81        self.peer_fd.get().unwrap()
82    }
83}
84
85impl Drop for VirtualSocket {
86    fn drop(&mut self) {
87        if let Some(peer_fd) = self.peer_fd().upgrade() {
88            // If the current readbuf is non-empty when the file description is closed,
89            // notify the peer that data lost has happened in current file description.
90            if let Some(readbuf) = &self.readbuf {
91                if !readbuf.borrow().buf.is_empty() {
92                    peer_fd.peer_lost_data.set(true);
93                }
94            }
95            // Notify peer fd that close has happened, since that can unblock reads and writes.
96            self.delayed_readiness_updates.add(peer_fd);
97        }
98    }
99}
100
101impl FileDescription for VirtualSocket {
102    fn name(&self) -> &'static str {
103        match self.fd_type {
104            VirtualSocketType::Socketpair => "socketpair",
105            VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "pipe",
106        }
107    }
108
109    fn metadata<'tcx>(
110        &self,
111    ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
112        let mode_name = match self.fd_type {
113            VirtualSocketType::Socketpair => "S_IFSOCK",
114            VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO",
115        };
116        interp_ok(Either::Right(mode_name))
117    }
118
119    fn read<'tcx>(
120        self: FileDescriptionRef<Self>,
121        _communicate_allowed: bool,
122        ptr: Pointer,
123        len: usize,
124        ecx: &mut MiriInterpCx<'tcx>,
125        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
126    ) -> InterpResult<'tcx> {
127        ecx.virtual_socket_read(self, ptr, len, /* is_non_block */ false, finish)
128    }
129
130    fn write<'tcx>(
131        self: FileDescriptionRef<Self>,
132        _communicate_allowed: bool,
133        ptr: Pointer,
134        len: usize,
135        ecx: &mut MiriInterpCx<'tcx>,
136        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
137    ) -> InterpResult<'tcx> {
138        ecx.virtual_socket_write(self, ptr, len, /* is_non_block */ false, finish)
139    }
140
141    fn short_fd_operations(&self) -> bool {
142        // Linux guarantees that when a read/write on a streaming socket comes back short,
143        // the kernel buffer is empty/full:
144        // See <https://man7.org/linux/man-pages/man7/epoll.7.html> in Q&A section.
145        // So we can't do short reads/writes here.
146        false
147    }
148
149    fn as_unix<'tcx>(
150        self: FileDescriptionRef<Self>,
151        _ecx: &MiriInterpCx<'tcx>,
152    ) -> FileDescriptionRef<dyn UnixFileDescription> {
153        self
154    }
155
156    fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
157        let mut flags = 0;
158
159        // Get flag for file access mode.
160        // The flag for both socketpair and pipe will remain the same even when the peer
161        // fd is closed, so we need to look at the original type of this socket, not at whether
162        // the peer socket still exists.
163        match self.fd_type {
164            VirtualSocketType::Socketpair => {
165                flags |= ecx.eval_libc_i32("O_RDWR");
166            }
167            VirtualSocketType::PipeRead => {
168                flags |= ecx.eval_libc_i32("O_RDONLY");
169            }
170            VirtualSocketType::PipeWrite => {
171                flags |= ecx.eval_libc_i32("O_WRONLY");
172            }
173        }
174
175        // Get flag for blocking status.
176        if self.is_nonblock.get() {
177            flags |= ecx.eval_libc_i32("O_NONBLOCK");
178        }
179
180        interp_ok(Scalar::from_i32(flags))
181    }
182
183    fn set_flags<'tcx>(
184        &self,
185        mut flag: i32,
186        ecx: &mut MiriInterpCx<'tcx>,
187    ) -> InterpResult<'tcx, Scalar> {
188        let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
189
190        // O_NONBLOCK flag can be set / unset by user.
191        if flag & o_nonblock == o_nonblock {
192            self.is_nonblock.set(true);
193            flag &= !o_nonblock;
194        } else {
195            self.is_nonblock.set(false);
196        }
197
198        // Throw error if there is any unsupported flag.
199        if flag != 0 {
200            throw_unsup_format!(
201                "fcntl: only O_NONBLOCK is supported for F_SETFL on socketpairs and pipes"
202            )
203        }
204
205        interp_ok(Scalar::from_i32(0))
206    }
207
208    fn readiness_watched(&self) -> Option<&ReadinessWatched> {
209        Some(&self.watched)
210    }
211
212    fn readiness(&self) -> Readiness {
213        // We only check the "readable", "writable", "read closed" and "write closed" readiness.
214        // If other event flags need to be supported in the future, the check should be added here.
215
216        let mut readiness = Readiness::EMPTY;
217
218        // Check if it is readable.
219        if let Some(readbuf) = &self.readbuf {
220            if !readbuf.borrow().buf.is_empty() {
221                readiness.readable = true;
222            }
223        } else {
224            // Without a read buffer, reading never blocks, so we are always ready.
225            readiness.readable = true;
226        }
227
228        // Check if is writable.
229        if let Some(peer_fd) = self.peer_fd().upgrade() {
230            if let Some(writebuf) = &peer_fd.readbuf {
231                let data_size = writebuf.borrow().buf.len();
232                let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
233                if available_space != 0 {
234                    readiness.writable = true;
235                }
236            } else {
237                // Without a write buffer, writing never blocks.
238                readiness.writable = true;
239            }
240        } else {
241            // Peer FD has been closed. This always sets both the "read closed" and "write closed" flags
242            // as we do not support `shutdown` that could be used to partially close the stream.
243            readiness.read_closed = true;
244            readiness.write_closed = true;
245            // Since the peer is closed, even if no data is available reads will return EOF and
246            // writes will return EPIPE. In other words, they won't block, so we mark this as ready
247            // for read and write.
248            readiness.readable = true;
249            readiness.writable = true;
250            // If there is data lost in peer_fd, set error readiness.
251            if self.peer_lost_data.get() {
252                readiness.error = true;
253            }
254        }
255        readiness
256    }
257}
258
259impl UnixFileDescription for VirtualSocket {
260    fn ioctl<'tcx>(
261        &self,
262        op: Scalar,
263        arg: Option<&OpTy<'tcx>>,
264        ecx: &mut MiriInterpCx<'tcx>,
265    ) -> InterpResult<'tcx, i32> {
266        match self.fd_type {
267            VirtualSocketType::Socketpair => { /* fall-through to below */ }
268            VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => {
269                // The standard library only uses ioctl for changing the blocking mode
270                // of Unix sockets. Thus, since using ioctl isn't the preferred way of
271                // changing the blocking mode, we don't support it on pipes.
272                throw_unsup_format!("cannot use ioctl on pipe");
273            }
274        }
275
276        let fionbio = ecx.eval_libc("FIONBIO");
277
278        if op == fionbio {
279            // On these OSes, Rust uses the ioctl, so we trust that it is reasonable and controls
280            // the same internal flag as fcntl.
281            if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)
282            {
283                // FIONBIO cannot be used to change the blocking mode of a socket on solarish targets:
284                // <https://github.com/rust-lang/rust/commit/dda5c97675b4f5b1f6fdab64606c8a1f21021b0a>
285                // Since there might be more targets which do weird things with this option, we use
286                // an allowlist instead of just denying solarish targets.
287                throw_unsup_format!(
288                    "ioctl: setting FIONBIO on sockets is unsupported on target {}",
289                    ecx.tcx.sess.target.os
290                );
291            }
292
293            let Some(value_ptr) = arg else {
294                throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");
295            };
296            let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
297            let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
298            self.is_nonblock.set(non_block);
299            return interp_ok(0);
300        }
301
302        throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");
303    }
304
305    fn as_socket<'tcx>(
306        self: FileDescriptionRef<Self>,
307        _ecx: &MiriInterpCx<'tcx>,
308    ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
309        match self.fd_type {
310            VirtualSocketType::Socketpair => Some(self),
311            VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => None,
312        }
313    }
314}
315
316impl UnixSocketFileDescription for VirtualSocket {
317    fn send<'tcx>(
318        self: FileDescriptionRef<Self>,
319        _communicate_allowed: bool,
320        ptr: Pointer,
321        len: usize,
322        is_non_block: bool,
323        ecx: &mut MiriInterpCx<'tcx>,
324        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
325    ) -> InterpResult<'tcx> {
326        ecx.virtual_socket_write(self, ptr, len, is_non_block, finish)
327    }
328
329    fn recv<'tcx>(
330        self: FileDescriptionRef<Self>,
331        _communicate_allowed: bool,
332        ptr: Pointer,
333        len: usize,
334        is_peek: bool,
335        is_non_block: bool,
336        ecx: &mut MiriInterpCx<'tcx>,
337        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
338    ) -> InterpResult<'tcx> {
339        if is_peek {
340            throw_unsup_format!("socketpair: virtual sockets don't support peeking")
341        }
342
343        ecx.virtual_socket_read(self, ptr, len, is_non_block, finish)
344    }
345}
346
347impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
348trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
349    /// Attempt two write `len` bytes from the buffer pointed to by `ptr` into the
350    /// virtual socket `socket`.
351    /// `is_non_block` specifies whether the operation should be performed as if the
352    /// socket was non-blocking.
353    /// After a successful write, `finish` is called with the amount of bytes written.
354    fn virtual_socket_write(
355        &mut self,
356        socket: FileDescriptionRef<VirtualSocket>,
357        ptr: Pointer,
358        len: usize,
359        is_non_block: bool,
360        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
361    ) -> InterpResult<'tcx> {
362        let this = self.eval_context_mut();
363
364        // Always succeed on write size 0.
365        // ("If count is zero and fd refers to a file other than a regular file, the results are not specified.")
366        if len == 0 {
367            return finish.call(this, Ok(0));
368        }
369
370        // We are writing to our peer's readbuf.
371        let Some(peer_fd) = socket.peer_fd().upgrade() else {
372            // If the upgrade from Weak to Rc fails, it indicates that all read ends have been
373            // closed. It is an error to write even if there would be space.
374            return finish.call(this, Err(ErrorKind::BrokenPipe.into()));
375        };
376
377        let Some(writebuf) = &peer_fd.readbuf else {
378            // Writing to the read end of a pipe.
379            return finish.call(this, Err(IoError::LibcError("EBADF")));
380        };
381
382        // Let's see if we can write.
383        let available_space =
384            MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len());
385        if available_space == 0 {
386            if socket.is_nonblock.get() || is_non_block {
387                // Non-blocking socketpair with a full buffer.
388                return finish.call(this, Err(ErrorKind::WouldBlock.into()));
389            } else {
390                socket.blocked_write_tid.borrow_mut().push(this.active_thread());
391                this.block_thread(
392                    BlockReason::VirtualSocket,
393                    None,
394                    callback!(
395                        @capture<'tcx> {
396                            socket: FileDescriptionRef<VirtualSocket>,
397                            ptr: Pointer,
398                            len: usize,
399                            is_non_block: bool,
400                            finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
401                        }
402                        |this, unblock: UnblockKind| {
403                            assert_eq!(unblock, UnblockKind::Ready);
404                            this.virtual_socket_write(socket, ptr, len, is_non_block, finish)
405                        }
406                    ),
407                );
408            }
409        } else {
410            // There is space to write!
411            let mut writebuf = writebuf.borrow_mut();
412            // Remember this clock so `read` can synchronize with us.
413            this.release_clock(|clock| {
414                writebuf.clock.join(clock);
415            })?;
416            // Do full write / partial write based on the space available.
417            let write_size = len.min(available_space);
418            let actual_write_size =
419                this.write_to_host(&mut writebuf.buf, write_size, ptr)?.unwrap();
420            assert_eq!(actual_write_size, write_size);
421
422            // Need to stop accessing peer_fd so that it can be notified.
423            drop(writebuf);
424
425            // Unblock all threads that are currently blocked on peer_fd's read.
426            let waiting_threads = std::mem::take(&mut *peer_fd.blocked_read_tid.borrow_mut());
427            // FIXME: We can randomize the order of unblocking.
428            for thread_id in waiting_threads {
429                this.unblock_thread(thread_id, BlockReason::VirtualSocket)?;
430            }
431            // Notify readiness watchers: we might be no longer writable, peer might now be readable.
432            // The notification to the peer seems to be always sent on Linux, even if the
433            // FD was readable before.
434            this.update_fd_readiness(socket, ReadinessUpdateFlags::DEFAULT)?;
435            this.update_fd_readiness(peer_fd, ReadinessUpdateFlags::FORCE_EDGE)?;
436
437            return finish.call(this, Ok(write_size));
438        }
439        interp_ok(())
440    }
441
442    /// Attempt to read `len` bytes from the virtual socket `socket` into the buffer
443    /// pointed to by `ptr`.
444    /// `is_non_block` specifies whether the operation should be performed as if the
445    /// socket was non-blocking.
446    /// After a successful read, `finish` is called with the amount of bytes read.
447    fn virtual_socket_read(
448        &mut self,
449        socket: FileDescriptionRef<VirtualSocket>,
450        ptr: Pointer,
451        len: usize,
452        is_non_block: bool,
453        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
454    ) -> InterpResult<'tcx> {
455        let this = self.eval_context_mut();
456
457        // Always succeed on read size 0.
458        if len == 0 {
459            return finish.call(this, Ok(0));
460        }
461
462        let Some(readbuf) = &socket.readbuf else {
463            // FIXME: This should return EBADF, but there's no nice way to do that as there's no
464            // corresponding ErrorKind variant.
465            throw_unsup_format!("reading from the write end of a pipe")
466        };
467
468        if readbuf.borrow_mut().buf.is_empty() {
469            if socket.peer_fd().upgrade().is_none() {
470                // Socketpair with no peer and empty buffer.
471                // 0 bytes successfully read indicates end-of-file.
472                return finish.call(this, Ok(0));
473            } else if socket.is_nonblock.get() || is_non_block {
474                // Non-blocking socketpair with writer and empty buffer.
475                // https://linux.die.net/man/2/read
476                // EAGAIN or EWOULDBLOCK can be returned for socket,
477                // POSIX.1-2001 allows either error to be returned for this case.
478                // Since there is no ErrorKind for EAGAIN, WouldBlock is used.
479                return finish.call(this, Err(ErrorKind::WouldBlock.into()));
480            } else {
481                socket.blocked_read_tid.borrow_mut().push(this.active_thread());
482                this.block_thread(
483                    BlockReason::VirtualSocket,
484                    None,
485                    callback!(
486                        @capture<'tcx> {
487                            socket: FileDescriptionRef<VirtualSocket>,
488                            ptr: Pointer,
489                            len: usize,
490                            is_non_block: bool,
491                            finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
492                        }
493                        |this, unblock: UnblockKind| {
494                            assert_eq!(unblock, UnblockKind::Ready);
495                            this.virtual_socket_read(socket, ptr, len, is_non_block, finish)
496                        }
497                    ),
498                );
499            }
500        } else {
501            // There's data to be read!
502            let mut readbuf = readbuf.borrow_mut();
503            // Synchronize with all previous writes to this buffer.
504            // FIXME: this over-synchronizes; a more precise approach would be to
505            // only sync with the writes whose data we will read.
506            this.acquire_clock(&readbuf.clock)?;
507
508            // Do full read / partial read based on the space available.
509            // Conveniently, `read` exists on `VecDeque` and has exactly the desired behavior.
510            let read_size = this.read_from_host(|buf| readbuf.buf.read(buf), len, ptr)?.unwrap();
511            let readbuf_now_empty = readbuf.buf.is_empty();
512
513            // Need to drop before others can access the readbuf again.
514            drop(readbuf);
515
516            // A notification should be provided for the peer file description even when it can
517            // only write 1 byte. This implementation is not compliant with the actual Linux kernel
518            // implementation. For optimization reasons, the kernel will only mark the file description
519            // as "writable" when it can write more than a certain number of bytes. Since we
520            // don't know what that *certain number* is, we will provide a notification every time
521            // a read is successful. This might result in our readiness emulation providing more
522            // events than the real system.
523            if let Some(peer_fd) = socket.peer_fd().upgrade() {
524                // Unblock all threads that are currently blocked on peer_fd's write.
525                let waiting_threads = std::mem::take(&mut *peer_fd.blocked_write_tid.borrow_mut());
526                // FIXME: We can randomize the order of unblocking.
527                for thread_id in waiting_threads {
528                    this.unblock_thread(thread_id, BlockReason::VirtualSocket)?;
529                }
530                // Notify readiness watchers: peer is now writable.
531                // Linux seems to always notify the peer if the read buffer is now empty.
532                // (Linux also does that if this was a "big" read, but to avoid some arbitrary
533                // threshold, we do not match that.)
534                this.update_fd_readiness(
535                    peer_fd,
536                    if readbuf_now_empty {
537                        ReadinessUpdateFlags::FORCE_EDGE
538                    } else {
539                        ReadinessUpdateFlags::DEFAULT
540                    },
541                )?;
542            };
543            // Notify readiness watchers: we might be no longer readable.
544            this.update_fd_readiness(socket, ReadinessUpdateFlags::DEFAULT)?;
545
546            return finish.call(this, Ok(read_size));
547        }
548        interp_ok(())
549    }
550}
551
552impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
553pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
554    /// For more information on the arguments see the socketpair manpage:
555    /// <https://linux.die.net/man/2/socketpair>
556    fn socketpair(
557        &mut self,
558        domain: &OpTy<'tcx>,
559        type_: &OpTy<'tcx>,
560        protocol: &OpTy<'tcx>,
561        sv: &OpTy<'tcx>,
562    ) -> InterpResult<'tcx, Scalar> {
563        let this = self.eval_context_mut();
564
565        let domain = this.read_scalar(domain)?.to_i32()?;
566        let mut flags = this.read_scalar(type_)?.to_i32()?;
567        let protocol = this.read_scalar(protocol)?.to_i32()?;
568        // This is really a pointer to `[i32; 2]` but we use a ptr-to-first-element representation.
569        let sv = this.deref_pointer_as(sv, this.machine.layouts.i32)?;
570
571        let mut is_sock_nonblock = false;
572
573        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
574        // if there is anything left at the end, that's an unsupported flag.
575        if matches!(
576            this.tcx.sess.target.os,
577            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
578        ) {
579            // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,
580            // Solaris, and Illumos targets.
581            let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
582            let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");
583            if flags & sock_nonblock == sock_nonblock {
584                is_sock_nonblock = true;
585                flags &= !sock_nonblock;
586            }
587            if flags & sock_cloexec == sock_cloexec {
588                flags &= !sock_cloexec;
589            }
590        }
591
592        // Fail on unsupported input.
593        // AF_UNIX and AF_LOCAL are synonyms, so we accept both in case
594        // their values differ.
595        if domain != this.eval_libc_i32("AF_UNIX") && domain != this.eval_libc_i32("AF_LOCAL") {
596            throw_unsup_format!(
597                "socketpair: domain {:#x} is unsupported, only AF_UNIX \
598                                 and AF_LOCAL are allowed",
599                domain
600            );
601        } else if flags != this.eval_libc_i32("SOCK_STREAM") {
602            throw_unsup_format!(
603                "socketpair: type {:#x} is unsupported, only SOCK_STREAM, \
604                                 SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
605                flags
606            );
607        } else if protocol != 0 {
608            throw_unsup_format!(
609                "socketpair: socket protocol {protocol} is unsupported, \
610                                 only 0 is allowed",
611            );
612        }
613
614        // Generate file descriptions.
615        let fds = &mut this.machine.fds;
616        let fd0 = fds.new_ref(VirtualSocket {
617            readbuf: Some(RefCell::new(Buffer::new())),
618            peer_fd: OnceCell::new(),
619            peer_lost_data: Cell::new(false),
620            blocked_read_tid: RefCell::new(Vec::new()),
621            blocked_write_tid: RefCell::new(Vec::new()),
622            is_nonblock: Cell::new(is_sock_nonblock),
623            fd_type: VirtualSocketType::Socketpair,
624            delayed_readiness_updates: Rc::clone(&this.machine.delayed_readiness_updates),
625            watched: ReadinessWatched::default(),
626        });
627        let fd1 = fds.new_ref(VirtualSocket {
628            readbuf: Some(RefCell::new(Buffer::new())),
629            peer_fd: OnceCell::new(),
630            peer_lost_data: Cell::new(false),
631            blocked_read_tid: RefCell::new(Vec::new()),
632            blocked_write_tid: RefCell::new(Vec::new()),
633            is_nonblock: Cell::new(is_sock_nonblock),
634            fd_type: VirtualSocketType::Socketpair,
635            delayed_readiness_updates: Rc::clone(&this.machine.delayed_readiness_updates),
636            watched: ReadinessWatched::default(),
637        });
638
639        // Make the file descriptions point to each other.
640        fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
641        fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
642
643        // Insert the file description to the fd table, generating the file descriptors.
644        let sv0 = fds.insert(fd0);
645        let sv1 = fds.insert(fd1);
646
647        // Return socketpair file descriptors to the caller.
648        let sv0 = Scalar::from_int(sv0, sv.layout.size);
649        let sv1 = Scalar::from_int(sv1, sv.layout.size);
650        this.write_scalar(sv0, &sv)?;
651        this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
652
653        interp_ok(Scalar::from_i32(0))
654    }
655
656    fn pipe2(
657        &mut self,
658        pipefd: &OpTy<'tcx>,
659        flags: Option<&OpTy<'tcx>>,
660    ) -> InterpResult<'tcx, Scalar> {
661        let this = self.eval_context_mut();
662
663        let pipefd = this.deref_pointer_as(pipefd, this.machine.layouts.i32)?;
664        let mut flags = match flags {
665            Some(flags) => this.read_scalar(flags)?.to_i32()?,
666            None => 0,
667        };
668
669        let cloexec = this.eval_libc_i32("O_CLOEXEC");
670        let o_nonblock = this.eval_libc_i32("O_NONBLOCK");
671
672        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
673        // if there is anything left at the end, that's an unsupported flag.
674        let mut is_nonblock = false;
675        if flags & o_nonblock == o_nonblock {
676            is_nonblock = true;
677            flags &= !o_nonblock;
678        }
679        // As usual we ignore CLOEXEC.
680        if flags & cloexec == cloexec {
681            flags &= !cloexec;
682        }
683        if flags != 0 {
684            throw_unsup_format!("unsupported flags in `pipe2`");
685        }
686
687        // Generate file descriptions.
688        // pipefd[0] refers to the read end of the pipe.
689        let fds = &mut this.machine.fds;
690        let fd0 = fds.new_ref(VirtualSocket {
691            readbuf: Some(RefCell::new(Buffer::new())),
692            peer_fd: OnceCell::new(),
693            peer_lost_data: Cell::new(false),
694            blocked_read_tid: RefCell::new(Vec::new()),
695            blocked_write_tid: RefCell::new(Vec::new()),
696            is_nonblock: Cell::new(is_nonblock),
697            fd_type: VirtualSocketType::PipeRead,
698            delayed_readiness_updates: Rc::clone(&this.machine.delayed_readiness_updates),
699            watched: ReadinessWatched::default(),
700        });
701        let fd1 = fds.new_ref(VirtualSocket {
702            readbuf: None,
703            peer_fd: OnceCell::new(),
704            peer_lost_data: Cell::new(false),
705            blocked_read_tid: RefCell::new(Vec::new()),
706            blocked_write_tid: RefCell::new(Vec::new()),
707            is_nonblock: Cell::new(is_nonblock),
708            fd_type: VirtualSocketType::PipeWrite,
709            delayed_readiness_updates: Rc::clone(&this.machine.delayed_readiness_updates),
710            watched: ReadinessWatched::default(),
711        });
712
713        // Make the file descriptions point to each other.
714        fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
715        fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
716
717        // Insert the file description to the fd table, generating the file descriptors.
718        let pipefd0 = fds.insert(fd0);
719        let pipefd1 = fds.insert(fd1);
720
721        // Return file descriptors to the caller.
722        let pipefd0 = Scalar::from_int(pipefd0, pipefd.layout.size);
723        let pipefd1 = Scalar::from_int(pipefd1, pipefd.layout.size);
724        this.write_scalar(pipefd0, &pipefd)?;
725        this.write_scalar(pipefd1, &pipefd.offset(pipefd.layout.size, pipefd.layout, this)?)?;
726
727        interp_ok(Scalar::from_i32(0))
728    }
729}