Skip to main content

folk_runtime_pipe/
socket.rs

1//! Safe wrapper around `socketpair(2)`.
2//!
3//! Creates a connected pair of Unix stream sockets. Both ends are marked
4//! `FD_CLOEXEC` after creation; the child's end must have this flag cleared
5//! inside `pre_exec` (see `spawn.rs`).
6
7use std::os::unix::io::{FromRawFd, OwnedFd};
8
9use anyhow::{Result, bail};
10
11/// Returns `(master_fd, child_fd)` as `OwnedFd` values.
12///
13/// The `master_fd` stays in the Rust process. The `child_fd` is dup2'd
14/// into the child's descriptor slot inside `pre_exec`.
15#[allow(unsafe_code)]
16pub fn create_socketpair() -> Result<(OwnedFd, OwnedFd)> {
17    let mut fds: [libc::c_int; 2] = [-1, -1];
18
19    // SAFETY: socketpair is a well-defined syscall; we check the return code.
20    let rc = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
21
22    if rc != 0 {
23        bail!("socketpair failed: {}", std::io::Error::last_os_error());
24    }
25
26    // Set FD_CLOEXEC on both ends (macOS doesn't support SOCK_CLOEXEC).
27    for &fd in &fds {
28        unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) };
29    }
30
31    // SAFETY: fds are valid file descriptors returned by socketpair.
32    let master = unsafe { OwnedFd::from_raw_fd(fds[0]) };
33    let child = unsafe { OwnedFd::from_raw_fd(fds[1]) };
34
35    Ok((master, child))
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use std::os::unix::io::AsRawFd;
42
43    #[test]
44    fn creates_connected_pair() {
45        let (master, child) = create_socketpair().unwrap();
46        assert!(master.as_raw_fd() >= 0);
47        assert!(child.as_raw_fd() >= 0);
48        assert_ne!(master.as_raw_fd(), child.as_raw_fd());
49    }
50}