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