#![cfg(all(target_os = "linux", feature = "youki-runtime"))]
use std::io::{IoSlice, IoSliceMut};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::net::UnixStream;
use nix::sys::socket::{recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags, UnixAddr};
#[allow(unsafe_code)]
pub fn recv_fd(stream: &UnixStream) -> std::io::Result<OwnedFd> {
let mut scratch = [0u8; 16];
let mut iov = [IoSliceMut::new(&mut scratch)];
let mut cmsg_buf = nix::cmsg_space!([RawFd; 1]);
let msg = recvmsg::<UnixAddr>(
stream.as_raw_fd(),
&mut iov,
Some(&mut cmsg_buf),
MsgFlags::empty(),
)
.map_err(std::io::Error::from)?;
let cmsgs = msg
.cmsgs()
.map_err(|e| std::io::Error::other(format!("recvmsg cmsgs decode failed: {e}")))?;
for cmsg in cmsgs {
if let ControlMessageOwned::ScmRights(fds) = cmsg {
if let Some(&raw) = fds.first() {
let owned = unsafe { OwnedFd::from_raw_fd(raw) };
return Ok(owned);
}
}
}
Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"console socket peer sent no SCM_RIGHTS file descriptor",
))
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn send_fd(stream: &UnixStream, fd: RawFd) -> std::io::Result<()> {
let payload = [0u8; 1];
let iov = [IoSlice::new(&payload)];
let fds = [fd];
let cmsg = [ControlMessage::ScmRights(&fds)];
sendmsg::<UnixAddr>(stream.as_raw_fd(), &iov, &cmsg, MsgFlags::empty(), None)
.map_err(std::io::Error::from)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::os::fd::AsRawFd;
#[test]
fn send_recv_real_pty_master_is_usable() {
let pty = nix::pty::openpty(None, None).expect("openpty");
let (a, b) = UnixStream::pair().expect("UnixStream::pair");
send_fd(&a, pty.master.as_raw_fd()).expect("send_fd");
let received = recv_fd(&b).expect("recv_fd");
drop(pty.master);
let mut slave_file = std::fs::File::from(pty.slave);
slave_file.write_all(b"ping").expect("write to slave");
slave_file.flush().expect("flush slave");
let mut master_file = std::fs::File::from(received);
let mut buf = [0u8; 4];
master_file.read_exact(&mut buf).expect("read from master");
assert_eq!(&buf, b"ping");
}
#[test]
fn send_recv_tempfile_fd_same_inode() {
use nix::sys::stat::fstat;
let tmp = tempfile::NamedTempFile::new().expect("tempfile");
let file = tmp.reopen().expect("reopen");
let orig = fstat(&file).expect("fstat orig");
let (a, b) = UnixStream::pair().expect("UnixStream::pair");
send_fd(&a, file.as_raw_fd()).expect("send_fd");
let received = recv_fd(&b).expect("recv_fd");
let recv_stat = fstat(&received).expect("fstat recv");
assert_eq!(orig.st_dev, recv_stat.st_dev, "device must match");
assert_eq!(orig.st_ino, recv_stat.st_ino, "inode must match");
}
}