//! macOS specifics of the Unix backend. use std::io; use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}; use std::path::Path; fn close_on_exec(pair: (OwnedFd, OwnedFd)) -> io::Result<(OwnedFd, OwnedFd)> { // Not atomic, but this backend's own children close foreign descriptors. for fd in [&pair.0, &pair.1] { if unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) } < 0 { return Err(io::Error::last_os_error()); } } Ok(pair) } pub(super) fn pipe() -> io::Result<(OwnedFd, OwnedFd)> { let mut fds = [0; 2]; if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 { return Err(io::Error::last_os_error()); } close_on_exec(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) }) } /// Returns the master and slave of a new terminal. pub(super) fn pty() -> io::Result<(OwnedFd, OwnedFd)> { let mut master = -1; let mut slave = -1; if unsafe { libc::openpty( &mut master, &mut slave, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), ) } < 0 { return Err(io::Error::last_os_error()); } close_on_exec(unsafe { (OwnedFd::from_raw_fd(master), OwnedFd::from_raw_fd(slave)) }) } /// The authorized executable. macOS has no `fexecve`, so it executes the /// authorized path. pub struct Executable; impl Executable { pub fn open(target: &Path) -> io::Result<(Self, std::fs::Metadata)> { Ok((Self, std::fs::metadata(target)?)) } pub(super) fn reserve(&self, _minimum: RawFd) -> io::Result { Ok(Self) } /// Runs after fork; returns only on failure. argv[0] is the authorized /// path. pub(super) fn execute( &self, argv: *const *const libc::c_char, env: *const *const libc::c_char, ) -> io::Error { unsafe { libc::execve(*argv, argv, env); } io::Error::last_os_error() } } /// macOS has no parent-death signal: an abruptly killed owner orphans its /// children, and only orderly exits clean up. pub(super) struct Owner; impl Owner { pub fn new() -> Self { Self } pub fn bind(&self) -> io::Result<()> { Ok(()) } } pub(super) struct Descriptors(Vec); impl Descriptors { /// Sizes the descriptor listing before fork, so the child need not /// allocate. pub fn new() -> io::Result { let size = unsafe { libc::proc_pidinfo( libc::getpid(), libc::PROC_PIDLISTFDS, 0, std::ptr::null_mut(), 0, ) }; if size <= 0 { return Err(io::Error::last_os_error()); } // Headroom for descriptors other threads open before fork. let count = size as usize / std::mem::size_of::() * 2 + 64; Ok(Self(Vec::with_capacity(count))) } /// Marks every descriptor from 3 up CLOEXEC; spawn re-arms the declared /// ones. macOS has neither close_range nor procfs, but lists a process's /// descriptors directly, including any above a lowered RLIMIT_NOFILE. pub fn restrict(&mut self) -> io::Result<()> { let capacity = (self.0.capacity() * std::mem::size_of::()) as libc::c_int; let size = unsafe { libc::proc_pidinfo( libc::getpid(), libc::PROC_PIDLISTFDS, 0, self.0.as_mut_ptr().cast(), capacity, ) }; if size <= 0 { return Err(io::Error::last_os_error()); } // A full buffer may have been truncated; fail closed. if size >= capacity { return Err(io::Error::from_raw_os_error(libc::EMFILE)); } unsafe { self.0 .set_len(size as usize / std::mem::size_of::()); } for entry in &self.0 { let fd = entry.proc_fd; if fd < 3 { continue; } let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; if flags < 0 || unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) } < 0 { return Err(io::Error::last_os_error()); } } Ok(()) } }