Skip to main content

miri/shims/unix/
socket.rs

1use std::cell::{Cell, RefCell, RefMut};
2use std::io;
3use std::io::Read;
4use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4};
5use std::sync::atomic::AtomicBool;
6
7use mio::event::Source;
8use mio::net::{TcpListener, TcpStream};
9use rustc_abi::Size;
10use rustc_const_eval::interpret::{InterpResult, interp_ok};
11use rustc_middle::throw_unsup_format;
12use rustc_target::spec::Os;
13
14use crate::shims::files::{EvalContextExt as _, FdId, FileDescription, FileDescriptionRef};
15use crate::shims::unix::UnixFileDescription;
16use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _};
17use crate::shims::unix::socket_address::EvalContextExt as _;
18use crate::*;
19
20#[derive(Debug, PartialEq)]
21enum SocketFamily {
22    // IPv4 internet protocols
23    IPv4,
24    // IPv6 internet protocols
25    IPv6,
26}
27
28#[derive(Debug)]
29enum SocketState {
30    /// No syscall after `socket` has been made.
31    Initial,
32    /// The `bind` syscall has been called on the socket.
33    /// This is only reachable from the [`SocketState::Initial`] state.
34    Bound(SocketAddr),
35    /// The `listen` syscall has been called on the socket.
36    /// This is only reachable from the [`SocketState::Bound`] state.
37    Listening(TcpListener),
38    /// The `connect` syscall has been called and we weren't yet able
39    /// to ensure the connection is established. This is only reachable
40    /// from the [`SocketState::Initial`] state.
41    Connecting(TcpStream),
42    /// The `connect` syscall has been called on the socket and
43    /// we ensured that the connection is established, or
44    /// the socket was created by the `accept` syscall.
45    /// For a socket created using the `connect` syscall, this is
46    /// only reachable from the [`SocketState::Connecting`] state.
47    Connected(TcpStream),
48    /// The SO_ERROR socket option has been set after calling
49    /// the `connect` syscall, indicating that the connection
50    /// attempt failed. By the POSIX specification, a socket is
51    /// is an unspecified state after a failed connection attempt
52    /// and thus nothing (except destroying the socket) should be
53    /// supported when a socket is in this state.
54    ConnectionFailed(TcpStream),
55}
56
57#[derive(Debug)]
58struct Socket {
59    /// Family of the socket, used to ensure socket only binds/connects to address of
60    /// same family.
61    family: SocketFamily,
62    /// Current state of the inner socket.
63    state: RefCell<SocketState>,
64    /// Whether this fd is non-blocking or not.
65    is_non_block: Cell<bool>,
66    /// The current blocking I/O readiness of the file description.
67    io_readiness: RefCell<BlockingIoSourceReadiness>,
68    /// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`.
69    error: RefCell<Option<io::Error>>,
70}
71
72impl FileDescription for Socket {
73    fn name(&self) -> &'static str {
74        "socket"
75    }
76
77    fn destroy<'tcx>(
78        self,
79        self_id: FdId,
80        communicate_allowed: bool,
81        ecx: &mut MiriInterpCx<'tcx>,
82    ) -> InterpResult<'tcx, io::Result<()>> {
83        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");
84
85        if matches!(
86            &*self.state.borrow(),
87            SocketState::Listening(_)
88                | SocketState::Connecting(_)
89                | SocketState::Connected(_)
90                | SocketState::ConnectionFailed(_)
91        ) {
92            // There exists an associated host socket so we need to deregister it
93            // from the blocking I/O manager.
94            ecx.machine.blocking_io.deregister(self_id, self)
95        };
96
97        interp_ok(Ok(()))
98    }
99
100    fn read<'tcx>(
101        self: FileDescriptionRef<Self>,
102        communicate_allowed: bool,
103        ptr: Pointer,
104        len: usize,
105        ecx: &mut MiriInterpCx<'tcx>,
106        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
107    ) -> InterpResult<'tcx> {
108        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");
109
110        let socket = self;
111
112        ecx.ensure_connected(
113            socket.clone(),
114            !socket.is_non_block.get(),
115            "read",
116            callback!(
117                @capture<'tcx> {
118                    socket: FileDescriptionRef<Socket>,
119                    ptr: Pointer,
120                    len: usize,
121                    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
122                } |this, result: Result<(), ()>| {
123                    if result.is_err() {
124                        return finish.call(this, Err(LibcError("ENOTCONN")))
125                    }
126
127                    // Since `read` is the same as `recv` with no flags, we just treat
128                    // the `read` as a `recv` here.
129
130                    if socket.is_non_block.get() {
131                        // We have a non-blocking socket and thus don't want to block until
132                        // we can read.
133                        let result = this.try_non_block_recv(&socket, ptr, len, /* should_peek */ false)?;
134                        finish.call(this, result)
135                    } else {
136                        // The socket is in blocking mode and thus the read call should block
137                        // until we can read some bytes from the socket.
138                        this.block_for_recv(socket, ptr, len, /* should_peek */ false, finish)
139                    }
140                }
141            ),
142        )
143    }
144
145    fn write<'tcx>(
146        self: FileDescriptionRef<Self>,
147        communicate_allowed: bool,
148        ptr: Pointer,
149        len: usize,
150        ecx: &mut MiriInterpCx<'tcx>,
151        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
152    ) -> InterpResult<'tcx> {
153        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");
154
155        let socket = self;
156
157        ecx.ensure_connected(
158            socket.clone(),
159            !socket.is_non_block.get(),
160            "write",
161            callback!(
162                @capture<'tcx> {
163                    socket: FileDescriptionRef<Socket>,
164                    ptr: Pointer,
165                    len: usize,
166                    finish: DynMachineCallback<'tcx, Result<usize, IoError>>
167                } |this, result: Result<(), ()>| {
168                    if result.is_err() {
169                        return finish.call(this, Err(LibcError("ENOTCONN")))
170                    }
171
172                    // Since `write` is the same as `send` with no flags, we just treat
173                    // the `write` as a `send` here.
174
175                    if socket.is_non_block.get() {
176                        // We have a non-blocking socket and thus don't want to block until
177                        // we can write.
178                        let result = this.try_non_block_send(&socket, ptr, len)?;
179                        return finish.call(this, result)
180                    } else {
181                        // The socket is in blocking mode and thus the write call should block
182                        // until we can write some bytes into the socket.
183                        this.block_for_send(socket, ptr, len, finish)
184                    }
185                }
186            ),
187        )
188    }
189
190    fn short_fd_operations(&self) -> bool {
191        // Linux guarantees that when a read/write on a streaming socket comes back short,
192        // the kernel buffer is empty/full:
193        // See <https://man7.org/linux/man-pages/man7/epoll.7.html> in Q&A section.
194        // So we can't do short reads/writes here.
195        false
196    }
197
198    fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
199        self
200    }
201
202    fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
203        let mut flags = ecx.eval_libc_i32("O_RDWR");
204
205        if self.is_non_block.get() {
206            flags |= ecx.eval_libc_i32("O_NONBLOCK");
207        }
208
209        interp_ok(Scalar::from_i32(flags))
210    }
211
212    fn set_flags<'tcx>(
213        &self,
214        mut flag: i32,
215        ecx: &mut MiriInterpCx<'tcx>,
216    ) -> InterpResult<'tcx, Scalar> {
217        let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
218
219        // O_NONBLOCK flag can be set / unset by user.
220        if flag & o_nonblock == o_nonblock {
221            self.is_non_block.set(true);
222            flag &= !o_nonblock;
223        } else {
224            self.is_non_block.set(false);
225        }
226
227        // Throw error if there is any unsupported flag.
228        if flag != 0 {
229            throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")
230        }
231
232        interp_ok(Scalar::from_i32(0))
233    }
234}
235
236impl UnixFileDescription for Socket {
237    fn ioctl<'tcx>(
238        &self,
239        op: Scalar,
240        arg: Option<&OpTy<'tcx>>,
241        ecx: &mut MiriInterpCx<'tcx>,
242    ) -> InterpResult<'tcx, i32> {
243        assert!(ecx.machine.communicate(), "cannot have `Socket` with isolation enabled!");
244
245        let fionbio = ecx.eval_libc("FIONBIO");
246
247        if op == fionbio {
248            // On these OSes, Rust uses the ioctl, so we trust that it is reasonable and controls
249            // the same internal flag as fcntl.
250            if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)
251            {
252                // FIONBIO cannot be used to change the blocking mode of a socket on solarish targets:
253                // <https://github.com/rust-lang/rust/commit/dda5c97675b4f5b1f6fdab64606c8a1f21021b0a>
254                // Since there might be more targets which do weird things with this option, we use
255                // an allowlist instead of just denying solarish targets.
256                throw_unsup_format!(
257                    "ioctl: setting FIONBIO on sockets is unsupported on target {}",
258                    ecx.tcx.sess.target.os
259                );
260            }
261
262            let Some(value_ptr) = arg else {
263                throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");
264            };
265            let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
266            let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
267            self.is_non_block.set(non_block);
268            return interp_ok(0);
269        }
270
271        throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");
272    }
273
274    fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> {
275        interp_ok(EpollReadiness::from(&*self.io_readiness.borrow()))
276    }
277}
278
279impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
280pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
281    /// For more information on the arguments see the socket manpage:
282    /// <https://linux.die.net/man/2/socket>
283    fn socket(
284        &mut self,
285        domain: &OpTy<'tcx>,
286        type_: &OpTy<'tcx>,
287        protocol: &OpTy<'tcx>,
288    ) -> InterpResult<'tcx, Scalar> {
289        let this = self.eval_context_mut();
290
291        let domain = this.read_scalar(domain)?.to_i32()?;
292        let mut flags = this.read_scalar(type_)?.to_i32()?;
293        let protocol = this.read_scalar(protocol)?.to_i32()?;
294
295        // Reject if isolation is enabled
296        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
297            this.reject_in_isolation("`socket`", reject_with)?;
298            return this.set_last_error_and_return_i32(LibcError("EACCES"));
299        }
300
301        let mut is_sock_nonblock = false;
302
303        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
304        // if there is anything left at the end, that's an unsupported flag.
305        if matches!(
306            this.tcx.sess.target.os,
307            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
308        ) {
309            // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,
310            // Solaris, and Illumos targets.
311            let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
312            let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");
313            if flags & sock_nonblock == sock_nonblock {
314                is_sock_nonblock = true;
315                flags &= !sock_nonblock;
316            }
317            if flags & sock_cloexec == sock_cloexec {
318                // We don't support `exec` so we can ignore this.
319                flags &= !sock_cloexec;
320            }
321        }
322
323        let family = if domain == this.eval_libc_i32("AF_INET") {
324            SocketFamily::IPv4
325        } else if domain == this.eval_libc_i32("AF_INET6") {
326            SocketFamily::IPv6
327        } else {
328            throw_unsup_format!(
329                "socket: domain {:#x} is unsupported, only AF_INET and \
330                AF_INET6 are allowed.",
331                domain
332            );
333        };
334
335        if flags != this.eval_libc_i32("SOCK_STREAM") {
336            throw_unsup_format!(
337                "socket: type {:#x} is unsupported, only SOCK_STREAM, \
338                SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
339                flags
340            );
341        }
342        if protocol != 0 && protocol != this.eval_libc_i32("IPPROTO_TCP") {
343            throw_unsup_format!(
344                "socket: socket protocol {protocol} is unsupported, \
345                only IPPROTO_TCP and 0 are allowed"
346            );
347        }
348
349        let fds = &mut this.machine.fds;
350        let fd = fds.new_ref(Socket {
351            family,
352            state: RefCell::new(SocketState::Initial),
353            is_non_block: Cell::new(is_sock_nonblock),
354            io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),
355            error: RefCell::new(None),
356        });
357
358        interp_ok(Scalar::from_i32(fds.insert(fd)))
359    }
360
361    fn bind(
362        &mut self,
363        socket: &OpTy<'tcx>,
364        address: &OpTy<'tcx>,
365        address_len: &OpTy<'tcx>,
366    ) -> InterpResult<'tcx, Scalar> {
367        let this = self.eval_context_mut();
368
369        let socket = this.read_scalar(socket)?.to_i32()?;
370        let address = match this.read_socket_address(address, address_len, "bind")? {
371            Ok(addr) => addr,
372            Err(e) => return this.set_last_error_and_return_i32(e),
373        };
374
375        // Get the file handle
376        let Some(fd) = this.machine.fds.get(socket) else {
377            return this.set_last_error_and_return_i32(LibcError("EBADF"));
378        };
379
380        let Some(socket) = fd.downcast::<Socket>() else {
381            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
382            return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
383        };
384
385        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
386        this.ensure_not_failed(&socket, "bind")?;
387
388        let mut state = socket.state.borrow_mut();
389
390        match *state {
391            SocketState::Initial => {
392                let address_family = match &address {
393                    SocketAddr::V4(_) => SocketFamily::IPv4,
394                    SocketAddr::V6(_) => SocketFamily::IPv6,
395                };
396
397                if socket.family != address_family {
398                    // Attempted to bind an address from a family that doesn't match
399                    // the family of the socket.
400                    let err = if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {
401                        // Linux man page states that `EINVAL` is used when there is an address family mismatch.
402                        // See <https://man7.org/linux/man-pages/man2/bind.2.html>
403                        LibcError("EINVAL")
404                    } else {
405                        // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address
406                        // family mismatch.
407                        // See <https://man7.org/linux/man-pages/man3/bind.3p.html>
408                        LibcError("EAFNOSUPPORT")
409                    };
410                    return this.set_last_error_and_return_i32(err);
411                }
412
413                *state = SocketState::Bound(address);
414            }
415            SocketState::Connecting(_) | SocketState::Connected(_) =>
416                throw_unsup_format!(
417                    "bind: socket is already connected and binding a
418                    connected socket is unsupported"
419                ),
420            SocketState::Bound(_) | SocketState::Listening(_) =>
421                throw_unsup_format!(
422                    "bind: socket is already bound and binding a socket \
423                    multiple times is unsupported"
424                ),
425            SocketState::ConnectionFailed(_) => unreachable!(),
426        }
427
428        interp_ok(Scalar::from_i32(0))
429    }
430
431    fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
432        let this = self.eval_context_mut();
433
434        let socket = this.read_scalar(socket)?.to_i32()?;
435        // Since the backlog value is just a performance hint we can ignore it.
436        let _backlog = this.read_scalar(backlog)?.to_i32()?;
437
438        // Get the file handle
439        let Some(fd) = this.machine.fds.get(socket) else {
440            return this.set_last_error_and_return_i32(LibcError("EBADF"));
441        };
442
443        let Some(socket) = fd.downcast::<Socket>() else {
444            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
445            return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
446        };
447
448        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
449        this.ensure_not_failed(&socket, "listen")?;
450
451        let mut state = socket.state.borrow_mut();
452
453        match *state {
454            SocketState::Bound(socket_addr) =>
455                match TcpListener::bind(socket_addr) {
456                    Ok(listener) => {
457                        *state = SocketState::Listening(listener);
458                        drop(state);
459                        // Register the socket to the blocking I/O manager because
460                        // we now have an associated host socket.
461                        this.machine.blocking_io.register(socket);
462                    }
463                    Err(e) => return this.set_last_error_and_return_i32(e),
464                },
465            SocketState::Initial => {
466                throw_unsup_format!(
467                    "listen: listening on a socket which isn't bound is unsupported"
468                )
469            }
470            SocketState::Listening(_) => {
471                throw_unsup_format!("listen: listening on a socket multiple times is unsupported")
472            }
473            SocketState::Connecting(_) | SocketState::Connected(_) => {
474                throw_unsup_format!("listen: listening on a connected socket is unsupported")
475            }
476            SocketState::ConnectionFailed(_) => unreachable!(),
477        }
478
479        interp_ok(Scalar::from_i32(0))
480    }
481
482    /// For more information on the arguments see the accept manpage:
483    /// <https://linux.die.net/man/2/accept4>
484    fn accept4(
485        &mut self,
486        socket: &OpTy<'tcx>,
487        address: &OpTy<'tcx>,
488        address_len: &OpTy<'tcx>,
489        flags: Option<&OpTy<'tcx>>,
490        // Location where the output scalar is written to.
491        dest: &MPlaceTy<'tcx>,
492    ) -> InterpResult<'tcx> {
493        let this = self.eval_context_mut();
494
495        let socket = this.read_scalar(socket)?.to_i32()?;
496        let address_ptr = this.read_pointer(address)?;
497        let address_len_ptr = this.read_pointer(address_len)?;
498        let mut flags =
499            if let Some(flags) = flags { this.read_scalar(flags)?.to_i32()? } else { 0 };
500
501        // Get the file handle
502        let Some(fd) = this.machine.fds.get(socket) else {
503            return this.set_last_error_and_return(LibcError("EBADF"), dest);
504        };
505
506        let Some(socket) = fd.downcast::<Socket>() else {
507            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
508            return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
509        };
510
511        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
512        this.ensure_not_failed(&socket, "accept4")?;
513
514        if !matches!(*socket.state.borrow(), SocketState::Listening(_)) {
515            throw_unsup_format!(
516                "accept4: accepting incoming connections is only allowed when socket is listening"
517            )
518        };
519
520        let mut is_client_sock_nonblock = false;
521
522        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
523        // if there is anything left at the end, that's an unsupported flag.
524        if matches!(
525            this.tcx.sess.target.os,
526            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
527        ) {
528            // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,
529            // Solaris, and Illumos targets.
530            let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
531            let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");
532            if flags & sock_nonblock == sock_nonblock {
533                is_client_sock_nonblock = true;
534                flags &= !sock_nonblock;
535            }
536            if flags & sock_cloexec == sock_cloexec {
537                // We don't support `exec` so we can ignore this.
538                flags &= !sock_cloexec;
539            }
540        }
541
542        if flags != 0 {
543            throw_unsup_format!(
544                "accept4: flag {flags:#x} is unsupported, only SOCK_CLOEXEC \
545                and SOCK_NONBLOCK are allowed",
546            );
547        }
548
549        if socket.is_non_block.get() {
550            // We have a non-blocking socket and thus don't want to block until
551            // we can accept an incoming connection.
552            match this.try_non_block_accept(
553                &socket,
554                address_ptr,
555                address_len_ptr,
556                is_client_sock_nonblock,
557            )? {
558                Ok(sockfd) => {
559                    // We need to create the scalar using the destination size since
560                    // `syscall(SYS_accept4, ...)` returns a long which doesn't match
561                    // the int returned from the `accept`/`accept4` syscalls.
562                    // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.
563                    this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), dest)
564                }
565                Err(e) => this.set_last_error_and_return(e, dest),
566            }
567        } else {
568            // The socket is in blocking mode and thus the accept call should block
569            // until an incoming connection is ready.
570            this.block_for_accept(
571                socket,
572                address_ptr,
573                address_len_ptr,
574                is_client_sock_nonblock,
575                dest.clone(),
576            )
577        }
578    }
579
580    fn connect(
581        &mut self,
582        socket: &OpTy<'tcx>,
583        address: &OpTy<'tcx>,
584        address_len: &OpTy<'tcx>,
585        // Location where the output scalar is written to.
586        dest: &MPlaceTy<'tcx>,
587    ) -> InterpResult<'tcx> {
588        let this = self.eval_context_mut();
589
590        let socket = this.read_scalar(socket)?.to_i32()?;
591        let address = match this.read_socket_address(address, address_len, "connect")? {
592            Ok(address) => address,
593            Err(e) => return this.set_last_error_and_return(e, dest),
594        };
595
596        // Get the file handle
597        let Some(fd) = this.machine.fds.get(socket) else {
598            return this.set_last_error_and_return(LibcError("EBADF"), dest);
599        };
600
601        let Some(socket) = fd.downcast::<Socket>() else {
602            // Man page specifies to return ENOTSOCK if `fd` is not a socket
603            return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
604        };
605
606        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
607        this.ensure_not_failed(&socket, "connect")?;
608
609        match &*socket.state.borrow() {
610            SocketState::Initial => { /* fall-through to below */ }
611            // The socket is already in a connecting state.
612            SocketState::Connecting(_) =>
613                return this.set_last_error_and_return(LibcError("EALREADY"), dest),
614            // We don't return EISCONN for already connected sockets, for which we're
615            // sure that the connection is established, since TCP sockets are usually
616            // allowed to be connected multiple times.
617            _ =>
618                throw_unsup_format!(
619                    "connect: connecting is only supported for sockets which are neither \
620                    bound, listening nor already connected"
621                ),
622        }
623
624        // This begins establishing the connection, but does not block until the stream is fully connected.
625        // We deal with that below.
626        match TcpStream::connect(address) {
627            Ok(stream) => {
628                *socket.state.borrow_mut() = SocketState::Connecting(stream);
629                // Register the socket to the blocking I/O manager because
630                // we now have an associated host socket.
631                this.machine.blocking_io.register(socket.clone());
632            }
633            Err(e) => return this.set_last_error_and_return(e, dest),
634        };
635
636        if socket.is_non_block.get() {
637            // We have a non-blocking socket and thus don't want to block until
638            // the connection is established.
639
640            // Since the [`TcpStream::connect`] function of mio hides the EINPROGRESS
641            // we just always return EINPROGRESS and check whether the connection succeeded
642            // once we want to use the connected socket.
643            this.set_last_error_and_return(LibcError("EINPROGRESS"), dest)
644        } else {
645            // The socket is in blocking mode and thus the connect call should block
646            // until the connection with the server is established.
647
648            let dest = dest.clone();
649
650            this.ensure_connected(
651                socket.clone(),
652                /* should_wait */ true,
653                "connect",
654                callback!(
655                    @capture<'tcx> {
656                        socket: FileDescriptionRef<Socket>,
657                        dest: MPlaceTy<'tcx>
658                    } |this, result: Result<(), ()>| {
659                        if result.is_err() {
660                            // An error occurred whilst connecting. We know
661                            // that it has been consumed by `ensure_connected`
662                            // and is now stored in `socket.error`.
663                            let err = socket.error.take().unwrap();
664                            this.set_last_error_and_return(err, &dest)
665                        } else {
666                            this.write_scalar(Scalar::from_i32(0), &dest)
667                        }
668                    }
669                ),
670            )
671        }
672    }
673
674    fn send(
675        &mut self,
676        socket: &OpTy<'tcx>,
677        buffer: &OpTy<'tcx>,
678        length: &OpTy<'tcx>,
679        flags: &OpTy<'tcx>,
680        // Location where the output scalar is written to.
681        dest: &MPlaceTy<'tcx>,
682    ) -> InterpResult<'tcx> {
683        let this = self.eval_context_mut();
684
685        let socket = this.read_scalar(socket)?.to_i32()?;
686        let buffer_ptr = this.read_pointer(buffer)?;
687        let size_layout = this.libc_ty_layout("size_t");
688        let length: usize =
689            this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();
690        let mut flags = this.read_scalar(flags)?.to_i32()?;
691
692        // Get the file handle
693        let Some(fd) = this.machine.fds.get(socket) else {
694            return this.set_last_error_and_return(LibcError("EBADF"), dest);
695        };
696
697        let Some(socket) = fd.downcast::<Socket>() else {
698            // Man page specifies to return ENOTSOCK if `fd` is not a socket
699            return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
700        };
701
702        let mut is_op_non_block = false;
703
704        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
705        // if there is anything left at the end, that's an unsupported flag.
706        if matches!(
707            this.tcx.sess.target.os,
708            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
709        ) {
710            // MSG_NOSIGNAL and MSG_DONTWAIT only exist on Linux, Android, FreeBSD,
711            // Solaris, and Illumos targets.
712            let msg_nosignal = this.eval_libc_i32("MSG_NOSIGNAL");
713            let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");
714            if flags & msg_nosignal == msg_nosignal {
715                // This is only needed to ensure that no EPIPE signal is sent when
716                // trying to send into a stream which is no longer connected.
717                // Since we don't support signals, we can ignore this.
718                flags &= !msg_nosignal;
719            }
720            if flags & msg_dontwait == msg_dontwait {
721                flags &= !msg_dontwait;
722                is_op_non_block = true;
723            }
724        }
725
726        if flags != 0 {
727            throw_unsup_format!(
728                "send: flag {flags:#x} is unsupported, only MSG_NOSIGNAL and MSG_DONTWAIT are allowed",
729            );
730        }
731
732        // If either the operation or the socket is non-blocking, we don't want
733        // to wait until the connection is established.
734        let should_wait = !is_op_non_block && !socket.is_non_block.get();
735        let dest = dest.clone();
736
737        this.ensure_connected(
738            socket.clone(),
739            should_wait,
740            "send",
741            callback!(
742                @capture<'tcx> {
743                    socket: FileDescriptionRef<Socket>,
744                    flags: i32,
745                    buffer_ptr: Pointer,
746                    length: usize,
747                    is_op_non_block: bool,
748                    dest: MPlaceTy<'tcx>,
749                } |this, result: Result<(), ()>| {
750                    if result.is_err() {
751                        return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)
752                    }
753
754                    if is_op_non_block || socket.is_non_block.get() {
755                        // We have a non-blocking operation or a non-blocking socket and
756                        // thus don't want to block until we can send.
757                        match this.try_non_block_send(&socket, buffer_ptr, length)? {
758                            Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
759                            Err(e) => this.set_last_error_and_return(e, &dest),
760                        }
761                    } else {
762                        // The socket is in blocking mode and thus the send call should block
763                        // until we can send some bytes into the socket.
764                        this.block_for_send(
765                            socket,
766                            buffer_ptr,
767                            length,
768                            callback!(@capture<'tcx> {
769                                dest: MPlaceTy<'tcx>
770                            } |this, result: Result<usize, IoError>| {
771                                match result {
772                                    Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
773                                    Err(e) => this.set_last_error_and_return(e, &dest)
774                                }
775                            }),
776                        )
777                    }
778                }
779            ),
780        )
781    }
782
783    fn recv(
784        &mut self,
785        socket: &OpTy<'tcx>,
786        buffer: &OpTy<'tcx>,
787        length: &OpTy<'tcx>,
788        flags: &OpTy<'tcx>,
789        // Location where the output scalar is written to.
790        dest: &MPlaceTy<'tcx>,
791    ) -> InterpResult<'tcx> {
792        let this = self.eval_context_mut();
793
794        let socket = this.read_scalar(socket)?.to_i32()?;
795        let buffer_ptr = this.read_pointer(buffer)?;
796        let size_layout = this.libc_ty_layout("size_t");
797        let length: usize =
798            this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();
799        let mut flags = this.read_scalar(flags)?.to_i32()?;
800
801        // Get the file handle
802        let Some(fd) = this.machine.fds.get(socket) else {
803            return this.set_last_error_and_return(LibcError("EBADF"), dest);
804        };
805
806        let Some(socket) = fd.downcast::<Socket>() else {
807            // Man page specifies to return ENOTSOCK if `fd` is not a socket
808            return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
809        };
810
811        let mut should_peek = false;
812        let mut is_op_non_block = false;
813
814        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so
815        // if there is anything left at the end, that's an unsupported flag.
816
817        let msg_peek = this.eval_libc_i32("MSG_PEEK");
818        if flags & msg_peek == msg_peek {
819            should_peek = true;
820            flags &= !msg_peek;
821        }
822
823        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {
824            // MSG_CMSG_CLOEXEC only exists on Linux, Android, FreeBSD,
825            // and Illumos targets.
826            let msg_cmsg_cloexec = this.eval_libc_i32("MSG_CMSG_CLOEXEC");
827            if flags & msg_cmsg_cloexec == msg_cmsg_cloexec {
828                // We don't support `exec` so we can ignore this.
829                flags &= !msg_cmsg_cloexec;
830            }
831        }
832
833        if matches!(
834            this.tcx.sess.target.os,
835            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
836        ) {
837            // MSG_DONTWAIT only exists on Linux, Android, FreeBSD,
838            // Solaris, and Illumos targets.
839            let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");
840            if flags & msg_dontwait == msg_dontwait {
841                flags &= !msg_dontwait;
842                is_op_non_block = true;
843            }
844        }
845
846        if flags != 0 {
847            throw_unsup_format!(
848                "recv: flag {flags:#x} is unsupported, only MSG_PEEK, MSG_DONTWAIT \
849                and MSG_CMSG_CLOEXEC are allowed",
850            );
851        }
852
853        // If either the operation or the socket is non-blocking, we don't want
854        // to wait until the connection is established.
855        let should_wait = !is_op_non_block && !socket.is_non_block.get();
856        let dest = dest.clone();
857
858        this.ensure_connected(
859            socket.clone(),
860            should_wait,
861            "recv",
862            callback!(
863                @capture<'tcx> {
864                    socket: FileDescriptionRef<Socket>,
865                    buffer_ptr: Pointer,
866                    length: usize,
867                    should_peek: bool,
868                    is_op_non_block: bool,
869                    dest: MPlaceTy<'tcx>,
870                } |this, result: Result<(), ()>| {
871                    if result.is_err() {
872                        return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)
873                    }
874
875                    if is_op_non_block || socket.is_non_block.get() {
876                        // We have a non-blocking operation or a non-blocking socket and
877                        // thus don't want to block until we can receive.
878                        match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
879                            Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
880                            Err(e) => this.set_last_error_and_return(e, &dest),
881                        }
882                    } else {
883                        // The socket is in blocking mode and thus the receive call should block
884                        // until we can receive some bytes from the socket.
885                        this.block_for_recv(
886                            socket,
887                            buffer_ptr,
888                            length,
889                            should_peek,
890                            callback!(@capture<'tcx> {
891                                dest: MPlaceTy<'tcx>
892                            } |this, result: Result<usize, IoError>| {
893                                match result {
894                                    Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),
895                                    Err(e) => this.set_last_error_and_return(e, &dest)
896                                }
897                            }),
898                        )
899                    }
900                }
901            ),
902        )
903    }
904
905    fn setsockopt(
906        &mut self,
907        socket: &OpTy<'tcx>,
908        level: &OpTy<'tcx>,
909        option_name: &OpTy<'tcx>,
910        option_value: &OpTy<'tcx>,
911        option_len: &OpTy<'tcx>,
912    ) -> InterpResult<'tcx, Scalar> {
913        let this = self.eval_context_mut();
914
915        let socket = this.read_scalar(socket)?.to_i32()?;
916        let level = this.read_scalar(level)?.to_i32()?;
917        let option_name = this.read_scalar(option_name)?.to_i32()?;
918        let option_value_ptr = this.read_pointer(option_value)?;
919        let socklen_layout = this.libc_ty_layout("socklen_t");
920        let option_len = this.read_scalar(option_len)?.to_int(socklen_layout.size)?;
921
922        // Get the file handle
923        let Some(fd) = this.machine.fds.get(socket) else {
924            return this.set_last_error_and_return_i32(LibcError("EBADF"));
925        };
926
927        let Some(socket) = fd.downcast::<Socket>() else {
928            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
929            return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
930        };
931
932        if level == this.eval_libc_i32("SOL_SOCKET") {
933            let opt_so_reuseaddr = this.eval_libc_i32("SO_REUSEADDR");
934
935            if matches!(this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) {
936                // SO_NOSIGPIPE only exists on MacOS, FreeBSD, and NetBSD.
937                let opt_so_nosigpipe = this.eval_libc_i32("SO_NOSIGPIPE");
938
939                if option_name == opt_so_nosigpipe {
940                    if option_len != 4 {
941                        // Option value should be C-int which is usually 4 bytes.
942                        return this.set_last_error_and_return_i32(LibcError("EINVAL"));
943                    }
944                    let option_value =
945                        this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
946                    let _val = this.read_scalar(&option_value)?.to_i32()?;
947                    // We entirely ignore this value since we do not support signals anyway.
948
949                    return interp_ok(Scalar::from_i32(0));
950                }
951            }
952
953            if option_name == opt_so_reuseaddr {
954                if option_len != 4 {
955                    // Option value should be C-int which is usually 4 bytes.
956                    return this.set_last_error_and_return_i32(LibcError("EINVAL"));
957                }
958                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
959                let _val = this.read_scalar(&option_value)?.to_i32()?;
960                // We entirely ignore this: std always sets REUSEADDR for us, and in the end it's more of a
961                // hint to bypass some arbitrary timeout anyway.
962                return interp_ok(Scalar::from_i32(0));
963            } else {
964                throw_unsup_format!(
965                    "setsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",
966                );
967            }
968        } else if level == this.eval_libc_i32("IPPROTO_IP") {
969            let opt_ip_ttl = this.eval_libc_i32("IP_TTL");
970
971            if option_name == opt_ip_ttl {
972                if option_len != 4 {
973                    // Option value should be C-uint which is usually 4 bytes.
974                    return this.set_last_error_and_return_i32(LibcError("EINVAL"));
975                }
976                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.u32);
977                let ttl = this.read_scalar(&option_value)?.to_u32()?;
978
979                let result = match &*socket.state.borrow() {
980                    SocketState::Initial | SocketState::Bound(_) =>
981                        throw_unsup_format!(
982                            "setsockopt: setting option IP_TTL on level IPPROTO_IP is only supported \
983                            on connected and listening sockets"
984                        ),
985                    SocketState::Listening(listener) => listener.set_ttl(ttl),
986                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>
987                        stream.set_ttl(ttl),
988                    SocketState::ConnectionFailed(_) => unreachable!(),
989                };
990
991                return match result {
992                    Ok(_) => interp_ok(Scalar::from_i32(0)),
993                    Err(e) => this.set_last_error_and_return_i32(e),
994                };
995            } else {
996                throw_unsup_format!(
997                    "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP",
998                );
999            }
1000        } else if level == this.eval_libc_i32("IPPROTO_TCP") {
1001            let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY");
1002
1003            if option_name == opt_tcp_nodelay {
1004                if option_len != 4 {
1005                    // Option value should be C-int which is usually 4 bytes.
1006                    return this.set_last_error_and_return_i32(LibcError("EINVAL"));
1007                }
1008                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);
1009                let nodelay = this.read_scalar(&option_value)?.to_i32()? != 0;
1010
1011                let result = match &*socket.state.borrow() {
1012                    SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>
1013                        throw_unsup_format!(
1014                            "setsockopt: setting option TCP_NODELAY on level IPPROTO_TCP is only supported \
1015                            on connected sockets"
1016                        ),
1017                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1018                        stream.set_nodelay(nodelay),
1019                    SocketState::ConnectionFailed(_) => unreachable!(),
1020                };
1021
1022                return match result {
1023                    Ok(_) => interp_ok(Scalar::from_i32(0)),
1024                    Err(e) => this.set_last_error_and_return_i32(e),
1025                };
1026            } else {
1027                throw_unsup_format!(
1028                    "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP"
1029                );
1030            }
1031        }
1032
1033        throw_unsup_format!(
1034            "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
1035            and IPPROTO_TCP are allowed"
1036        );
1037    }
1038
1039    fn getsockopt(
1040        &mut self,
1041        socket: &OpTy<'tcx>,
1042        level: &OpTy<'tcx>,
1043        option_name: &OpTy<'tcx>,
1044        option_value: &OpTy<'tcx>,
1045        option_len: &OpTy<'tcx>,
1046    ) -> InterpResult<'tcx, Scalar> {
1047        let this = self.eval_context_mut();
1048
1049        let socket = this.read_scalar(socket)?.to_i32()?;
1050        let level = this.read_scalar(level)?.to_i32()?;
1051        let option_name = this.read_scalar(option_name)?.to_i32()?;
1052        // These two pointers are used to return the value: `len_ptr` initially stores how much space
1053        // is available. If the actual value fits into that space, it is written to
1054        // `value_ptr` and `len_ptr` is updated to represent how many bytes
1055        // were actually written. If the value does not fit, it is silently truncated.
1056        // Also see <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getsockopt.html>.
1057        let option_value_ptr = this.read_pointer(option_value)?;
1058        let option_len_ptr = this.read_pointer(option_len)?;
1059
1060        // Get the file handle
1061        let Some(fd) = this.machine.fds.get(socket) else {
1062            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1063        };
1064
1065        let Some(socket) = fd.downcast::<Socket>() else {
1066            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
1067            return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
1068        };
1069
1070        if option_value_ptr == Pointer::null() || option_len_ptr == Pointer::null() {
1071            // This socket option returns a value and thus we need to return EFAULT
1072            // when either the value or the length pointers are null pointers.
1073            return this.set_last_error_and_return_i32(LibcError("EFAULT"));
1074        }
1075
1076        let socklen_layout = this.libc_ty_layout("socklen_t");
1077        let option_len_ptr_mplace = this.ptr_to_mplace(option_len_ptr, socklen_layout);
1078        let option_len: usize = this
1079            .read_scalar(&option_len_ptr_mplace)?
1080            .to_int(socklen_layout.size)?
1081            .try_into()
1082            .unwrap();
1083
1084        // We need a temporary buffer as `option_value_ptr` might not point to a large enough
1085        // buffer, in which case we have to truncate.
1086        let value_buffer = if level == this.eval_libc_i32("SOL_SOCKET") {
1087            let opt_so_error = this.eval_libc_i32("SO_ERROR");
1088
1089            if option_name == opt_so_error {
1090                // Reading SO_ERROR should always return the latest async error. Because our stored
1091                // `socket.error` could be outdated, we attempt to update it here.
1092                this.update_last_error(&socket);
1093
1094                let return_value = match socket.error.take() {
1095                    Some(err) => this.io_error_to_errnum(err)?.to_i32()?,
1096                    // If there is no error, we return 0 as the option value.
1097                    None => 0,
1098                };
1099
1100                // Clear our own stored error -- it was either `take`n above or it is outdated.
1101                socket.error.replace(None);
1102
1103                // We know there is no longer an async error and thus we need to update the
1104                // I/O and epoll readiness of the socket.
1105                socket.io_readiness.borrow_mut().error = false;
1106                this.update_epoll_active_events(socket, /* force_edge */ false)?;
1107
1108                // Allocate new buffer on the stack with the `i32` layout.
1109                let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;
1110                this.write_int(return_value, &value_buffer)?;
1111                value_buffer
1112            } else {
1113                throw_unsup_format!(
1114                    "getsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",
1115                );
1116            }
1117        } else if level == this.eval_libc_i32("IPPROTO_IP") {
1118            let opt_ip_ttl = this.eval_libc_i32("IP_TTL");
1119
1120            if option_name == opt_ip_ttl {
1121                let ttl = match &*socket.state.borrow() {
1122                    SocketState::Initial | SocketState::Bound(_) =>
1123                        throw_unsup_format!(
1124                            "getsockopt: reading option IP_TTL on level IPPROTO_IP is only supported \
1125                            on connected and listening sockets"
1126                        ),
1127                    SocketState::Listening(listener) => listener.ttl(),
1128                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1129                        stream.ttl(),
1130                    SocketState::ConnectionFailed(_) => unreachable!(),
1131                };
1132
1133                let ttl = match ttl {
1134                    Ok(ttl) => ttl,
1135                    Err(e) => return this.set_last_error_and_return_i32(e),
1136                };
1137
1138                // Allocate new buffer on the stack with the `u32` layout.
1139                let value_buffer = this.allocate(this.machine.layouts.u32, MemoryKind::Stack)?;
1140                this.write_int(ttl, &value_buffer)?;
1141                value_buffer
1142            } else {
1143                throw_unsup_format!(
1144                    "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP",
1145                );
1146            }
1147        } else if level == this.eval_libc_i32("IPPROTO_TCP") {
1148            let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY");
1149
1150            if option_name == opt_tcp_nodelay {
1151                let nodelay = match &*socket.state.borrow() {
1152                    SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>
1153                        throw_unsup_format!(
1154                            "getsockopt: reading option TCP_NODELAY on level IPPROTO_TCP is only supported \
1155                            on connected sockets"
1156                        ),
1157                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1158                        stream.nodelay(),
1159                    SocketState::ConnectionFailed(_) => unreachable!(),
1160                };
1161
1162                let nodelay = match nodelay {
1163                    Ok(nodelay) => nodelay,
1164                    Err(e) => return this.set_last_error_and_return_i32(e),
1165                };
1166
1167                // Allocate new buffer on the stack with the `i32` layout.
1168                let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;
1169                this.write_int(i32::from(nodelay), &value_buffer)?;
1170                value_buffer
1171            } else {
1172                throw_unsup_format!(
1173                    "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP"
1174                );
1175            }
1176        } else {
1177            throw_unsup_format!(
1178                "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \
1179                and IPPROTO_TCP are allowed"
1180            )
1181        };
1182
1183        // Truncated size of the output value.
1184        let output_value_len = value_buffer.layout.size.min(Size::from_bytes(option_len));
1185        // Copy the truncated value into the buffer pointed to by `option_value_ptr`.
1186        this.mem_copy(
1187            value_buffer.ptr(),
1188            option_value_ptr,
1189            // Truncate the value to fit the provided buffer.
1190            output_value_len,
1191            // The buffers are guaranteed to not overlap since the `value_buffer`
1192            // was just newly allocated on the stack.
1193            true,
1194        )?;
1195        // Deallocate the value buffer as it was only needed to store the value and
1196        // copy it into the buffer pointed to by `option_value_ptr`.
1197        this.deallocate_ptr(value_buffer.ptr(), None, MemoryKind::Stack)?;
1198
1199        // On output, the length pointer contains the amount of bytes written -- not the size
1200        // of the value before truncation.
1201        this.write_scalar(
1202            Scalar::from_uint(output_value_len.bytes(), socklen_layout.size),
1203            &option_len_ptr_mplace,
1204        )?;
1205
1206        interp_ok(Scalar::from_i32(0))
1207    }
1208
1209    fn getsockname(
1210        &mut self,
1211        socket: &OpTy<'tcx>,
1212        address: &OpTy<'tcx>,
1213        address_len: &OpTy<'tcx>,
1214    ) -> InterpResult<'tcx, Scalar> {
1215        let this = self.eval_context_mut();
1216
1217        let socket = this.read_scalar(socket)?.to_i32()?;
1218        let address_ptr = this.read_pointer(address)?;
1219        let address_len_ptr = this.read_pointer(address_len)?;
1220
1221        // Get the file handle
1222        let Some(fd) = this.machine.fds.get(socket) else {
1223            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1224        };
1225
1226        let Some(socket) = fd.downcast::<Socket>() else {
1227            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
1228            return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
1229        };
1230
1231        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
1232        this.ensure_not_failed(&socket, "getsockname")?;
1233
1234        let state = socket.state.borrow();
1235
1236        let address = match &*state {
1237            SocketState::Bound(address) => {
1238                if address.port() == 0 {
1239                    // The socket is bound to a zero-port which means it gets assigned a random
1240                    // port. Since we don't yet have an underlying socket, we don't know what this
1241                    // random port will be and thus this is unsupported.
1242                    throw_unsup_format!(
1243                        "getsockname: when the port is 0, getting the socket address before \
1244                        calling `listen` or `connect` is unsupported"
1245                    )
1246                }
1247
1248                *address
1249            }
1250            SocketState::Listening(listener) =>
1251                match listener.local_addr() {
1252                    Ok(address) => address,
1253                    Err(e) => return this.set_last_error_and_return_i32(e),
1254                },
1255            SocketState::Connecting(stream) | SocketState::Connected(stream) => {
1256                if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) {
1257                    // FIXME: On Windows hosts `TcpStream::local_addr` returns `0.0.0.0:0` whilst
1258                    // the socket is connecting:
1259                    // <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname#remarks>
1260                    // This is problematic because UNIX targets could expect a real local address even
1261                    // for a connecting non-blocking socket.
1262
1263                    static DEDUP: AtomicBool = AtomicBool::new(false);
1264                    if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {
1265                        this.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname);
1266                    }
1267                }
1268                match stream.local_addr() {
1269                    Ok(address) => address,
1270                    Err(e) => return this.set_last_error_and_return_i32(e),
1271                }
1272            }
1273            // For non-bound sockets the POSIX manual says the returned address is unspecified.
1274            // Often this is 0.0.0.0:0 and thus we set it to this value.
1275            SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),
1276            SocketState::ConnectionFailed(_) => unreachable!(),
1277        };
1278
1279        this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")
1280            .map(|_| Scalar::from_i32(0))
1281    }
1282
1283    fn getpeername(
1284        &mut self,
1285        socket: &OpTy<'tcx>,
1286        address: &OpTy<'tcx>,
1287        address_len: &OpTy<'tcx>,
1288        // Location where the output scalar is written to.
1289        dest: &MPlaceTy<'tcx>,
1290    ) -> InterpResult<'tcx> {
1291        let this = self.eval_context_mut();
1292
1293        let socket = this.read_scalar(socket)?.to_i32()?;
1294        let address_ptr = this.read_pointer(address)?;
1295        let address_len_ptr = this.read_pointer(address_len)?;
1296
1297        // Get the file handle
1298        let Some(fd) = this.machine.fds.get(socket) else {
1299            return this.set_last_error_and_return(LibcError("EBADF"), dest);
1300        };
1301
1302        let Some(socket) = fd.downcast::<Socket>() else {
1303            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
1304            return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);
1305        };
1306
1307        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
1308
1309        let dest = dest.clone();
1310
1311        // It's only safe to call [`TcpStream::peer_addr`] after the socket is connected since
1312        // UNIX targets should return ENOTCONN when the connection is not yet established.
1313        this.ensure_connected(
1314            socket.clone(),
1315            /* should_wait */ false,
1316            "getpeername",
1317            callback!(
1318                @capture<'tcx> {
1319                    socket: FileDescriptionRef<Socket>,
1320                    address_ptr: Pointer,
1321                    address_len_ptr: Pointer,
1322                    dest: MPlaceTy<'tcx>,
1323                } |this, result: Result<(), ()>| {
1324                    if result.is_err() {
1325                        return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)
1326                    };
1327
1328                    let SocketState::Connected(stream) = &*socket.state.borrow() else {
1329                        unreachable!()
1330                    };
1331
1332                    let address = match stream.peer_addr() {
1333                        Ok(address) => address,
1334                        Err(e) => return this.set_last_error_and_return(e, &dest),
1335                    };
1336
1337                    this.write_socket_address(
1338                        &address,
1339                        address_ptr,
1340                        address_len_ptr,
1341                        "getpeername",
1342                    )?;
1343                   this.write_scalar(Scalar::from_i32(0), &dest)
1344                }
1345            ),
1346        )
1347    }
1348
1349    fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
1350        let this = self.eval_context_mut();
1351
1352        let socket = this.read_scalar(socket)?.to_i32()?;
1353        let how = this.read_scalar(how)?.to_i32()?;
1354
1355        // Get the file handle
1356        let Some(fd) = this.machine.fds.get(socket) else {
1357            return this.set_last_error_and_return_i32(LibcError("EBADF"));
1358        };
1359
1360        let Some(socket) = fd.downcast::<Socket>() else {
1361            // Man page specifies to return ENOTSOCK if `fd` is not a socket.
1362            return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));
1363        };
1364
1365        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");
1366        this.ensure_not_failed(&socket, "shutdown")?;
1367
1368        let state = socket.state.borrow();
1369
1370        let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {
1371            return this.set_last_error_and_return_i32(LibcError("ENOTCONN"));
1372        };
1373
1374        let is_read_shutdown = how == this.eval_libc_i32("SHUT_RD");
1375        let is_write_shutdown = how == this.eval_libc_i32("SHUT_WR");
1376        let is_read_write_shutdown = how == this.eval_libc_i32("SHUT_RDWR");
1377
1378        let how = match () {
1379            _ if is_read_shutdown => Shutdown::Read,
1380            _ if is_write_shutdown => Shutdown::Write,
1381            _ if is_read_write_shutdown => Shutdown::Both,
1382            // An invalid value was passed to `how`.
1383            _ => return this.set_last_error_and_return_i32(LibcError("EINVAL")),
1384        };
1385
1386        if let Err(e) = stream.shutdown(how) {
1387            return this.set_last_error_and_return_i32(e);
1388        };
1389
1390        drop(state);
1391
1392        // Because we map cross platform mio readiness to epoll readiness and
1393        // the different platforms don't treat `shutdown` the same way, we set
1394        // the readiness after a `shutdown` manually to achieve more consistent
1395        // epoll readiness. Otherwise we do not generate enough epoll events
1396        // on partial shutdowns on Windows hosts.
1397        let mut readiness = socket.io_readiness.borrow_mut();
1398        // Closing the read end of a socket causes an EPOLLRDHUP event.
1399        readiness.read_closed |= is_read_shutdown || is_read_write_shutdown;
1400        // Only shutting down the write end doesn't cause an EPOLLHUP event
1401        // and thus we won't set the `write_closed` readiness for it here.
1402        readiness.write_closed |= is_read_write_shutdown;
1403        // The Linux kernel also sets EPOLLIN when both ends of a socket are closed:
1404        // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/tcp.c#L584-L588>
1405        readiness.readable |= is_read_write_shutdown;
1406
1407        drop(readiness);
1408
1409        // Update the epoll readiness for the socket.
1410        this.update_epoll_active_events(socket, /* force_edge */ false)?;
1411
1412        interp_ok(Scalar::from_i32(0))
1413    }
1414}
1415
1416impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
1417trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
1418    /// Block the thread until there's an incoming connection or an error occurred.
1419    ///
1420    /// This recursively calls itself should the operation still block for some reason.
1421    ///
1422    /// **Note**: This function is only safe to call when having previously ensured
1423    /// that the socket is in [`SocketState::Listening`].
1424    fn block_for_accept(
1425        &mut self,
1426        socket: FileDescriptionRef<Socket>,
1427        address_ptr: Pointer,
1428        address_len_ptr: Pointer,
1429        is_client_sock_nonblock: bool,
1430        dest: MPlaceTy<'tcx>,
1431    ) -> InterpResult<'tcx> {
1432        let this = self.eval_context_mut();
1433        this.block_thread_for_io(
1434            socket.clone(),
1435            BlockingIoInterest::Read,
1436            None,
1437            callback!(@capture<'tcx> {
1438                address_ptr: Pointer,
1439                address_len_ptr: Pointer,
1440                is_client_sock_nonblock: bool,
1441                socket: FileDescriptionRef<Socket>,
1442                dest: MPlaceTy<'tcx>,
1443            } |this, kind: UnblockKind| {
1444                assert_eq!(kind, UnblockKind::Ready);
1445
1446                // Remove the blocking I/O interest for unblocking this thread.
1447                this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1448
1449                match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? {
1450                    Ok(sockfd) => {
1451                        // We need to create the scalar using the destination size since
1452                        // `syscall(SYS_accept4, ...)` returns a long which doesn't match
1453                        // the int returned from the `accept`/`accept4` syscalls.
1454                        // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.
1455                        this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), &dest)
1456                    },
1457                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1458                        // We need to block the thread again as it would still block.
1459                        this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest)
1460                    }
1461                    Err(e) => this.set_last_error_and_return(e, &dest),
1462                }
1463            }),
1464        )
1465    }
1466
1467    /// Attempt to accept an incoming connection on the listening socket in a
1468    /// non-blocking manner.
1469    ///
1470    /// **Note**: This function is only safe to call when having previously ensured
1471    /// that the socket is in [`SocketState::Listening`].
1472    fn try_non_block_accept(
1473        &mut self,
1474        socket: &FileDescriptionRef<Socket>,
1475        address_ptr: Pointer,
1476        address_len_ptr: Pointer,
1477        is_client_sock_nonblock: bool,
1478    ) -> InterpResult<'tcx, Result<i32, IoError>> {
1479        let this = self.eval_context_mut();
1480
1481        let state = socket.state.borrow();
1482        let SocketState::Listening(listener) = &*state else {
1483            panic!(
1484                "try_non_block_accept must only be called when socket is in `SocketState::Listening`"
1485            )
1486        };
1487
1488        let (stream, addr) = match listener.accept() {
1489            Ok(peer) => peer,
1490            Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
1491                // We know that the source is not readable so we need to update its readiness.
1492                socket.io_readiness.borrow_mut().readable = false;
1493                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1494
1495                return interp_ok(Err(IoError::HostError(e)));
1496            }
1497            Err(e) => return interp_ok(Err(IoError::HostError(e))),
1498        };
1499
1500        let family = match addr {
1501            SocketAddr::V4(_) => SocketFamily::IPv4,
1502            SocketAddr::V6(_) => SocketFamily::IPv6,
1503        };
1504
1505        if address_ptr != Pointer::null() {
1506            // We only attempt a write if the address pointer is not a null pointer.
1507            // If the address pointer is a null pointer the user isn't interested in the
1508            // address and we don't need to write anything.
1509            this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")?;
1510        }
1511
1512        let fd = this.machine.fds.new_ref(Socket {
1513            family,
1514            state: RefCell::new(SocketState::Connected(stream)),
1515            is_non_block: Cell::new(is_client_sock_nonblock),
1516            io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),
1517            error: RefCell::new(None),
1518        });
1519        // Register the socket to the blocking I/O manager because
1520        // there is an associated host socket.
1521        this.machine.blocking_io.register(fd.clone());
1522        let sockfd = this.machine.fds.insert(fd);
1523        interp_ok(Ok(sockfd))
1524    }
1525
1526    /// Block the thread until we can send bytes into the connected socket
1527    /// or an error occurred.
1528    ///
1529    /// This recursively calls itself should the operation still block for some reason.
1530    ///
1531    /// **Note**: This function is only safe to call when having previously ensured
1532    /// that the socket is in [`SocketState::Connected`].
1533    fn block_for_send(
1534        &mut self,
1535        socket: FileDescriptionRef<Socket>,
1536        buffer_ptr: Pointer,
1537        length: usize,
1538        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1539    ) -> InterpResult<'tcx> {
1540        let this = self.eval_context_mut();
1541        this.block_thread_for_io(
1542            socket.clone(),
1543            BlockingIoInterest::Write,
1544            None,
1545            callback!(@capture<'tcx> {
1546                socket: FileDescriptionRef<Socket>,
1547                buffer_ptr: Pointer,
1548                length: usize,
1549                finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1550            } |this, kind: UnblockKind| {
1551                assert_eq!(kind, UnblockKind::Ready);
1552
1553                // Remove the blocking I/O interest for unblocking this thread.
1554                this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1555
1556                match this.try_non_block_send(&socket, buffer_ptr, length)? {
1557                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1558                        // We need to block the thread again as it would still block.
1559                        this.block_for_send(socket, buffer_ptr, length, finish)
1560                    },
1561                    result => finish.call(this, result)
1562                }
1563            }),
1564        )
1565    }
1566
1567    /// Attempt to send bytes into the connected socket in a non-blocking manner.
1568    ///
1569    /// **Note**: This function is only safe to call when having previously ensured
1570    /// that the socket is in [`SocketState::Connected`].
1571    fn try_non_block_send(
1572        &mut self,
1573        socket: &FileDescriptionRef<Socket>,
1574        buffer_ptr: Pointer,
1575        length: usize,
1576    ) -> InterpResult<'tcx, Result<usize, IoError>> {
1577        let this = self.eval_context_mut();
1578
1579        let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {
1580            panic!("try_non_block_send must only be called when the socket is connected")
1581        };
1582
1583        // This is a *non-blocking* write.
1584        let result = this.write_to_host(stream, length, buffer_ptr)?;
1585        match result {
1586            Err(IoError::HostError(e))
1587                if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1588            {
1589                // We know that the source is not writable so we need to update it's readiness.
1590                socket.io_readiness.borrow_mut().writable = false;
1591                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1592
1593                // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK
1594                // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.
1595                interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1596            }
1597            Ok(bytes_written) if bytes_written < length => {
1598                // We had a short write. On Unix hosts using the `epoll` and `kqueue` backends, a
1599                // short write means that the write buffer is full. We update the readiness
1600                // accordingly, which means that next time we see "writable" we will report an epoll
1601                // edge. Some applications (e.g. tokio) rely on this behavior; see
1602                // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L244-L264>.
1603                if cfg!(any(
1604                    // epoll
1605                    target_os = "android",
1606                    target_os = "illumos",
1607                    target_os = "linux",
1608                    target_os = "redox",
1609                    // kqueue
1610                    target_os = "dragonfly",
1611                    target_os = "freebsd",
1612                    target_os = "ios",
1613                    target_os = "macos",
1614                    target_os = "netbsd",
1615                    target_os = "openbsd",
1616                    target_os = "tvos",
1617                    target_os = "visionos",
1618                    target_os = "watchos",
1619                )) {
1620                    socket.io_readiness.borrow_mut().writable = false;
1621                    this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1622                } else {
1623                    // On hosts which don't use the `epoll` or `kqueue` backends, a short write
1624                    // doesn't imply a full write buffer. However, the target we are emulating might
1625                    // guarantee this behavior. To prevent applications from being stuck on such
1626                    // targets waiting on a new readiness event, we emit a new edge which still
1627                    // contains a writable readiness. This should trick the applications into trying
1628                    // another write which would then return EWOULDBLOCK should it really be full.
1629                    // This results in an unrealistic execution but we don't have another way of
1630                    // finding out whether the write buffer is full. The "default case" of linux
1631                    // host and linux target isn't affected by this.
1632                    this.update_epoll_active_events(socket.clone(), /* force_edge */ true)?;
1633                }
1634                interp_ok(result)
1635            }
1636            result => interp_ok(result),
1637        }
1638    }
1639
1640    /// Block the thread until we can receive bytes from the connected socket
1641    /// or an error occurred.
1642    ///
1643    /// This recursively calls itself should the operation still block for some reason.
1644    ///
1645    /// **Note**: This function is only safe to call when having previously ensured
1646    /// that the socket is in [`SocketState::Connected`].
1647    fn block_for_recv(
1648        &mut self,
1649        socket: FileDescriptionRef<Socket>,
1650        buffer_ptr: Pointer,
1651        length: usize,
1652        should_peek: bool,
1653        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1654    ) -> InterpResult<'tcx> {
1655        let this = self.eval_context_mut();
1656        this.block_thread_for_io(
1657            socket.clone(),
1658            BlockingIoInterest::Read,
1659            None,
1660            callback!(@capture<'tcx> {
1661                socket: FileDescriptionRef<Socket>,
1662                buffer_ptr: Pointer,
1663                length: usize,
1664                should_peek: bool,
1665                finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
1666            } |this, kind: UnblockKind| {
1667                assert_eq!(kind, UnblockKind::Ready);
1668
1669                // Remove the blocking I/O interest for unblocking this thread.
1670                this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1671
1672                match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {
1673                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {
1674                        // We need to block the thread again as it would still block.
1675                        this.block_for_recv(socket, buffer_ptr, length, should_peek, finish)
1676                    },
1677                    result => finish.call(this, result)
1678                }
1679            }),
1680        )
1681    }
1682
1683    /// Attempt to receive bytes from the connected socket in a non-blocking manner.
1684    ///
1685    /// **Note**: This function is only safe to call when having previously ensured
1686    /// that the socket is in [`SocketState::Connected`].
1687    fn try_non_block_recv(
1688        &mut self,
1689        socket: &FileDescriptionRef<Socket>,
1690        buffer_ptr: Pointer,
1691        length: usize,
1692        should_peek: bool,
1693    ) -> InterpResult<'tcx, Result<usize, IoError>> {
1694        let this = self.eval_context_mut();
1695
1696        let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {
1697            panic!("try_non_block_recv must only be called when the socket is connected")
1698        };
1699
1700        // This is a *non-blocking* read/peek.
1701        let result = this.read_from_host(
1702            |buf| {
1703                if should_peek { stream.peek(buf) } else { stream.read(buf) }
1704            },
1705            length,
1706            buffer_ptr,
1707        )?;
1708        match result {
1709            Err(IoError::HostError(e))
1710                if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>
1711            {
1712                // We know that the source is not readable so we need to update it's readiness.
1713                socket.io_readiness.borrow_mut().readable = false;
1714                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1715
1716                // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK
1717                // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.
1718                interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))
1719            }
1720            Ok(bytes_read) if !should_peek && bytes_read < length && bytes_read > 0 => {
1721                // We had a short read (and were not peeking). (Note that reading 0 bytes is guaranteed
1722                // to indicate EOF, and can never happen spuriously, so we have to exclude that case.)
1723                // On Unix hosts using the `epoll` and `kqueue` backends, a short read means that the
1724                // read buffer is empty. We update the readiness accordingly, which means that next time
1725                // we see "readable" we will report an epoll edge. Some applications (e.g. tokio) rely on
1726                // this behavior; see
1727                // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L190-L210>
1728                if cfg!(any(
1729                    // epoll
1730                    target_os = "android",
1731                    target_os = "illumos",
1732                    target_os = "linux",
1733                    target_os = "redox",
1734                    // kqueue
1735                    target_os = "dragonfly",
1736                    target_os = "freebsd",
1737                    target_os = "ios",
1738                    target_os = "macos",
1739                    target_os = "netbsd",
1740                    target_os = "openbsd",
1741                    target_os = "tvos",
1742                    target_os = "visionos",
1743                    target_os = "watchos",
1744                )) {
1745                    socket.io_readiness.borrow_mut().readable = false;
1746                    this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;
1747                } else {
1748                    // On hosts which don't use the `epoll` or `kqueue` backends, a short read
1749                    // doesn't imply an empty read buffer. However, the target we are emulating
1750                    // might guarantee this behavior. To prevent applications from being stuck on
1751                    // such targets waiting on a new readiness event, we emit a new edge which still
1752                    // contains a readable readiness. This should trick the applications into trying
1753                    // another read which would then return EWOULDBLOCK should it really be empty.
1754                    // This results in an unrealistic execution but we don't have another way of
1755                    // finding out whether the read buffer is empty. The "default case" of linux
1756                    // host and linux target isn't affected by this.
1757                    this.update_epoll_active_events(socket.clone(), /* force_edge */ true)?;
1758                }
1759                interp_ok(result)
1760            }
1761            result => interp_ok(result),
1762        }
1763    }
1764
1765    // Execute the provided callback function when the socket is either in
1766    // [`SocketState::Connected`] or an error occurred.
1767    /// If the socket is currently neither in the [`SocketState::Connecting`] nor
1768    /// the [`SocketState::Connecting`] state, [`Err`] is returned.
1769    /// When the callback function is called with [`Ok`], then we're guaranteed
1770    /// that the socket is in the [`SocketState::Connected`] state.
1771    ///
1772    /// This method internally calls `ensure_not_failed` and thus an unsupported
1773    /// error is thrown should `socket` be in [`SocketState::ConnectionFailed`].
1774    ///
1775    /// This function can optionally also block until either an error occurred or
1776    /// the socket reached the [`SocketState::Connected`] state.
1777    fn ensure_connected(
1778        &mut self,
1779        socket: FileDescriptionRef<Socket>,
1780        should_wait: bool,
1781        foreign_name: &'static str,
1782        action: DynMachineCallback<'tcx, Result<(), ()>>,
1783    ) -> InterpResult<'tcx> {
1784        let this = self.eval_context_mut();
1785
1786        let state = socket.state.borrow();
1787        match &*state {
1788            SocketState::Connecting(_) => { /* fall-through to below */ }
1789            SocketState::Connected(_) => {
1790                drop(state);
1791                return action.call(this, Ok(()));
1792            }
1793            _ => {
1794                drop(state);
1795                this.ensure_not_failed(&socket, foreign_name)?;
1796                return action.call(this, Err(()));
1797            }
1798        };
1799
1800        drop(state);
1801
1802        // We're currently connecting. Since the underlying mio socket is non-blocking,
1803        // the only way to determine whether we are done connecting is by polling.
1804        // If we should wait until the connection is established, the timeout is `None`.
1805        // Otherwise, we use a zero duration timeout, i.e. we return immediately
1806        // (but we still go through the scheduler once -- which is fine).
1807        let deadline =
1808            if should_wait { None } else { Some(this.machine.monotonic_clock.now().into()) };
1809
1810        this.block_thread_for_io(
1811            socket.clone(),
1812            BlockingIoInterest::Write,
1813            deadline,
1814            callback!(
1815                @capture<'tcx> {
1816                    socket: FileDescriptionRef<Socket>,
1817                    should_wait: bool,
1818                    foreign_name: &'static str,
1819                    action: DynMachineCallback<'tcx, Result<(), ()>>,
1820                } |this, kind: UnblockKind| {
1821                    // Remove the blocking I/O interest for unblocking this thread.
1822                    this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());
1823
1824                    if UnblockKind::TimedOut == kind {
1825                        // We can only time out when `should_wait` is false.
1826                        // This then means that the socket is not yet connected.
1827                        assert!(!should_wait);
1828                        return action.call(this, Err(()))
1829                    }
1830
1831                    // The thread woke up because it's ready, indicating a writeable or error event.
1832
1833                    let state = socket.state.borrow();
1834                    match &*state {
1835                        SocketState::Connecting(_) => { /* fall-through to below */ },
1836                        SocketState::Connected(_) => {
1837                            drop(state);
1838                            // This can happen because we blocked the thread:
1839                            // maybe another thread "upgraded" the connection in the meantime.
1840                            return action.call(this, Ok(()))
1841                        },
1842                        _ => {
1843                            drop(state);
1844                            // We ensured that we only block when we're currently connecting.
1845                            // Since this thread just got rescheduled, it could be that another
1846                            // thread realized that the connection failed and we're thus in
1847                            // an "invalid state".
1848                            this.ensure_not_failed(&socket, foreign_name)?;
1849                            return action.call(this, Err(()))
1850                        }
1851                    };
1852
1853                    drop(state);
1854
1855                    // Set `socket.error` if `socket` currently has an error.
1856                    this.update_last_error(&socket);
1857
1858                    if socket.error.borrow().is_some() {
1859                        // There was an error during connecting.
1860                        // It's the program's responsibility to read SO_ERROR itself.
1861                        return action.call(this, Err(()))
1862                    }
1863
1864                    // There was no error during connecting. Mio advises also reading the peer address
1865                    // to ensure that socket is actually connected and that it wasn't a spurious wake-up:
1866                    // <https://docs.rs/mio/latest/mio/net/struct.TcpStream.html#notes>
1867                    //
1868                    // Attempting to read the peer address would introduce an edge-case where the
1869                    // write end of the socket could already be shutdown before it received a
1870                    // writable event. When we then call [`TcpStream::peer_addr`] we receive an
1871                    // error. This would need extra state for storing whether the write end was
1872                    // manually closed using `shutdown`.
1873                    // Also, tokio doesn't read the peer address and everything seems to be fine,
1874                    // so we don't do that either:
1875                    // <https://github.com/tokio-rs/mio/issues/1942#issuecomment-4162607761>
1876                    // In other words, we are assuming that there will be no spurious
1877                    // wakeups while establishing the connection.
1878
1879                    // The connection is established.
1880
1881                    // Temporarily use dummy state to take ownership of the stream.
1882                    let mut state = socket.state.borrow_mut();
1883                    let SocketState::Connecting(stream) = std::mem::replace(&mut*state, SocketState::Initial) else {
1884                        // At the start of the function we ensured that we're currently connecting.
1885                        unreachable!()
1886                    };
1887                    *state = SocketState::Connected(stream);
1888                    drop(state);
1889                    action.call(this, Ok(()))
1890                }
1891            ),
1892        )
1893    }
1894
1895    /// Ensure that `socket` is not in the [`SocketState::ConnectionFailed`] state.
1896    /// If `socket` is currently in [`SocketState::ConnectionFailed`], an unsupported
1897    /// error is thrown.
1898    fn ensure_not_failed(
1899        &self,
1900        socket: &FileDescriptionRef<Socket>,
1901        foreign_name: &'static str,
1902    ) -> InterpResult<'tcx> {
1903        if let SocketState::ConnectionFailed(_) = &*socket.state.borrow() {
1904            throw_unsup_format!(
1905                "{foreign_name}: sockets are in an unspecified state after a failed `connect`; \
1906                any operation on such a socket is thus unsupported"
1907            );
1908        } else {
1909            interp_ok(())
1910        }
1911    }
1912
1913    /// Check whether the underlying host socket of `socket` contains an error.
1914    /// If there is an error, we store it in `socket.error`.
1915    ///
1916    /// Should `socket` be in the [`SocketState::Connecting`] state whilst there is
1917    /// an error on the host socket, we transition into the [`SocketState::ConnectionFailed`]
1918    /// state because we know that `socket` can no longer successfully establish a
1919    /// connection.
1920    fn update_last_error(&self, socket: &FileDescriptionRef<Socket>) {
1921        let mut state = socket.state.borrow_mut();
1922
1923        let new_error = match &*state {
1924            SocketState::Listening(listener) =>
1925                listener.take_error().expect("Reading SO_ERROR should not fail"),
1926            SocketState::Connecting(stream) | SocketState::Connected(stream) =>
1927                stream.take_error().expect("Reading SO_ERROR should not fail"),
1928            SocketState::Initial | SocketState::Bound(_) | SocketState::ConnectionFailed(_) => None,
1929        };
1930
1931        let Some(new_error) = new_error else { return };
1932
1933        // Store the error such that we can return it when
1934        // `getsockopt(SOL_SOCKET, SO_ERROR, ...)` is called on the socket.
1935        socket.error.replace(Some(new_error));
1936
1937        if matches!(&*state, SocketState::Connecting(_)) {
1938            // After reading an error on a connecting socket, we know that
1939            // the connection won't be established anymore. By the POSIX
1940            // specification, the socket is now in an unspecified state.
1941            // We thus change the socket state to `ConnectionFailed`.
1942
1943            // Temporarily use dummy state to take ownership of the stream.
1944            let SocketState::Connecting(stream) =
1945                std::mem::replace(&mut *state, SocketState::Initial)
1946            else {
1947                unreachable!()
1948            };
1949            *state = SocketState::ConnectionFailed(stream);
1950        }
1951    }
1952}
1953
1954impl VisitProvenance for FileDescriptionRef<Socket> {
1955    // A socket doesn't contain any references to machine memory
1956    // and thus we don't need to propagate the visit.
1957    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}
1958}
1959
1960impl SourceFileDescription for Socket {
1961    fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> {
1962        let mut state = self.state.borrow_mut();
1963        match &mut *state {
1964            SocketState::Listening(listener) => f(listener),
1965            SocketState::Connecting(stream)
1966            | SocketState::Connected(stream)
1967            | SocketState::ConnectionFailed(stream) => f(stream),
1968            // We never try adding a socket which is not backed by a real socket to the poll registry.
1969            _ => unreachable!(),
1970        }
1971    }
1972
1973    fn get_readiness_mut(&self) -> RefMut<'_, BlockingIoSourceReadiness> {
1974        self.io_readiness.borrow_mut()
1975    }
1976}