Skip to main content

unix_ancillary/
ext.rs

1use std::io;
2use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd};
3use std::os::unix::net::{UnixDatagram, UnixStream};
4
5use crate::ancillary::{AncillaryData, SocketAncillary};
6use crate::cmsg;
7use crate::platform;
8
9const DEFAULT_STREAM_BUF: usize = 4096;
10const DEFAULT_DATAGRAM_BUF: usize = 65536;
11
12/// Result of a successful `recv_fds` call.
13///
14/// Truncation is a hard error in the high-level API: if you observe a
15/// successful `Ok(ReceivedFds)`, every fd the kernel deposited has been
16/// accounted for. Surplus fds beyond the caller's requested `N` are closed
17/// automatically before this struct is returned.
18#[derive(Debug)]
19#[non_exhaustive]
20pub struct ReceivedFds {
21    /// Application data bytes received.
22    pub data: Vec<u8>,
23    /// File descriptors received, capped at `N`. Each `OwnedFd` is closed on
24    /// drop.
25    pub fds: Vec<OwnedFd>,
26}
27
28fn send_fds_impl(fd: BorrowedFd<'_>, data: &[u8], fds: &[BorrowedFd<'_>]) -> io::Result<usize> {
29    let mut buf = vec![0u8; SocketAncillary::buffer_size_for_rights(fds.len())];
30    let mut ancillary = SocketAncillary::new(&mut buf);
31    ancillary
32        .add_fds(fds)
33        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
34
35    let iov = [io::IoSlice::new(data)];
36    cmsg::sendmsg_vectored(fd, &iov, ancillary.buffer, ancillary.length)
37}
38
39/// Receive into `data_buf`, capping fds returned to the caller at `N`.
40///
41/// The internal cmsg buffer is sized to a platform-specific upper bound that
42/// the kernel cannot exceed for a single `SCM_RIGHTS` message:
43///
44/// - Linux / *BSD: a fixed `SCM_MAX_FD = 253` per-message cap.
45/// - macOS: the receiver's current `RLIMIT_NOFILE`, since the kernel must
46///   allocate an fd table entry per delivered fd and cannot deliver more
47///   than the receiver can hold.
48///
49/// Truncation is therefore kernel-impossible. Surplus fds beyond `N` are
50/// wrapped in `OwnedFd` and dropped before returning, closing them. If the
51/// kernel still reports `MSG_CTRUNC` (defensive — should be unreachable),
52/// every fd we extracted is closed and an error is returned.
53fn recv_fds_into_impl<const N: usize>(
54    fd: BorrowedFd<'_>,
55    data_buf: &mut [u8],
56) -> io::Result<(usize, Vec<OwnedFd>)> {
57    let cap = N.max(platform::max_recv_fds());
58    let mut anc_buf = vec![0u8; SocketAncillary::buffer_size_for_rights(cap)];
59
60    let mut iov = [io::IoSliceMut::new(data_buf)];
61    let result = cmsg::recvmsg_vectored(fd, &mut iov, &mut anc_buf)?;
62
63    let ancillary = SocketAncillary {
64        buffer: &mut anc_buf,
65        length: result.ancillary_len,
66        truncated: result.truncated,
67    };
68
69    // Wrap every fd the kernel handed us. Surplus past `N` are dropped at
70    // function exit, closing them and preventing leaks.
71    let mut all_fds: Vec<OwnedFd> = Vec::new();
72    for msg in ancillary.messages() {
73        match msg {
74            AncillaryData::ScmRights(rights) => all_fds.extend(rights),
75        }
76    }
77
78    if result.truncated {
79        // Defensive path: should be unreachable because `cap` exceeds
80        // every kernel's per-message fd limit. If we land here, fds may
81        // have been deposited beyond our buffer (macOS) and we cannot
82        // reach them. Drop everything we DID receive and surface the
83        // error so the caller knows the connection state is suspect.
84        drop(all_fds);
85        return Err(io::Error::other(
86            "ancillary truncated despite oversized buffer; possible fd leak — abort the connection",
87        ));
88    }
89
90    // Keep first N; remaining `OwnedFd`s drop here, closing surplus fds.
91    let kept: Vec<OwnedFd> = all_fds.into_iter().take(N).collect();
92
93    Ok((result.bytes_read, kept))
94}
95
96/// Extension trait for `UnixStream` adding fd-passing convenience methods.
97pub trait UnixStreamExt {
98    /// Send `data` plus borrowed file descriptors over the stream.
99    ///
100    /// Caller retains ownership of the fds.
101    fn send_fds(&self, data: &[u8], fds: &[impl AsFd]) -> io::Result<usize>;
102
103    /// Receive data and up to `N` file descriptors.
104    ///
105    /// Allocates an internal 4 KiB data buffer plus an oversized cmsg buffer
106    /// that prevents kernel-level fd leaks. Surplus fds beyond `N` are
107    /// closed automatically.
108    fn recv_fds<const N: usize>(&self) -> io::Result<ReceivedFds>;
109
110    /// Like [`recv_fds`](Self::recv_fds) but writes data into a
111    /// caller-supplied buffer. Returns `(bytes_read, fds)`.
112    fn recv_fds_into<const N: usize>(
113        &self,
114        data_buf: &mut [u8],
115    ) -> io::Result<(usize, Vec<OwnedFd>)>;
116}
117
118impl UnixStreamExt for UnixStream {
119    fn send_fds(&self, data: &[u8], fds: &[impl AsFd]) -> io::Result<usize> {
120        let borrowed: Vec<BorrowedFd<'_>> = fds.iter().map(|f| f.as_fd()).collect();
121        send_fds_impl(self.as_fd(), data, &borrowed)
122    }
123
124    fn recv_fds<const N: usize>(&self) -> io::Result<ReceivedFds> {
125        let mut data_buf = vec![0u8; DEFAULT_STREAM_BUF];
126        let (n, fds) = recv_fds_into_impl::<N>(self.as_fd(), &mut data_buf)?;
127        data_buf.truncate(n);
128        Ok(ReceivedFds {
129            data: data_buf,
130            fds,
131        })
132    }
133
134    fn recv_fds_into<const N: usize>(
135        &self,
136        data_buf: &mut [u8],
137    ) -> io::Result<(usize, Vec<OwnedFd>)> {
138        recv_fds_into_impl::<N>(self.as_fd(), data_buf)
139    }
140}
141
142/// Extension trait for `UnixDatagram` adding fd-passing convenience methods.
143pub trait UnixDatagramExt {
144    /// Send `data` plus borrowed fds. The socket must be connected.
145    fn send_fds(&self, data: &[u8], fds: &[impl AsFd]) -> io::Result<usize>;
146
147    /// Receive data and up to `N` fds.
148    ///
149    /// Allocates an internal 64 KiB data buffer plus an oversized cmsg
150    /// buffer that prevents kernel-level fd leaks. Surplus fds beyond `N`
151    /// are closed automatically.
152    fn recv_fds<const N: usize>(&self) -> io::Result<ReceivedFds>;
153
154    /// Like [`recv_fds`](Self::recv_fds) but writes data into a
155    /// caller-supplied buffer. Returns `(bytes_read, fds)`.
156    fn recv_fds_into<const N: usize>(
157        &self,
158        data_buf: &mut [u8],
159    ) -> io::Result<(usize, Vec<OwnedFd>)>;
160}
161
162impl UnixDatagramExt for UnixDatagram {
163    fn send_fds(&self, data: &[u8], fds: &[impl AsFd]) -> io::Result<usize> {
164        let borrowed: Vec<BorrowedFd<'_>> = fds.iter().map(|f| f.as_fd()).collect();
165        send_fds_impl(self.as_fd(), data, &borrowed)
166    }
167
168    fn recv_fds<const N: usize>(&self) -> io::Result<ReceivedFds> {
169        let mut data_buf = vec![0u8; DEFAULT_DATAGRAM_BUF];
170        let (n, fds) = recv_fds_into_impl::<N>(self.as_fd(), &mut data_buf)?;
171        data_buf.truncate(n);
172        Ok(ReceivedFds {
173            data: data_buf,
174            fds,
175        })
176    }
177
178    fn recv_fds_into<const N: usize>(
179        &self,
180        data_buf: &mut [u8],
181    ) -> io::Result<(usize, Vec<OwnedFd>)> {
182        recv_fds_into_impl::<N>(self.as_fd(), data_buf)
183    }
184}