src/tools/miri/src/shims/unix/socket.rs RUST 2,096 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,096.
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;6use std::time::Duration;78use mio::event::Source;9use mio::net::{TcpListener, TcpStream};10use rustc_abi::Size;11use rustc_const_eval::interpret::{InterpResult, interp_ok};12use rustc_middle::throw_unsup_format;13use rustc_target::spec::Os;1415use crate::shims::files::{EvalContextExt as _, FdId, FileDescription, FileDescriptionRef};16use crate::shims::unix::UnixFileDescription;17use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _};18use crate::shims::unix::socket_address::EvalContextExt as _;19use crate::*;2021#[derive(Debug, PartialEq)]22enum SocketFamily {23    // IPv4 internet protocols24    IPv4,25    // IPv6 internet protocols26    IPv6,27}2829#[derive(Debug)]30enum SocketState {31    /// No syscall after `socket` has been made.32    Initial,33    /// The `bind` syscall has been called on the socket.34    /// This is only reachable from the [`SocketState::Initial`] state.35    Bound(SocketAddr),36    /// The `listen` syscall has been called on the socket.37    /// This is only reachable from the [`SocketState::Bound`] state.38    Listening(TcpListener),39    /// The `connect` syscall has been called and we weren't yet able40    /// to ensure the connection is established. This is only reachable41    /// from the [`SocketState::Initial`] state.42    Connecting(TcpStream),43    /// The `connect` syscall has been called on the socket and44    /// we ensured that the connection is established, or45    /// the socket was created by the `accept` syscall.46    /// For a socket created using the `connect` syscall, this is47    /// only reachable from the [`SocketState::Connecting`] state.48    Connected(TcpStream),49    /// The SO_ERROR socket option has been set after calling50    /// the `connect` syscall, indicating that the connection51    /// attempt failed. By the POSIX specification, a socket is52    /// is an unspecified state after a failed connection attempt53    /// and thus nothing (except destroying the socket) should be54    /// supported when a socket is in this state.55    ConnectionFailed(TcpStream),56}5758#[derive(Debug)]59struct Socket {60    /// Family of the socket, used to ensure socket only binds/connects to address of61    /// same family.62    family: SocketFamily,63    /// Current state of the inner socket.64    state: RefCell<SocketState>,65    /// Whether this fd is non-blocking or not.66    is_non_block: Cell<bool>,67    /// The current blocking I/O readiness of the file description.68    io_readiness: RefCell<BlockingIoSourceReadiness>,69    /// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`.70    error: RefCell<Option<io::Error>>,71    /// Read timeout of the socket. [`None`] means that reads can block indefinitely.72    /// The timeout is applied to the monotonic clock (the Unix specification doesn't73    /// specify which clock to use, but the monotonic clock is more common for74    /// relative timeouts).75    /// This is ignored when the socket is non-blocking.76    read_timeout: Cell<Option<Duration>>,77    /// Write timeout of the socket. [`None`] means that writes can block indefinitely.78    /// The timeout is applied to the monotonic clock (the Unix specification doesn't79    /// specify which clock to use, but the monotonic clock is more common80    /// for relative timeouts).81    /// This is ignored when the socket is non-blocking.82    write_timeout: Cell<Option<Duration>>,83}8485impl FileDescription for Socket {86    fn name(&self) -> &'static str {87        "socket"88    }8990    fn destroy<'tcx>(91        self,92        self_id: FdId,93        communicate_allowed: bool,94        ecx: &mut MiriInterpCx<'tcx>,95    ) -> InterpResult<'tcx, io::Result<()>> {96        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");9798        if matches!(99            &*self.state.borrow(),100            SocketState::Listening(_)101                | SocketState::Connecting(_)102                | SocketState::Connected(_)103                | SocketState::ConnectionFailed(_)104        ) {105            // There exists an associated host socket so we need to deregister it106            // from the blocking I/O manager.107            ecx.machine.blocking_io.deregister(self_id, self)108        };109110        interp_ok(Ok(()))111    }112113    fn read<'tcx>(114        self: FileDescriptionRef<Self>,115        communicate_allowed: bool,116        ptr: Pointer,117        len: usize,118        ecx: &mut MiriInterpCx<'tcx>,119        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,120    ) -> InterpResult<'tcx> {121        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");122123        let socket = self;124        let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.read_timeout.get());125126        ecx.ensure_connected(127            socket.clone(),128            deadline.clone(),129            "read",130            callback!(131                @capture<'tcx> {132                    socket: FileDescriptionRef<Socket>,133                    deadline: Option<Deadline>,134                    ptr: Pointer,135                    len: usize,136                    finish: DynMachineCallback<'tcx, Result<usize, IoError>>,137                } |this, result: Result<(), ()>| {138                    if result.is_err() {139                        return finish.call(this, Err(LibcError("ENOTCONN")))140                    }141142                    // Since `read` is the same as `recv` with no flags, we just treat143                    // the `read` as a `recv` here.144145                    if socket.is_non_block.get() {146                        // We have a non-blocking socket and thus don't want to block until147                        // we can read.148                        let result = this.try_non_block_recv(&socket, ptr, len, /* should_peek */ false)?;149                        finish.call(this, result)150                    } else {151                        // The socket is in blocking mode and thus the read call should block152                        // until we can read some bytes from the socket or the timeout exceeded.153                        this.block_for_recv(socket, deadline, ptr, len, /* should_peek */ false, finish)154                    }155                }156            ),157        )158    }159160    fn write<'tcx>(161        self: FileDescriptionRef<Self>,162        communicate_allowed: bool,163        ptr: Pointer,164        len: usize,165        ecx: &mut MiriInterpCx<'tcx>,166        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,167    ) -> InterpResult<'tcx> {168        assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");169170        let socket = self;171        let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.write_timeout.get());172173        ecx.ensure_connected(174            socket.clone(),175            deadline.clone(),176            "write",177            callback!(178                @capture<'tcx> {179                    socket: FileDescriptionRef<Socket>,180                    deadline: Option<Deadline>,181                    ptr: Pointer,182                    len: usize,183                    finish: DynMachineCallback<'tcx, Result<usize, IoError>>184                } |this, result: Result<(), ()>| {185                    if result.is_err() {186                        return finish.call(this, Err(LibcError("ENOTCONN")))187                    }188189                    // Since `write` is the same as `send` with no flags, we just treat190                    // the `write` as a `send` here.191192                    if socket.is_non_block.get() {193                        // We have a non-blocking socket and thus don't want to block until194                        // we can write.195                        let result = this.try_non_block_send(&socket, ptr, len)?;196                        return finish.call(this, result)197                    } else {198                        // The socket is in blocking mode and thus the write call should block199                        // until we can write some bytes into the socket or the timeout exceeded.200                        this.block_for_send(socket, deadline, ptr, len, finish)201                    }202                }203            ),204        )205    }206207    fn short_fd_operations(&self) -> bool {208        // Linux guarantees that when a read/write on a streaming socket comes back short,209        // the kernel buffer is empty/full:210        // See <https://man7.org/linux/man-pages/man7/epoll.7.html> in Q&A section.211        // So we can't do short reads/writes here.212        false213    }214215    fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {216        self217    }218219    fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {220        let mut flags = ecx.eval_libc_i32("O_RDWR");221222        if self.is_non_block.get() {223            flags |= ecx.eval_libc_i32("O_NONBLOCK");224        }225226        interp_ok(Scalar::from_i32(flags))227    }228229    fn set_flags<'tcx>(230        &self,231        mut flag: i32,232        ecx: &mut MiriInterpCx<'tcx>,233    ) -> InterpResult<'tcx, Scalar> {234        let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");235236        // O_NONBLOCK flag can be set / unset by user.237        if flag & o_nonblock == o_nonblock {238            self.is_non_block.set(true);239            flag &= !o_nonblock;240        } else {241            self.is_non_block.set(false);242        }243244        // Throw error if there is any unsupported flag.245        if flag != 0 {246            throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")247        }248249        interp_ok(Scalar::from_i32(0))250    }251}252253impl UnixFileDescription for Socket {254    fn ioctl<'tcx>(255        &self,256        op: Scalar,257        arg: Option<&OpTy<'tcx>>,258        ecx: &mut MiriInterpCx<'tcx>,259    ) -> InterpResult<'tcx, i32> {260        assert!(ecx.machine.communicate(), "cannot have `Socket` with isolation enabled!");261262        let fionbio = ecx.eval_libc("FIONBIO");263264        if op == fionbio {265            // On these OSes, Rust uses the ioctl, so we trust that it is reasonable and controls266            // the same internal flag as fcntl.267            if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)268            {269                // FIONBIO cannot be used to change the blocking mode of a socket on solarish targets:270                // <https://github.com/rust-lang/rust/commit/dda5c97675b4f5b1f6fdab64606c8a1f21021b0a>271                // Since there might be more targets which do weird things with this option, we use272                // an allowlist instead of just denying solarish targets.273                throw_unsup_format!(274                    "ioctl: setting FIONBIO on sockets is unsupported on target {}",275                    ecx.tcx.sess.target.os276                );277            }278279            let Some(value_ptr) = arg else {280                throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");281            };282            let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;283            let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;284            self.is_non_block.set(non_block);285            return interp_ok(0);286        }287288        throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");289    }290291    fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> {292        interp_ok(EpollReadiness::from(&*self.io_readiness.borrow()))293    }294}295296impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}297pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {298    /// For more information on the arguments see the socket manpage:299    /// <https://linux.die.net/man/2/socket>300    fn socket(301        &mut self,302        domain: &OpTy<'tcx>,303        type_: &OpTy<'tcx>,304        protocol: &OpTy<'tcx>,305    ) -> InterpResult<'tcx, Scalar> {306        let this = self.eval_context_mut();307308        let domain = this.read_scalar(domain)?.to_i32()?;309        let mut flags = this.read_scalar(type_)?.to_i32()?;310        let protocol = this.read_scalar(protocol)?.to_i32()?;311312        // Reject if isolation is enabled313        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {314            this.reject_in_isolation("`socket`", reject_with)?;315            return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));316        }317318        let mut is_sock_nonblock = false;319320        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so321        // if there is anything left at the end, that's an unsupported flag.322        if matches!(323            this.tcx.sess.target.os,324            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos325        ) {326            // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,327            // Solaris, and Illumos targets.328            let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");329            let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");330            if flags & sock_nonblock == sock_nonblock {331                is_sock_nonblock = true;332                flags &= !sock_nonblock;333            }334            if flags & sock_cloexec == sock_cloexec {335                // We don't support `exec` so we can ignore this.336                flags &= !sock_cloexec;337            }338        }339340        let family = if domain == this.eval_libc_i32("AF_INET") {341            SocketFamily::IPv4342        } else if domain == this.eval_libc_i32("AF_INET6") {343            SocketFamily::IPv6344        } else {345            throw_unsup_format!(346                "socket: domain {:#x} is unsupported, only AF_INET and \347                AF_INET6 are allowed.",348                domain349            );350        };351352        if flags != this.eval_libc_i32("SOCK_STREAM") {353            throw_unsup_format!(354                "socket: type {:#x} is unsupported, only SOCK_STREAM, \355                SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",356                flags357            );358        }359        if protocol != 0 && protocol != this.eval_libc_i32("IPPROTO_TCP") {360            throw_unsup_format!(361                "socket: socket protocol {protocol} is unsupported, \362                only IPPROTO_TCP and 0 are allowed"363            );364        }365366        let fds = &mut this.machine.fds;367        let fd = fds.new_ref(Socket {368            family,369            state: RefCell::new(SocketState::Initial),370            is_non_block: Cell::new(is_sock_nonblock),371            io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),372            error: RefCell::new(None),373            read_timeout: Cell::new(None),374            write_timeout: Cell::new(None),375        });376377        interp_ok(Scalar::from_i32(fds.insert(fd)))378    }379380    fn bind(381        &mut self,382        socket: &OpTy<'tcx>,383        address: &OpTy<'tcx>,384        address_len: &OpTy<'tcx>,385    ) -> InterpResult<'tcx, Scalar> {386        let this = self.eval_context_mut();387388        let socket = this.read_scalar(socket)?.to_i32()?;389        let address = match this.read_socket_address(address, address_len, "bind")? {390            Ok(addr) => addr,391            Err(e) => return this.set_errno_and_return_neg1_i32(e),392        };393394        // Get the file handle395        let Some(fd) = this.machine.fds.get(socket) else {396            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));397        };398399        let Some(socket) = fd.downcast::<Socket>() else {400            // Man page specifies to return ENOTSOCK if `fd` is not a socket.401            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));402        };403404        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");405        this.ensure_not_failed(&socket, "bind")?;406407        let mut state = socket.state.borrow_mut();408409        match *state {410            SocketState::Initial => {411                let address_family = match &address {412                    SocketAddr::V4(_) => SocketFamily::IPv4,413                    SocketAddr::V6(_) => SocketFamily::IPv6,414                };415416                if socket.family != address_family {417                    // Attempted to bind an address from a family that doesn't match418                    // the family of the socket.419                    let err = if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {420                        // Linux man page states that `EINVAL` is used when there is an address family mismatch.421                        // See <https://man7.org/linux/man-pages/man2/bind.2.html>422                        LibcError("EINVAL")423                    } else {424                        // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address425                        // family mismatch.426                        // See <https://man7.org/linux/man-pages/man3/bind.3p.html>427                        LibcError("EAFNOSUPPORT")428                    };429                    return this.set_errno_and_return_neg1_i32(err);430                }431432                *state = SocketState::Bound(address);433            }434            SocketState::Connecting(_) | SocketState::Connected(_) =>435                throw_unsup_format!(436                    "bind: socket is already connected and binding a437                    connected socket is unsupported"438                ),439            SocketState::Bound(_) | SocketState::Listening(_) =>440                throw_unsup_format!(441                    "bind: socket is already bound and binding a socket \442                    multiple times is unsupported"443                ),444            SocketState::ConnectionFailed(_) => unreachable!(),445        }446447        interp_ok(Scalar::from_i32(0))448    }449450    fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {451        let this = self.eval_context_mut();452453        let socket = this.read_scalar(socket)?.to_i32()?;454        // Since the backlog value is just a performance hint we can ignore it.455        let _backlog = this.read_scalar(backlog)?.to_i32()?;456457        // Get the file handle458        let Some(fd) = this.machine.fds.get(socket) else {459            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));460        };461462        let Some(socket) = fd.downcast::<Socket>() else {463            // Man page specifies to return ENOTSOCK if `fd` is not a socket.464            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));465        };466467        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");468        this.ensure_not_failed(&socket, "listen")?;469470        let mut state = socket.state.borrow_mut();471472        match *state {473            SocketState::Bound(socket_addr) =>474                match TcpListener::bind(socket_addr) {475                    Ok(listener) => {476                        *state = SocketState::Listening(listener);477                        drop(state);478                        // Register the socket to the blocking I/O manager because479                        // we now have an associated host socket.480                        this.machine.blocking_io.register(socket);481                    }482                    Err(e) => return this.set_errno_and_return_neg1_i32(e),483                },484            SocketState::Initial => {485                throw_unsup_format!(486                    "listen: listening on a socket which isn't bound is unsupported"487                )488            }489            SocketState::Listening(_) => {490                throw_unsup_format!("listen: listening on a socket multiple times is unsupported")491            }492            SocketState::Connecting(_) | SocketState::Connected(_) => {493                throw_unsup_format!("listen: listening on a connected socket is unsupported")494            }495            SocketState::ConnectionFailed(_) => unreachable!(),496        }497498        interp_ok(Scalar::from_i32(0))499    }500501    /// For more information on the arguments see the accept manpage:502    /// <https://linux.die.net/man/2/accept4>503    fn accept4(504        &mut self,505        socket: &OpTy<'tcx>,506        address: &OpTy<'tcx>,507        address_len: &OpTy<'tcx>,508        flags: Option<&OpTy<'tcx>>,509        // Location where the output scalar is written to.510        dest: &MPlaceTy<'tcx>,511    ) -> InterpResult<'tcx> {512        let this = self.eval_context_mut();513514        let socket = this.read_scalar(socket)?.to_i32()?;515        let address_ptr = this.read_pointer(address)?;516        let address_len_ptr = this.read_pointer(address_len)?;517        let mut flags =518            if let Some(flags) = flags { this.read_scalar(flags)?.to_i32()? } else { 0 };519520        // Get the file handle521        let Some(fd) = this.machine.fds.get(socket) else {522            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);523        };524525        let Some(socket) = fd.downcast::<Socket>() else {526            // Man page specifies to return ENOTSOCK if `fd` is not a socket.527            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);528        };529530        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");531        this.ensure_not_failed(&socket, "accept4")?;532533        if !matches!(*socket.state.borrow(), SocketState::Listening(_)) {534            throw_unsup_format!(535                "accept4: accepting incoming connections is only allowed when socket is listening"536            )537        };538539        let mut is_client_sock_nonblock = false;540541        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so542        // if there is anything left at the end, that's an unsupported flag.543        if matches!(544            this.tcx.sess.target.os,545            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos546        ) {547            // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,548            // Solaris, and Illumos targets.549            let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");550            let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");551            if flags & sock_nonblock == sock_nonblock {552                is_client_sock_nonblock = true;553                flags &= !sock_nonblock;554            }555            if flags & sock_cloexec == sock_cloexec {556                // We don't support `exec` so we can ignore this.557                flags &= !sock_cloexec;558            }559        }560561        if flags != 0 {562            throw_unsup_format!(563                "accept4: flag {flags:#x} is unsupported, only SOCK_CLOEXEC \564                and SOCK_NONBLOCK are allowed",565            );566        }567568        if socket.is_non_block.get() {569            // We have a non-blocking socket and thus don't want to block until570            // we can accept an incoming connection.571            match this.try_non_block_accept(572                &socket,573                address_ptr,574                address_len_ptr,575                is_client_sock_nonblock,576            )? {577                Ok(sockfd) => {578                    // We need to create the scalar using the destination size since579                    // `syscall(SYS_accept4, ...)` returns a long which doesn't match580                    // the int returned from the `accept`/`accept4` syscalls.581                    // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.582                    this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), dest)583                }584                Err(e) => this.set_errno_and_return_neg1(e, dest),585            }586        } else {587            // The socket is in blocking mode and thus the accept call should block588            // until an incoming connection is ready.589590            if socket.read_timeout.get().is_some() {591                // Some Unixes like Linux also apply the SO_RCVTIMEO socket option592                // to `accept` calls:593                // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/inet_connection_sock.c#L668-L675>594                // This is currently not supported by Miri.595                throw_unsup_format!(596                    "accept4: blocking accept is not supported when SO_RCVTIMEO is non-zero"597                )598            }599600            this.block_for_accept(601                socket,602                address_ptr,603                address_len_ptr,604                is_client_sock_nonblock,605                dest.clone(),606            )607        }608    }609610    fn connect(611        &mut self,612        socket: &OpTy<'tcx>,613        address: &OpTy<'tcx>,614        address_len: &OpTy<'tcx>,615        // Location where the output scalar is written to.616        dest: &MPlaceTy<'tcx>,617    ) -> InterpResult<'tcx> {618        let this = self.eval_context_mut();619620        let socket = this.read_scalar(socket)?.to_i32()?;621        let address = match this.read_socket_address(address, address_len, "connect")? {622            Ok(address) => address,623            Err(e) => return this.set_errno_and_return_neg1(e, dest),624        };625626        // Get the file handle627        let Some(fd) = this.machine.fds.get(socket) else {628            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);629        };630631        let Some(socket) = fd.downcast::<Socket>() else {632            // Man page specifies to return ENOTSOCK if `fd` is not a socket633            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);634        };635636        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");637        this.ensure_not_failed(&socket, "connect")?;638639        match &*socket.state.borrow() {640            SocketState::Initial => { /* fall-through to below */ }641            // The socket is already in a connecting state.642            SocketState::Connecting(_) =>643                return this.set_errno_and_return_neg1(LibcError("EALREADY"), dest),644            // We don't return EISCONN for already connected sockets, for which we're645            // sure that the connection is established, since TCP sockets are usually646            // allowed to be connected multiple times.647            _ =>648                throw_unsup_format!(649                    "connect: connecting is only supported for sockets which are neither \650                    bound, listening nor already connected"651                ),652        }653654        // This begins establishing the connection, but does not block until the stream is fully connected.655        // We deal with that below.656        match TcpStream::connect(address) {657            Ok(stream) => {658                *socket.state.borrow_mut() = SocketState::Connecting(stream);659                // Register the socket to the blocking I/O manager because660                // we now have an associated host socket.661                this.machine.blocking_io.register(socket.clone());662            }663            Err(e) => return this.set_errno_and_return_neg1(e, dest),664        };665666        if socket.is_non_block.get() {667            // We have a non-blocking socket and thus don't want to block until668            // the connection is established.669670            // Since the [`TcpStream::connect`] function of mio hides the EINPROGRESS671            // we just always return EINPROGRESS and check whether the connection succeeded672            // once we want to use the connected socket.673            this.set_errno_and_return_neg1(LibcError("EINPROGRESS"), dest)674        } else {675            // The socket is in blocking mode and thus the connect call should block676            // until the connection with the server is established.677678            if socket.write_timeout.get().is_some() {679                // Some Unixes like Linux also apply the SO_SNDTIMEO socket option680                // to `connect` calls:681                // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/af_inet.c#L701-L710>682                // This is currently not supported by Miri.683                throw_unsup_format!(684                    "connect: blocking connect is not supported when SO_SNDTIMEO is non-zero"685                )686            }687688            let dest = dest.clone();689            this.ensure_connected(690                socket.clone(),691                /* deadline */ None,692                "connect",693                callback!(694                    @capture<'tcx> {695                        socket: FileDescriptionRef<Socket>,696                        dest: MPlaceTy<'tcx>697                    } |this, result: Result<(), ()>| {698                        if result.is_err() {699                            // An error occurred whilst connecting. We know700                            // that it has been consumed by `ensure_connected`701                            // and is now stored in `socket.error`.702                            let err = socket.error.take().unwrap();703                            this.set_errno_and_return_neg1(err, &dest)704                        } else {705                            this.write_scalar(Scalar::from_i32(0), &dest)706                        }707                    }708                ),709            )710        }711    }712713    fn send(714        &mut self,715        socket: &OpTy<'tcx>,716        buffer: &OpTy<'tcx>,717        length: &OpTy<'tcx>,718        flags: &OpTy<'tcx>,719        // Location where the output scalar is written to.720        dest: &MPlaceTy<'tcx>,721    ) -> InterpResult<'tcx> {722        let this = self.eval_context_mut();723724        let socket = this.read_scalar(socket)?.to_i32()?;725        let buffer_ptr = this.read_pointer(buffer)?;726        let size_layout = this.libc_ty_layout("size_t");727        let length: usize =728            this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();729        let mut flags = this.read_scalar(flags)?.to_i32()?;730731        // Get the file handle732        let Some(fd) = this.machine.fds.get(socket) else {733            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);734        };735736        let Some(socket) = fd.downcast::<Socket>() else {737            // Man page specifies to return ENOTSOCK if `fd` is not a socket738            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);739        };740741        let mut is_op_non_block = false;742743        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so744        // if there is anything left at the end, that's an unsupported flag.745        if matches!(746            this.tcx.sess.target.os,747            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos748        ) {749            // MSG_NOSIGNAL and MSG_DONTWAIT only exist on Linux, Android, FreeBSD,750            // Solaris, and Illumos targets.751            let msg_nosignal = this.eval_libc_i32("MSG_NOSIGNAL");752            let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");753            if flags & msg_nosignal == msg_nosignal {754                // This is only needed to ensure that no EPIPE signal is sent when755                // trying to send into a stream which is no longer connected.756                // Since we don't support signals, we can ignore this.757                flags &= !msg_nosignal;758            }759            if flags & msg_dontwait == msg_dontwait {760                flags &= !msg_dontwait;761                is_op_non_block = true;762            }763        }764765        if flags != 0 {766            throw_unsup_format!(767                "send: flag {flags:#x} is unsupported, only MSG_NOSIGNAL and MSG_DONTWAIT are allowed",768            );769        }770771        let is_non_block = is_op_non_block || socket.is_non_block.get();772        let deadline = this.action_deadline(is_non_block, socket.write_timeout.get());773        let dest = dest.clone();774775        this.ensure_connected(776            socket.clone(),777            deadline.clone(),778            "send",779            callback!(780                @capture<'tcx> {781                    socket: FileDescriptionRef<Socket>,782                    deadline: Option<Deadline>,783                    flags: i32,784                    buffer_ptr: Pointer,785                    length: usize,786                    is_non_block: bool,787                    dest: MPlaceTy<'tcx>,788                } |this, result: Result<(), ()>| {789                    if result.is_err() {790                        return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)791                    }792793                    if is_non_block {794                        // We have a non-blocking operation or a non-blocking socket and795                        // thus don't want to block until we can send.796                        match this.try_non_block_send(&socket, buffer_ptr, length)? {797                            Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),798                            Err(e) => this.set_errno_and_return_neg1(e, &dest),799                        }800                    } else {801                        // The socket is in blocking mode and thus the send call should block802                        // until we can send some bytes into the socket or the timeout exceeded.803                        this.block_for_send(804                            socket,805                            deadline,806                            buffer_ptr,807                            length,808                            callback!(@capture<'tcx> {809                                dest: MPlaceTy<'tcx>810                            } |this, result: Result<usize, IoError>| {811                                match result {812                                    Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),813                                    Err(e) => this.set_errno_and_return_neg1(e, &dest)814                                }815                            }),816                        )817                    }818                }819            ),820        )821    }822823    fn recv(824        &mut self,825        socket: &OpTy<'tcx>,826        buffer: &OpTy<'tcx>,827        length: &OpTy<'tcx>,828        flags: &OpTy<'tcx>,829        // Location where the output scalar is written to.830        dest: &MPlaceTy<'tcx>,831    ) -> InterpResult<'tcx> {832        let this = self.eval_context_mut();833834        let socket = this.read_scalar(socket)?.to_i32()?;835        let buffer_ptr = this.read_pointer(buffer)?;836        let size_layout = this.libc_ty_layout("size_t");837        let length: usize =838            this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();839        let mut flags = this.read_scalar(flags)?.to_i32()?;840841        // Get the file handle842        let Some(fd) = this.machine.fds.get(socket) else {843            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);844        };845846        let Some(socket) = fd.downcast::<Socket>() else {847            // Man page specifies to return ENOTSOCK if `fd` is not a socket848            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);849        };850851        let mut should_peek = false;852        let mut is_op_non_block = false;853854        // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so855        // if there is anything left at the end, that's an unsupported flag.856857        let msg_peek = this.eval_libc_i32("MSG_PEEK");858        if flags & msg_peek == msg_peek {859            should_peek = true;860            flags &= !msg_peek;861        }862863        if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {864            // MSG_CMSG_CLOEXEC only exists on Linux, Android, FreeBSD,865            // and Illumos targets.866            let msg_cmsg_cloexec = this.eval_libc_i32("MSG_CMSG_CLOEXEC");867            if flags & msg_cmsg_cloexec == msg_cmsg_cloexec {868                // We don't support `exec` so we can ignore this.869                flags &= !msg_cmsg_cloexec;870            }871        }872873        if matches!(874            this.tcx.sess.target.os,875            Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos876        ) {877            // MSG_DONTWAIT only exists on Linux, Android, FreeBSD,878            // Solaris, and Illumos targets.879            let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");880            if flags & msg_dontwait == msg_dontwait {881                flags &= !msg_dontwait;882                is_op_non_block = true;883            }884        }885886        if flags != 0 {887            throw_unsup_format!(888                "recv: flag {flags:#x} is unsupported, only MSG_PEEK, MSG_DONTWAIT \889                and MSG_CMSG_CLOEXEC are allowed",890            );891        }892893        let is_non_block = is_op_non_block || socket.is_non_block.get();894        let deadline = this.action_deadline(is_non_block, socket.read_timeout.get());895        let dest = dest.clone();896897        this.ensure_connected(898            socket.clone(),899            deadline.clone(),900            "recv",901            callback!(902                @capture<'tcx> {903                    socket: FileDescriptionRef<Socket>,904                    deadline: Option<Deadline>,905                    buffer_ptr: Pointer,906                    length: usize,907                    should_peek: bool,908                    is_non_block: bool,909                    dest: MPlaceTy<'tcx>,910                } |this, result: Result<(), ()>| {911                    if result.is_err() {912                        return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)913                    }914915                    if is_non_block {916                        // We have a non-blocking operation or a non-blocking socket and917                        // thus don't want to block until we can receive.918                        match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {919                            Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),920                            Err(e) => this.set_errno_and_return_neg1(e, &dest),921                        }922                    } else {923                        // The socket is in blocking mode and thus the receive call should block924                        // until we can receive some bytes from the socket or the timeout exceeded.925                        this.block_for_recv(926                            socket,927                            deadline,928                            buffer_ptr,929                            length,930                            should_peek,931                            callback!(@capture<'tcx> {932                                dest: MPlaceTy<'tcx>933                            } |this, result: Result<usize, IoError>| {934                                match result {935                                    Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),936                                    Err(e) => this.set_errno_and_return_neg1(e, &dest)937                                }938                            }),939                        )940                    }941                }942            ),943        )944    }945946    fn setsockopt(947        &mut self,948        socket: &OpTy<'tcx>,949        level: &OpTy<'tcx>,950        option_name: &OpTy<'tcx>,951        option_value: &OpTy<'tcx>,952        option_len: &OpTy<'tcx>,953    ) -> InterpResult<'tcx, Scalar> {954        let this = self.eval_context_mut();955956        let socket = this.read_scalar(socket)?.to_i32()?;957        let level = this.read_scalar(level)?.to_i32()?;958        let option_name = this.read_scalar(option_name)?.to_i32()?;959        let option_value_ptr = this.read_pointer(option_value)?;960        let socklen_layout = this.libc_ty_layout("socklen_t");961        let option_len = this.read_scalar(option_len)?.to_int(socklen_layout.size)?;962963        // Get the file handle964        let Some(fd) = this.machine.fds.get(socket) else {965            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));966        };967968        let Some(socket) = fd.downcast::<Socket>() else {969            // Man page specifies to return ENOTSOCK if `fd` is not a socket.970            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));971        };972973        if level == this.eval_libc_i32("SOL_SOCKET") {974            let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO");975            let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO");976            let opt_so_reuseaddr = this.eval_libc_i32("SO_REUSEADDR");977978            if matches!(this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) {979                // SO_NOSIGPIPE only exists on MacOS, FreeBSD, and NetBSD.980                let opt_so_nosigpipe = this.eval_libc_i32("SO_NOSIGPIPE");981982                if option_name == opt_so_nosigpipe {983                    if option_len != 4 {984                        // Option value should be C-int which is usually 4 bytes.985                        return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));986                    }987                    let option_value =988                        this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);989                    let _val = this.read_scalar(&option_value)?.to_i32()?;990                    // We entirely ignore this value since we do not support signals anyway.991992                    return interp_ok(Scalar::from_i32(0));993                }994            }995996            if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo {997                let timeval_layout = this.libc_ty_layout("timeval");998                let option_value = this.ptr_to_mplace(option_value_ptr, timeval_layout);9991000                let timeout = match this.read_timeval(&option_value)? {1001                    None => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")),1002                    Some(Duration::ZERO) => None,1003                    Some(duration) => Some(duration),1004                };10051006                if option_name == opt_so_rcvtimeo {1007                    socket.read_timeout.set(timeout);1008                } else {1009                    socket.write_timeout.set(timeout);1010                }10111012                return interp_ok(Scalar::from_i32(0));1013            }10141015            if option_name == opt_so_reuseaddr {1016                if option_len != 4 {1017                    // Option value should be C-int which is usually 4 bytes.1018                    return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1019                }1020                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);1021                let _val = this.read_scalar(&option_value)?.to_i32()?;1022                // We entirely ignore this: std always sets REUSEADDR for us, and in the end it's more of a1023                // hint to bypass some arbitrary timeout anyway.1024                return interp_ok(Scalar::from_i32(0));1025            } else {1026                throw_unsup_format!(1027                    "setsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",1028                );1029            }1030        } else if level == this.eval_libc_i32("IPPROTO_IP") {1031            let opt_ip_ttl = this.eval_libc_i32("IP_TTL");10321033            if option_name == opt_ip_ttl {1034                if option_len != 4 {1035                    // Option value should be C-uint which is usually 4 bytes.1036                    return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1037                }1038                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.u32);1039                let ttl = this.read_scalar(&option_value)?.to_u32()?;10401041                let result = match &*socket.state.borrow() {1042                    SocketState::Initial | SocketState::Bound(_) =>1043                        throw_unsup_format!(1044                            "setsockopt: setting option IP_TTL on level IPPROTO_IP is only supported \1045                            on connected and listening sockets"1046                        ),1047                    SocketState::Listening(listener) => listener.set_ttl(ttl),1048                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>1049                        stream.set_ttl(ttl),1050                    SocketState::ConnectionFailed(_) => unreachable!(),1051                };10521053                return match result {1054                    Ok(_) => interp_ok(Scalar::from_i32(0)),1055                    Err(e) => this.set_errno_and_return_neg1_i32(e),1056                };1057            } else {1058                throw_unsup_format!(1059                    "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP",1060                );1061            }1062        } else if level == this.eval_libc_i32("IPPROTO_TCP") {1063            let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY");10641065            if option_name == opt_tcp_nodelay {1066                if option_len != 4 {1067                    // Option value should be C-int which is usually 4 bytes.1068                    return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1069                }1070                let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);1071                let nodelay = this.read_scalar(&option_value)?.to_i32()? != 0;10721073                let result = match &*socket.state.borrow() {1074                    SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>1075                        throw_unsup_format!(1076                            "setsockopt: setting option TCP_NODELAY on level IPPROTO_TCP is only supported \1077                            on connected sockets"1078                        ),1079                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>1080                        stream.set_nodelay(nodelay),1081                    SocketState::ConnectionFailed(_) => unreachable!(),1082                };10831084                return match result {1085                    Ok(_) => interp_ok(Scalar::from_i32(0)),1086                    Err(e) => this.set_errno_and_return_neg1_i32(e),1087                };1088            } else {1089                throw_unsup_format!(1090                    "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP"1091                );1092            }1093        }10941095        throw_unsup_format!(1096            "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \1097            and IPPROTO_TCP are allowed"1098        );1099    }11001101    fn getsockopt(1102        &mut self,1103        socket: &OpTy<'tcx>,1104        level: &OpTy<'tcx>,1105        option_name: &OpTy<'tcx>,1106        option_value: &OpTy<'tcx>,1107        option_len: &OpTy<'tcx>,1108    ) -> InterpResult<'tcx, Scalar> {1109        let this = self.eval_context_mut();11101111        let socket = this.read_scalar(socket)?.to_i32()?;1112        let level = this.read_scalar(level)?.to_i32()?;1113        let option_name = this.read_scalar(option_name)?.to_i32()?;1114        // These two pointers are used to return the value: `len_ptr` initially stores how much space1115        // is available. If the actual value fits into that space, it is written to1116        // `value_ptr` and `len_ptr` is updated to represent how many bytes1117        // were actually written. If the value does not fit, it is silently truncated.1118        // Also see <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getsockopt.html>.1119        let option_value_ptr = this.read_pointer(option_value)?;1120        let option_len_ptr = this.read_pointer(option_len)?;11211122        // Get the file handle1123        let Some(fd) = this.machine.fds.get(socket) else {1124            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1125        };11261127        let Some(socket) = fd.downcast::<Socket>() else {1128            // Man page specifies to return ENOTSOCK if `fd` is not a socket.1129            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));1130        };11311132        if option_value_ptr == Pointer::null() || option_len_ptr == Pointer::null() {1133            // This socket option returns a value and thus we need to return EFAULT1134            // when either the value or the length pointers are null pointers.1135            return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));1136        }11371138        let socklen_layout = this.libc_ty_layout("socklen_t");1139        let option_len_ptr_mplace = this.ptr_to_mplace(option_len_ptr, socklen_layout);1140        let option_len: usize = this1141            .read_scalar(&option_len_ptr_mplace)?1142            .to_int(socklen_layout.size)?1143            .try_into()1144            .unwrap();11451146        // We need a temporary buffer as `option_value_ptr` might not point to a large enough1147        // buffer, in which case we have to truncate.1148        let value_buffer = if level == this.eval_libc_i32("SOL_SOCKET") {1149            let opt_so_error = this.eval_libc_i32("SO_ERROR");1150            let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO");1151            let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO");11521153            if option_name == opt_so_error {1154                // Reading SO_ERROR should always return the latest async error. Because our stored1155                // `socket.error` could be outdated, we attempt to update it here.1156                this.update_last_error(&socket);11571158                let return_value = match socket.error.take() {1159                    Some(err) => this.io_error_to_errnum(err)?.to_i32()?,1160                    // If there is no error, we return 0 as the option value.1161                    None => 0,1162                };11631164                // Clear our own stored error -- it was either `take`n above or it is outdated.1165                socket.error.replace(None);11661167                // We know there is no longer an async error and thus we need to update the1168                // I/O and epoll readiness of the socket.1169                socket.io_readiness.borrow_mut().error = false;1170                this.update_epoll_active_events(socket, /* force_edge */ false)?;11711172                // Allocate new buffer on the stack with the `i32` layout.1173                let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;1174                this.write_int(return_value, &value_buffer)?;1175                value_buffer1176            } else if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo {1177                let timeout = if option_name == opt_so_rcvtimeo {1178                    socket.read_timeout.get()1179                } else {1180                    socket.write_timeout.get()1181                }1182                .unwrap_or_default();11831184                let secs = timeout.as_secs();1185                let usecs = timeout.subsec_micros();11861187                let timeval_layout = this.libc_ty_layout("timeval");1188                // Allocate new buffer on the stack with the `timeval` layout.1189                let timeval_buffer = this.allocate(timeval_layout, MemoryKind::Stack)?;11901191                let sec_field = this.project_field_named(&timeval_buffer, "tv_sec")?;1192                this.write_int(secs, &sec_field)?;11931194                let usec_field = this.project_field_named(&timeval_buffer, "tv_usec")?;1195                this.write_int(usecs, &usec_field)?;11961197                timeval_buffer1198            } else {1199                throw_unsup_format!(1200                    "getsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",1201                );1202            }1203        } else if level == this.eval_libc_i32("IPPROTO_IP") {1204            let opt_ip_ttl = this.eval_libc_i32("IP_TTL");12051206            if option_name == opt_ip_ttl {1207                let ttl = match &*socket.state.borrow() {1208                    SocketState::Initial | SocketState::Bound(_) =>1209                        throw_unsup_format!(1210                            "getsockopt: reading option IP_TTL on level IPPROTO_IP is only supported \1211                            on connected and listening sockets"1212                        ),1213                    SocketState::Listening(listener) => listener.ttl(),1214                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>1215                        stream.ttl(),1216                    SocketState::ConnectionFailed(_) => unreachable!(),1217                };12181219                let ttl = match ttl {1220                    Ok(ttl) => ttl,1221                    Err(e) => return this.set_errno_and_return_neg1_i32(e),1222                };12231224                // Allocate new buffer on the stack with the `u32` layout.1225                let value_buffer = this.allocate(this.machine.layouts.u32, MemoryKind::Stack)?;1226                this.write_int(ttl, &value_buffer)?;1227                value_buffer1228            } else {1229                throw_unsup_format!(1230                    "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP",1231                );1232            }1233        } else if level == this.eval_libc_i32("IPPROTO_TCP") {1234            let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY");12351236            if option_name == opt_tcp_nodelay {1237                let nodelay = match &*socket.state.borrow() {1238                    SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>1239                        throw_unsup_format!(1240                            "getsockopt: reading option TCP_NODELAY on level IPPROTO_TCP is only supported \1241                            on connected sockets"1242                        ),1243                    SocketState::Connecting(stream) | SocketState::Connected(stream) =>1244                        stream.nodelay(),1245                    SocketState::ConnectionFailed(_) => unreachable!(),1246                };12471248                let nodelay = match nodelay {1249                    Ok(nodelay) => nodelay,1250                    Err(e) => return this.set_errno_and_return_neg1_i32(e),1251                };12521253                // Allocate new buffer on the stack with the `i32` layout.1254                let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;1255                this.write_int(i32::from(nodelay), &value_buffer)?;1256                value_buffer1257            } else {1258                throw_unsup_format!(1259                    "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP"1260                );1261            }1262        } else {1263            throw_unsup_format!(1264                "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \1265                and IPPROTO_TCP are allowed"1266            )1267        };12681269        // Truncated size of the output value.1270        let output_value_len = value_buffer.layout.size.min(Size::from_bytes(option_len));1271        // Copy the truncated value into the buffer pointed to by `option_value_ptr`.1272        this.mem_copy(1273            value_buffer.ptr(),1274            option_value_ptr,1275            // Truncate the value to fit the provided buffer.1276            output_value_len,1277            // The buffers are guaranteed to not overlap since the `value_buffer`1278            // was just newly allocated on the stack.1279            true,1280        )?;1281        // Deallocate the value buffer as it was only needed to store the value and1282        // copy it into the buffer pointed to by `option_value_ptr`.1283        this.deallocate_ptr(value_buffer.ptr(), None, MemoryKind::Stack)?;12841285        // On output, the length pointer contains the amount of bytes written -- not the size1286        // of the value before truncation.1287        this.write_scalar(1288            Scalar::from_uint(output_value_len.bytes(), socklen_layout.size),1289            &option_len_ptr_mplace,1290        )?;12911292        interp_ok(Scalar::from_i32(0))1293    }12941295    fn getsockname(1296        &mut self,1297        socket: &OpTy<'tcx>,1298        address: &OpTy<'tcx>,1299        address_len: &OpTy<'tcx>,1300    ) -> InterpResult<'tcx, Scalar> {1301        let this = self.eval_context_mut();13021303        let socket = this.read_scalar(socket)?.to_i32()?;1304        let address_ptr = this.read_pointer(address)?;1305        let address_len_ptr = this.read_pointer(address_len)?;13061307        // Get the file handle1308        let Some(fd) = this.machine.fds.get(socket) else {1309            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1310        };13111312        let Some(socket) = fd.downcast::<Socket>() else {1313            // Man page specifies to return ENOTSOCK if `fd` is not a socket.1314            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));1315        };13161317        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");1318        this.ensure_not_failed(&socket, "getsockname")?;13191320        let state = socket.state.borrow();13211322        let address = match &*state {1323            SocketState::Bound(address) => {1324                if address.port() == 0 {1325                    // The socket is bound to a zero-port which means it gets assigned a random1326                    // port. Since we don't yet have an underlying socket, we don't know what this1327                    // random port will be and thus this is unsupported.1328                    throw_unsup_format!(1329                        "getsockname: when the port is 0, getting the socket address before \1330                        calling `listen` or `connect` is unsupported"1331                    )1332                }13331334                *address1335            }1336            SocketState::Listening(listener) =>1337                match listener.local_addr() {1338                    Ok(address) => address,1339                    Err(e) => return this.set_errno_and_return_neg1_i32(e),1340                },1341            SocketState::Connecting(stream) | SocketState::Connected(stream) => {1342                if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) {1343                    // FIXME: On Windows hosts `TcpStream::local_addr` returns `0.0.0.0:0` whilst1344                    // the socket is connecting:1345                    // <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname#remarks>1346                    // This is problematic because UNIX targets could expect a real local address even1347                    // for a connecting non-blocking socket.13481349                    static DEDUP: AtomicBool = AtomicBool::new(false);1350                    if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {1351                        this.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname);1352                    }1353                }1354                match stream.local_addr() {1355                    Ok(address) => address,1356                    Err(e) => return this.set_errno_and_return_neg1_i32(e),1357                }1358            }1359            // For non-bound sockets the POSIX manual says the returned address is unspecified.1360            // Often this is 0.0.0.0:0 and thus we set it to this value.1361            SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),1362            SocketState::ConnectionFailed(_) => unreachable!(),1363        };13641365        this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")1366            .map(|_| Scalar::from_i32(0))1367    }13681369    fn getpeername(1370        &mut self,1371        socket: &OpTy<'tcx>,1372        address: &OpTy<'tcx>,1373        address_len: &OpTy<'tcx>,1374        // Location where the output scalar is written to.1375        dest: &MPlaceTy<'tcx>,1376    ) -> InterpResult<'tcx> {1377        let this = self.eval_context_mut();13781379        let socket = this.read_scalar(socket)?.to_i32()?;1380        let address_ptr = this.read_pointer(address)?;1381        let address_len_ptr = this.read_pointer(address_len)?;13821383        // Get the file handle1384        let Some(fd) = this.machine.fds.get(socket) else {1385            return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);1386        };13871388        let Some(socket) = fd.downcast::<Socket>() else {1389            // Man page specifies to return ENOTSOCK if `fd` is not a socket.1390            return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);1391        };13921393        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");13941395        let dest = dest.clone();13961397        // It's only safe to call [`TcpStream::peer_addr`] after the socket is connected since1398        // UNIX targets should return ENOTCONN when the connection is not yet established.1399        this.ensure_connected(1400            socket.clone(),1401            // Check whether the socket is connected without blocking.1402            Some(this.machine.monotonic_clock.now().into()),1403            "getpeername",1404            callback!(1405                @capture<'tcx> {1406                    socket: FileDescriptionRef<Socket>,1407                    address_ptr: Pointer,1408                    address_len_ptr: Pointer,1409                    dest: MPlaceTy<'tcx>,1410                } |this, result: Result<(), ()>| {1411                    if result.is_err() {1412                        return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)1413                    };14141415                    let SocketState::Connected(stream) = &*socket.state.borrow() else {1416                        unreachable!()1417                    };14181419                    let address = match stream.peer_addr() {1420                        Ok(address) => address,1421                        Err(e) => return this.set_errno_and_return_neg1(e, &dest),1422                    };14231424                    this.write_socket_address(1425                        &address,1426                        address_ptr,1427                        address_len_ptr,1428                        "getpeername",1429                    )?;1430                   this.write_scalar(Scalar::from_i32(0), &dest)1431                }1432            ),1433        )1434    }14351436    fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1437        let this = self.eval_context_mut();14381439        let socket = this.read_scalar(socket)?.to_i32()?;1440        let how = this.read_scalar(how)?.to_i32()?;14411442        // Get the file handle1443        let Some(fd) = this.machine.fds.get(socket) else {1444            return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1445        };14461447        let Some(socket) = fd.downcast::<Socket>() else {1448            // Man page specifies to return ENOTSOCK if `fd` is not a socket.1449            return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));1450        };14511452        assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");1453        this.ensure_not_failed(&socket, "shutdown")?;14541455        let state = socket.state.borrow();14561457        let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {1458            return this.set_errno_and_return_neg1_i32(LibcError("ENOTCONN"));1459        };14601461        let is_read_shutdown = how == this.eval_libc_i32("SHUT_RD");1462        let is_write_shutdown = how == this.eval_libc_i32("SHUT_WR");1463        let is_read_write_shutdown = how == this.eval_libc_i32("SHUT_RDWR");14641465        let how = match () {1466            _ if is_read_shutdown => Shutdown::Read,1467            _ if is_write_shutdown => Shutdown::Write,1468            _ if is_read_write_shutdown => Shutdown::Both,1469            // An invalid value was passed to `how`.1470            _ => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")),1471        };14721473        if let Err(e) = stream.shutdown(how) {1474            return this.set_errno_and_return_neg1_i32(e);1475        };14761477        drop(state);14781479        // Because we map cross platform mio readiness to epoll readiness and1480        // the different platforms don't treat `shutdown` the same way, we set1481        // the readiness after a `shutdown` manually to achieve more consistent1482        // epoll readiness. Otherwise we do not generate enough epoll events1483        // on partial shutdowns on Windows hosts.1484        let mut readiness = socket.io_readiness.borrow_mut();1485        // Closing the read end of a socket causes an EPOLLRDHUP event.1486        readiness.read_closed |= is_read_shutdown || is_read_write_shutdown;1487        // Only shutting down the write end doesn't cause an EPOLLHUP event1488        // and thus we won't set the `write_closed` readiness for it here.1489        readiness.write_closed |= is_read_write_shutdown;1490        // The Linux kernel also sets EPOLLIN when both ends of a socket are closed:1491        // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/tcp.c#L584-L588>1492        readiness.readable |= is_read_write_shutdown;14931494        drop(readiness);14951496        // Update the epoll readiness for the socket.1497        this.update_epoll_active_events(socket, /* force_edge */ false)?;14981499        interp_ok(Scalar::from_i32(0))1500    }1501}15021503impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}1504trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {1505    /// Get the deadline for an action (e.g. reading or writing).1506    /// When `is_non_block` is [`true`], the returned deadline is "now", i.e.,1507    /// we wake up immediately if the action cannot be completed.1508    /// If `action_timeout` is `Some(duration)`, the returned deadline is in the1509    /// future be the specified `duration`. Otherwise, no deadline ([`None`]) is1510    /// returned, indicating that the action can block indefinitely.1511    fn action_deadline(1512        &self,1513        is_non_block: bool,1514        action_timeout: Option<Duration>,1515    ) -> Option<Deadline> {1516        let this = self.eval_context_ref();15171518        if is_non_block {1519            // Non-blocking sockets always have a zero timeout.1520            Some(this.machine.monotonic_clock.now().into())1521        } else {1522            action_timeout1523                .map(|duration| this.machine.monotonic_clock.now().add_lossy(duration).into())1524        }1525    }15261527    /// Block the thread until there's an incoming connection 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 ensured1532    /// that the socket is in [`SocketState::Listening`].1533    fn block_for_accept(1534        &mut self,1535        socket: FileDescriptionRef<Socket>,1536        address_ptr: Pointer,1537        address_len_ptr: Pointer,1538        is_client_sock_nonblock: bool,1539        dest: MPlaceTy<'tcx>,1540    ) -> InterpResult<'tcx> {1541        let this = self.eval_context_mut();1542        this.block_thread_for_io(1543            socket.clone(),1544            BlockingIoInterest::Read,1545            /* deadline */ None,1546            callback!(@capture<'tcx> {1547                socket: FileDescriptionRef<Socket>,1548                address_ptr: Pointer,1549                address_len_ptr: Pointer,1550                is_client_sock_nonblock: bool,1551                dest: MPlaceTy<'tcx>,1552            } |this, kind: UnblockKind| {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());15551556                match kind {1557                    UnblockKind::Ready => { /* fall-through to below */ },1558                    // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.1559                    UnblockKind::TimedOut => return this.set_errno_and_return_neg1(LibcError("EWOULDBLOCK"), &dest)1560                }15611562                match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? {1563                    Ok(sockfd) => {1564                        // We need to create the scalar using the destination size since1565                        // `syscall(SYS_accept4, ...)` returns a long which doesn't match1566                        // the int returned from the `accept`/`accept4` syscalls.1567                        // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.1568                        this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), &dest)1569                    },1570                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1571                        // We need to block the thread again as it would still block.1572                        this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest)1573                    }1574                    Err(e) => this.set_errno_and_return_neg1(e, &dest),1575                }1576            }),1577        )1578    }15791580    /// Attempt to accept an incoming connection on the listening socket in a1581    /// non-blocking manner.1582    ///1583    /// **Note**: This function is only safe to call when having previously ensured1584    /// that the socket is in [`SocketState::Listening`].1585    fn try_non_block_accept(1586        &mut self,1587        socket: &FileDescriptionRef<Socket>,1588        address_ptr: Pointer,1589        address_len_ptr: Pointer,1590        is_client_sock_nonblock: bool,1591    ) -> InterpResult<'tcx, Result<i32, IoError>> {1592        let this = self.eval_context_mut();15931594        let state = socket.state.borrow();1595        let SocketState::Listening(listener) = &*state else {1596            panic!(1597                "try_non_block_accept must only be called when socket is in `SocketState::Listening`"1598            )1599        };16001601        let (stream, addr) = match listener.accept() {1602            Ok(peer) => peer,1603            Err(e) if e.kind() == io::ErrorKind::WouldBlock => {1604                // We know that the source is not readable so we need to update its readiness.1605                socket.io_readiness.borrow_mut().readable = false;1606                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;16071608                return interp_ok(Err(IoError::HostError(e)));1609            }1610            Err(e) => return interp_ok(Err(IoError::HostError(e))),1611        };16121613        let family = match addr {1614            SocketAddr::V4(_) => SocketFamily::IPv4,1615            SocketAddr::V6(_) => SocketFamily::IPv6,1616        };16171618        if address_ptr != Pointer::null() {1619            // We only attempt a write if the address pointer is not a null pointer.1620            // If the address pointer is a null pointer the user isn't interested in the1621            // address and we don't need to write anything.1622            this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")?;1623        }16241625        let fd = this.machine.fds.new_ref(Socket {1626            family,1627            state: RefCell::new(SocketState::Connected(stream)),1628            is_non_block: Cell::new(is_client_sock_nonblock),1629            io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),1630            error: RefCell::new(None),1631            read_timeout: Cell::new(None),1632            write_timeout: Cell::new(None),1633        });1634        // Register the socket to the blocking I/O manager because1635        // there is an associated host socket.1636        this.machine.blocking_io.register(fd.clone());1637        let sockfd = this.machine.fds.insert(fd);1638        interp_ok(Ok(sockfd))1639    }16401641    /// Block the thread until we can send bytes into the connected socket1642    /// or an error occurred.1643    ///1644    /// This recursively calls itself should the operation still block for some reason.1645    ///1646    /// **Note**: This function is only safe to call when having previously ensured1647    /// that the socket is in [`SocketState::Connected`].1648    fn block_for_send(1649        &mut self,1650        socket: FileDescriptionRef<Socket>,1651        deadline: Option<Deadline>,1652        buffer_ptr: Pointer,1653        length: usize,1654        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1655    ) -> InterpResult<'tcx> {1656        let this = self.eval_context_mut();1657        this.block_thread_for_io(1658            socket.clone(),1659            BlockingIoInterest::Write,1660            deadline.clone(),1661            callback!(@capture<'tcx> {1662                socket: FileDescriptionRef<Socket>,1663                deadline: Option<Deadline>,1664                buffer_ptr: Pointer,1665                length: usize,1666                finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1667            } |this, kind: UnblockKind| {1668                // Remove the blocking I/O interest for unblocking this thread.1669                this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());16701671                match kind {1672                    UnblockKind::Ready => { /* fall-through to below */ },1673                    // When the write timeout is exceeded EAGAIN/EWOULDBLOCK is returned.1674                    UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))1675                }16761677                match this.try_non_block_send(&socket, buffer_ptr, length)? {1678                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1679                        // We need to block the thread again as it would still block.1680                        this.block_for_send(socket, deadline, buffer_ptr, length, finish)1681                    },1682                    result => finish.call(this, result)1683                }1684            }),1685        )1686    }16871688    /// Attempt to send bytes into the connected socket in a non-blocking manner.1689    ///1690    /// **Note**: This function is only safe to call when having previously ensured1691    /// that the socket is in [`SocketState::Connected`].1692    fn try_non_block_send(1693        &mut self,1694        socket: &FileDescriptionRef<Socket>,1695        buffer_ptr: Pointer,1696        length: usize,1697    ) -> InterpResult<'tcx, Result<usize, IoError>> {1698        let this = self.eval_context_mut();16991700        let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {1701            panic!("try_non_block_send must only be called when the socket is connected")1702        };17031704        // This is a *non-blocking* write.1705        let result = this.write_to_host(stream, length, buffer_ptr)?;1706        match result {1707            Err(IoError::HostError(e))1708                if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>1709            {1710                // We know that the source is not writable so we need to update it's readiness.1711                socket.io_readiness.borrow_mut().writable = false;1712                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;17131714                // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK1715                // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.1716                interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))1717            }1718            Ok(bytes_written) if bytes_written < length => {1719                // We had a short write. On Unix hosts using the `epoll` and `kqueue` backends, a1720                // short write means that the write buffer is full. We update the readiness1721                // accordingly, which means that next time we see "writable" we will report an epoll1722                // edge. Some applications (e.g. tokio) rely on this behavior; see1723                // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L244-L264>.1724                if cfg!(any(1725                    // epoll1726                    target_os = "android",1727                    target_os = "illumos",1728                    target_os = "linux",1729                    target_os = "redox",1730                    // kqueue1731                    target_os = "dragonfly",1732                    target_os = "freebsd",1733                    target_os = "ios",1734                    target_os = "macos",1735                    target_os = "netbsd",1736                    target_os = "openbsd",1737                    target_os = "tvos",1738                    target_os = "visionos",1739                    target_os = "watchos",1740                )) {1741                    socket.io_readiness.borrow_mut().writable = false;1742                    this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;1743                } else {1744                    // On hosts which don't use the `epoll` or `kqueue` backends, a short write1745                    // doesn't imply a full write buffer. However, the target we are emulating might1746                    // guarantee this behavior. To prevent applications from being stuck on such1747                    // targets waiting on a new readiness event, we emit a new edge which still1748                    // contains a writable readiness. This should trick the applications into trying1749                    // another write which would then return EWOULDBLOCK should it really be full.1750                    // This results in an unrealistic execution but we don't have another way of1751                    // finding out whether the write buffer is full. The "default case" of linux1752                    // host and linux target isn't affected by this.1753                    this.update_epoll_active_events(socket.clone(), /* force_edge */ true)?;1754                }1755                interp_ok(result)1756            }1757            result => interp_ok(result),1758        }1759    }17601761    /// Block the thread until we can receive bytes from the connected socket1762    /// or an error occurred.1763    ///1764    /// This recursively calls itself should the operation still block for some reason.1765    ///1766    /// **Note**: This function is only safe to call when having previously ensured1767    /// that the socket is in [`SocketState::Connected`].1768    fn block_for_recv(1769        &mut self,1770        socket: FileDescriptionRef<Socket>,1771        deadline: Option<Deadline>,1772        buffer_ptr: Pointer,1773        length: usize,1774        should_peek: bool,1775        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1776    ) -> InterpResult<'tcx> {1777        let this = self.eval_context_mut();1778        this.block_thread_for_io(1779            socket.clone(),1780            BlockingIoInterest::Read,1781            deadline.clone(),1782            callback!(@capture<'tcx> {1783                socket: FileDescriptionRef<Socket>,1784                deadline: Option<Deadline>,1785                buffer_ptr: Pointer,1786                length: usize,1787                should_peek: bool,1788                finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1789            } |this, kind: UnblockKind| {1790                // Remove the blocking I/O interest for unblocking this thread.1791                this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());17921793                match kind {1794                    UnblockKind::Ready => { /* fall-through to below */ },1795                    // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.1796                    UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))1797                }17981799                match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {1800                    Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1801                        // We need to block the thread again as it would still block.1802                        this.block_for_recv(socket, deadline, buffer_ptr, length, should_peek, finish)1803                    },1804                    result => finish.call(this, result)1805                }1806            }),1807        )1808    }18091810    /// Attempt to receive bytes from the connected socket in a non-blocking manner.1811    ///1812    /// **Note**: This function is only safe to call when having previously ensured1813    /// that the socket is in [`SocketState::Connected`].1814    fn try_non_block_recv(1815        &mut self,1816        socket: &FileDescriptionRef<Socket>,1817        buffer_ptr: Pointer,1818        length: usize,1819        should_peek: bool,1820    ) -> InterpResult<'tcx, Result<usize, IoError>> {1821        let this = self.eval_context_mut();18221823        let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {1824            panic!("try_non_block_recv must only be called when the socket is connected")1825        };18261827        // This is a *non-blocking* read/peek.1828        let result = this.read_from_host(1829            |buf| {1830                if should_peek { stream.peek(buf) } else { stream.read(buf) }1831            },1832            length,1833            buffer_ptr,1834        )?;1835        match result {1836            Err(IoError::HostError(e))1837                if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>1838            {1839                // We know that the source is not readable so we need to update it's readiness.1840                socket.io_readiness.borrow_mut().readable = false;1841                this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;18421843                // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK1844                // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.1845                interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))1846            }1847            Ok(bytes_read) if !should_peek && bytes_read < length && bytes_read > 0 => {1848                // We had a short read (and were not peeking). (Note that reading 0 bytes is guaranteed1849                // to indicate EOF, and can never happen spuriously, so we have to exclude that case.)1850                // On Unix hosts using the `epoll` and `kqueue` backends, a short read means that the1851                // read buffer is empty. We update the readiness accordingly, which means that next time1852                // we see "readable" we will report an epoll edge. Some applications (e.g. tokio) rely on1853                // this behavior; see1854                // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L190-L210>1855                if cfg!(any(1856                    // epoll1857                    target_os = "android",1858                    target_os = "illumos",1859                    target_os = "linux",1860                    target_os = "redox",1861                    // kqueue1862                    target_os = "dragonfly",1863                    target_os = "freebsd",1864                    target_os = "ios",1865                    target_os = "macos",1866                    target_os = "netbsd",1867                    target_os = "openbsd",1868                    target_os = "tvos",1869                    target_os = "visionos",1870                    target_os = "watchos",1871                )) {1872                    socket.io_readiness.borrow_mut().readable = false;1873                    this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;1874                } else {1875                    // On hosts which don't use the `epoll` or `kqueue` backends, a short read1876                    // doesn't imply an empty read buffer. However, the target we are emulating1877                    // might guarantee this behavior. To prevent applications from being stuck on1878                    // such targets waiting on a new readiness event, we emit a new edge which still1879                    // contains a readable readiness. This should trick the applications into trying1880                    // another read which would then return EWOULDBLOCK should it really be empty.1881                    // This results in an unrealistic execution but we don't have another way of1882                    // finding out whether the read buffer is empty. The "default case" of linux1883                    // host and linux target isn't affected by this.1884                    this.update_epoll_active_events(socket.clone(), /* force_edge */ true)?;1885                }1886                interp_ok(result)1887            }1888            result => interp_ok(result),1889        }1890    }18911892    // Execute the provided callback function when the socket is either in1893    // [`SocketState::Connected`] or an error occurred.1894    /// If the socket is currently neither in the [`SocketState::Connecting`] nor1895    /// the [`SocketState::Connecting`] state, [`Err`] is returned.1896    /// When the callback function is called with [`Ok`], then we're guaranteed1897    /// that the socket is in the [`SocketState::Connected`] state.1898    ///1899    /// This method internally calls `ensure_not_failed` and thus an unsupported1900    /// error is thrown should `socket` be in [`SocketState::ConnectionFailed`].1901    ///1902    /// This function can optionally also block until either an error occurred or1903    /// the socket reached the [`SocketState::Connected`] state.1904    fn ensure_connected(1905        &mut self,1906        socket: FileDescriptionRef<Socket>,1907        deadline: Option<Deadline>,1908        foreign_name: &'static str,1909        action: DynMachineCallback<'tcx, Result<(), ()>>,1910    ) -> InterpResult<'tcx> {1911        let this = self.eval_context_mut();19121913        let state = socket.state.borrow();1914        match &*state {1915            SocketState::Connecting(_) => { /* fall-through to below */ }1916            SocketState::Connected(_) => {1917                drop(state);1918                return action.call(this, Ok(()));1919            }1920            _ => {1921                drop(state);1922                this.ensure_not_failed(&socket, foreign_name)?;1923                return action.call(this, Err(()));1924            }1925        };19261927        drop(state);19281929        // We're currently connecting. Since the underlying mio socket is non-blocking,1930        // the only way to determine whether we are done connecting is by polling.19311932        this.block_thread_for_io(1933            socket.clone(),1934            BlockingIoInterest::Write,1935            deadline,1936            callback!(1937                @capture<'tcx> {1938                    socket: FileDescriptionRef<Socket>,1939                    foreign_name: &'static str,1940                    action: DynMachineCallback<'tcx, Result<(), ()>>,1941                } |this, kind: UnblockKind| {1942                    // Remove the blocking I/O interest for unblocking this thread.1943                    this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());19441945                    if UnblockKind::TimedOut == kind {1946                        // This then means that the socket is not yet connected.1947                        return action.call(this, Err(()))1948                    }19491950                    // The thread woke up because it's ready, indicating a writeable or error event.19511952                    let state = socket.state.borrow();1953                    match &*state {1954                        SocketState::Connecting(_) => { /* fall-through to below */ },1955                        SocketState::Connected(_) => {1956                            drop(state);1957                            // This can happen because we blocked the thread:1958                            // maybe another thread "upgraded" the connection in the meantime.1959                            return action.call(this, Ok(()))1960                        },1961                        _ => {1962                            drop(state);1963                            // We ensured that we only block when we're currently connecting.1964                            // Since this thread just got rescheduled, it could be that another1965                            // thread realized that the connection failed and we're thus in1966                            // an "invalid state".1967                            this.ensure_not_failed(&socket, foreign_name)?;1968                            return action.call(this, Err(()))1969                        }1970                    };19711972                    drop(state);19731974                    // Set `socket.error` if `socket` currently has an error.1975                    this.update_last_error(&socket);19761977                    if socket.error.borrow().is_some() {1978                        // There was an error during connecting.1979                        // It's the program's responsibility to read SO_ERROR itself.1980                        return action.call(this, Err(()))1981                    }19821983                    // There was no error during connecting. Mio advises also reading the peer address1984                    // to ensure that socket is actually connected and that it wasn't a spurious wake-up:1985                    // <https://docs.rs/mio/latest/mio/net/struct.TcpStream.html#notes>1986                    //1987                    // Attempting to read the peer address would introduce an edge-case where the1988                    // write end of the socket could already be shutdown before it received a1989                    // writable event. When we then call [`TcpStream::peer_addr`] we receive an1990                    // error. This would need extra state for storing whether the write end was1991                    // manually closed using `shutdown`.1992                    // Also, tokio doesn't read the peer address and everything seems to be fine,1993                    // so we don't do that either:1994                    // <https://github.com/tokio-rs/mio/issues/1942#issuecomment-4162607761>1995                    // In other words, we are assuming that there will be no spurious1996                    // wakeups while establishing the connection.19971998                    // The connection is established.19992000                    // Temporarily use dummy state to take ownership of the stream.

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.