#![allow(
clippy::unnecessary_mut_passed, // CMSG_ macros only exist for *const
clippy::useless_conversion, // not useless on all platforms
clippy::match_overlapping_arm, // cumbersome to avoid when using inclusive ranges
clippy::borrow_deref_ref, // avoid infinite loop in Borrow impl
dead_code // TODO
)]
use std::ops::{Deref, DerefMut};
use std::borrow::{Borrow, BorrowMut};
use std::os::unix::io::RawFd;
use std::io::{self, ErrorKind, IoSlice, IoSliceMut};
use std::alloc::{self, Layout};
use std::convert::TryInto;
use std::{mem, ptr, slice};
use std::marker::PhantomData;
use libc::{c_int, c_uint, c_void};
use libc::{msghdr, iovec, cmsghdr, sockaddr, sockaddr_un};
use libc::{sendmsg, recvmsg, close};
use libc::{MSG_TRUNC, MSG_CTRUNC};
#[cfg(not(any(target_os="illumos", target_os="solaris")))]
use libc::{CMSG_SPACE, CMSG_LEN, CMSG_DATA, CMSG_FIRSTHDR, CMSG_NXTHDR};
use libc::{SOL_SOCKET, SCM_RIGHTS};
#[cfg(any(target_os="linux", target_os="android"))]
use libc::SCM_CREDENTIALS;
#[cfg(not(any(target_vendor="apple", target_os="illumos", target_os="solaris", target_os = "haiku")))]
use libc::MSG_CMSG_CLOEXEC;
use crate::helpers::*;
use crate::UnixSocketAddr;
use crate::credentials::{SendCredentials, ReceivedCredentials};
#[cfg(any(target_os="linux", target_os="android"))]
use crate::credentials::RawReceivedCredentials;
#[cfg(any(
all(target_os="linux", not(target_env="musl")),
target_os="android",
target_env="uclibc",
))]
type ControlLen = usize;
#[cfg(not(any(
all(target_os="linux", not(target_env="musl")),
target_os="android",
target_env="uclibc",
)))]
type ControlLen = libc::socklen_t;
pub fn send_ancillary(
socket: RawFd, to: Option<&UnixSocketAddr>, flags: c_int,
bytes: &[IoSlice], fds: &[RawFd], creds: Option<SendCredentials>
) -> Result<usize, io::Error> {
#[cfg(not(any(target_os="linux", target_os="android")))]
let _ = creds; unsafe {
let mut msg: msghdr = mem::zeroed();
msg.msg_name = ptr::null_mut();
msg.msg_namelen = 0;
msg.msg_iov = bytes.as_ptr() as *mut iovec;
msg.msg_iovlen = match bytes.len().try_into() {
Ok(len) => len,
Err(_) => {
return Err(io::Error::new(ErrorKind::InvalidInput, "too many byte slices"));
}
};
msg.msg_flags = 0;
msg.msg_control = ptr::null_mut();
msg.msg_controllen = 0;
if let Some(addr) = to {
let (addr, len) = addr.as_raw();
msg.msg_name = addr as *const sockaddr_un as *const c_void as *mut c_void;
msg.msg_namelen = len;
}
let mut needed_capacity = 0;
#[cfg(any(target_os="linux", target_os="android"))]
let creds = creds.map(|creds| {
let creds = creds.into_raw();
needed_capacity += CMSG_SPACE(mem::size_of_val(&creds) as u32);
creds
});
if fds.len() > 0 {
if fds.len() > 0xff_ff_ff {
return Err(io::Error::new(ErrorKind::InvalidInput, "too many file descriptors"));
}
#[cfg(not(any(target_os="illumos", target_os="solaris")))] {
needed_capacity += CMSG_SPACE(mem::size_of_val::<[RawFd]>(fds) as u32);
}
#[cfg(any(target_os="illumos", target_os="solaris"))] {
return Err(io::Error::new(
ErrorKind::Other,
"ancillary data support is not implemented yet for Illumos or Solaris"
))
}
}
#[repr(C)]
struct AncillaryFixedBuf([cmsghdr; 0], [u8; 256]);
let mut ancillary_buf = AncillaryFixedBuf([], [0; 256]);
msg.msg_controllen = needed_capacity as ControlLen;
if needed_capacity != 0 {
if needed_capacity as usize <= mem::size_of::<AncillaryFixedBuf>() {
msg.msg_control = &mut ancillary_buf.1 as *mut [u8; 256] as *mut c_void;
} else {
let layout = Layout::from_size_align(
needed_capacity as usize,
mem::align_of::<cmsghdr>()
).unwrap();
msg.msg_control = alloc::alloc(layout) as *mut c_void;
}
#[cfg(not(any(target_os="illumos", target_os="solaris")))] {
let header_ptr = CMSG_FIRSTHDR(&mut msg);
assert!(!header_ptr.is_null(), "CMSG_FIRSTHDR returned unexpected NULL pointer");
#[allow(unused_mut)]
let mut header = &mut*header_ptr;
#[cfg(any(target_os="linux", target_os="android"))] {
if let Some(creds) = creds {
header.cmsg_level = SOL_SOCKET;
header.cmsg_type = SCM_CREDENTIALS;
header.cmsg_len = CMSG_LEN(mem::size_of_val(&creds) as u32) as ControlLen;
*(CMSG_DATA(header) as *mut c_void as *mut _) = creds;
let header_ptr = CMSG_NXTHDR(&mut msg, header);
assert!(!header_ptr.is_null(), "CMSG_NXTHDR returned unexpected NULL pointer");
header = &mut*header_ptr;
}
}
if fds.len() > 0 {
header.cmsg_level = SOL_SOCKET;
header.cmsg_type = SCM_RIGHTS;
header.cmsg_len = CMSG_LEN(mem::size_of_val(fds) as u32) as ControlLen;
let mut dst = CMSG_DATA(header) as *mut c_void as *mut RawFd;
for &fd in fds {
ptr::write_unaligned(dst, fd);
dst = dst.add(1);
}
}
}
}
let result = cvt_r!(sendmsg(socket, &msg, flags | MSG_NOSIGNAL));
if needed_capacity as usize > mem::size_of::<AncillaryFixedBuf>() {
let layout = Layout::from_size_align(needed_capacity as usize, mem::align_of::<cmsghdr>()).unwrap();
alloc::dealloc(msg.msg_control as *mut u8, layout);
}
result.map(|sent| sent as usize )
}
}
#[repr(C)]
pub struct AncillaryBuf {
capacity: ControlLen,
ptr: *mut u8,
_align: [cmsghdr; 0],
on_stack: [u8; Self::MAX_STACK_CAPACITY],
}
impl Drop for AncillaryBuf {
fn drop(&mut self) {
unsafe {
if self.capacity as usize > Self::MAX_STACK_CAPACITY {
let layout = Layout::from_size_align(
self.capacity as usize,
mem::align_of::<cmsghdr>()
).unwrap();
alloc::dealloc(self.ptr as *mut u8, layout);
}
}
}
}
impl AncillaryBuf {
pub const MAX_STACK_CAPACITY: usize = 256;
pub const MAX_CAPACITY: usize = ControlLen::max_value() as usize;
pub fn with_capacity(bytes: usize) -> Self {
Self {
capacity: bytes as ControlLen,
ptr: match bytes {
0..=Self::MAX_STACK_CAPACITY => ptr::null_mut(),
0..=Self::MAX_CAPACITY => unsafe {
let layout = Layout::from_size_align(
bytes as usize,
mem::align_of::<cmsghdr>()
).unwrap();
alloc::alloc_zeroed(layout)
},
_ => panic!("capacity is too high"),
},
_align: [],
on_stack: [0; Self::MAX_STACK_CAPACITY],
}
}
pub fn with_fd_capacity(num_fds: usize) -> Self {
#[cfg(not(any(target_os="illumos", target_os="solaris")))]
unsafe {
let max_fds =
(c_uint::max_value() - CMSG_SPACE(0)) as usize
/ mem::size_of::<RawFd>();
if num_fds == 0 {
Self::with_capacity(0)
} else if num_fds <= max_fds {
let payload_bytes = num_fds * mem::size_of::<RawFd>();
Self::with_capacity(CMSG_SPACE(payload_bytes as c_uint) as usize)
} else {
panic!("too many file descriptors for ancillary buffer length")
}
}
#[cfg(any(target_os="illumos", target_os="solaris"))] {
Self::with_capacity(num_fds) }
}
}
impl Default for AncillaryBuf {
fn default() -> Self {
Self {
capacity: Self::MAX_STACK_CAPACITY as ControlLen,
ptr: ptr::null_mut(),
_align: [],
on_stack: [0; Self::MAX_STACK_CAPACITY],
}
}
}
impl Deref for AncillaryBuf {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
self.on_stack.get(..self.capacity as usize)
.unwrap_or_else(|| slice::from_raw_parts(self.ptr, self.capacity as usize) )
}
}
}
impl DerefMut for AncillaryBuf {
fn deref_mut(&mut self) -> &mut[u8] {
unsafe {
match self.on_stack.get_mut(..self.capacity as usize) {
Some(on_stack) => on_stack,
None => slice::from_raw_parts_mut(self.ptr, self.capacity as usize)
}
}
}
}
impl Borrow<[u8]> for AncillaryBuf {
fn borrow(&self) -> &[u8] {
&*self
}
}
impl BorrowMut<[u8]> for AncillaryBuf {
fn borrow_mut(&mut self) -> &mut[u8] {
&mut*self
}
}
impl AsRef<[u8]> for AncillaryBuf {
fn as_ref(&self) -> &[u8] {
&*self
}
}
impl AsMut<[u8]> for AncillaryBuf {
fn as_mut(&mut self) -> &mut[u8] {
&mut*self
}
}
pub struct FdSliceIterator<'a> {
pos: usize,
slice: &'a FdSlice<'a>,
}
impl<'a> Iterator for FdSliceIterator<'a> {
type Item = RawFd;
fn next(&mut self) -> Option<Self::Item> {
if self.slice.len > self.pos {
let ret = unsafe {
self.slice.unaligned_ptr.add(self.pos).read_unaligned()
};
self.pos += 1;
Some(ret)
} else {
None
}
}
}
pub struct FdSlice<'a> {
unaligned_ptr: *const RawFd,
len: usize,
_borrow: PhantomData<&'a RawFd>,
}
impl<'a> FdSlice<'a> {
unsafe fn new(unaligned_ptr: *const RawFd, len: usize) -> Self {
debug_assert!(!unaligned_ptr.is_null(), "No NULL pointer for FdSlice");
Self {
unaligned_ptr,
len,
_borrow: PhantomData,
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn iter(&self) -> FdSliceIterator {
(&self).into_iter()
}
}
impl<'a> IntoIterator for &'a FdSlice<'a> {
type Item = RawFd;
type IntoIter = FdSliceIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
FdSliceIterator {
pos: 0,
slice: self,
}
}
}
pub enum AncillaryItem<'a> {
Fds(FdSlice<'a>),
#[allow(unused)]
Credentials(ReceivedCredentials),
Unsupported
}
pub struct Ancillary<'a> {
msg: msghdr,
_ancillary_buf: PhantomData<&'a[u8]>,
#[cfg(not(any(target_os="illumos", target_os="solaris")))]
next_message: *mut cmsghdr,
}
impl<'a> Iterator for Ancillary<'a> {
type Item = AncillaryItem<'a>;
#[cfg(not(any(target_os="illumos", target_os="solaris")))]
fn next(&mut self) -> Option<AncillaryItem<'a>> {
unsafe {
if self.next_message.is_null() {
return None;
}
let msg_bytes = (*self.next_message).cmsg_len as usize;
let payload_bytes = msg_bytes - CMSG_LEN(0) as usize;
let item = match ((*self.next_message).cmsg_level, (*self.next_message).cmsg_type) {
(SOL_SOCKET, SCM_RIGHTS) => {
let num_fds = payload_bytes / mem::size_of::<RawFd>();
let first_fd = CMSG_DATA(self.next_message) as *const c_void;
let first_fd = first_fd.cast::<RawFd>();
#[cfg(any(target_vendor="apple", target_os="freebsd"))] {
let fds = FdSlice::new(first_fd, num_fds);
for fd in &fds {
let _ = set_cloexec(fd, true);
}
}
let fds = FdSlice::new(first_fd, num_fds);
AncillaryItem::Fds(fds)
}
#[cfg(any(target_os="linux", target_os="android"))]
(SOL_SOCKET, SCM_CREDENTIALS) => {
let creds_ptr = CMSG_DATA(self.next_message) as *const c_void;
debug_assert!(
creds_ptr as usize & (mem::align_of::<RawReceivedCredentials>()-1) == 0,
"CMSG_DATA() is aligned"
);
let creds_ptr = creds_ptr as *const RawReceivedCredentials;
AncillaryItem::Credentials(ReceivedCredentials::from_raw(*creds_ptr))
}
_ => AncillaryItem::Unsupported,
};
self.next_message = CMSG_NXTHDR(&mut self.msg, self.next_message);
Some(item)
}
}
#[cfg(any(target_os="illumos", target_os="solaris"))]
fn next(&mut self) -> Option<Self::Item> {
None
}
}
impl<'a> Drop for Ancillary<'a> {
fn drop(&mut self) {
for ancillary in self {
if let AncillaryItem::Fds(fds) = ancillary {
for fd in &fds {
unsafe { close(fd) };
}
}
}
}
}
impl<'a> Ancillary<'a> {
pub fn message_truncated(&self) -> bool {
self.msg.msg_flags & MSG_TRUNC != 0
}
#[allow(unused)] pub fn ancillary_truncated(&self) -> bool {
self.msg.msg_flags & MSG_CTRUNC != 0
}
}
pub fn recv_ancillary<'ancillary_buf>(
socket: RawFd, from: Option<&mut UnixSocketAddr>, mut flags: c_int,
bufs: &mut[IoSliceMut], ancillary_buf: &'ancillary_buf mut[u8],
) -> Result<(usize, Ancillary<'ancillary_buf>), io::Error> {
unsafe {
let mut msg: msghdr = mem::zeroed();
msg.msg_name = ptr::null_mut();
msg.msg_namelen = 0;
msg.msg_iov = bufs.as_mut_ptr() as *mut iovec;
msg.msg_iovlen = match bufs.len().try_into() {
Ok(len) => len,
Err(_) => {
return Err(io::Error::new(ErrorKind::InvalidInput, "too many content buffers"));
}
};
msg.msg_flags = 0;
msg.msg_control = ptr::null_mut();
msg.msg_controllen = 0;
if ancillary_buf.len() > 0 {
#[cfg(any(target_os="illumos", target_os="solaris"))] {
return Err(io::Error::new(
ErrorKind::Other,
"ancillary message support is not implemented yet on Illumos or Solaris, sorry"
))
}
if ancillary_buf.as_ptr() as usize % mem::align_of::<cmsghdr>() != 0 {
let msg = "ancillary buffer is not properly aligned";
return Err(io::Error::new(ErrorKind::InvalidInput, msg));
}
if ancillary_buf.len() > ControlLen::max_value() as usize {
let msg = "ancillary buffer is too big";
return Err(io::Error::new(ErrorKind::InvalidInput, msg));
}
msg.msg_control = ancillary_buf.as_mut_ptr() as *mut c_void;
msg.msg_controllen = ancillary_buf.len() as ControlLen;
}
flags |= MSG_NOSIGNAL;
#[cfg(not(any(target_vendor="apple", target_os="illumos", target_os="solaris", target_os = "haiku")))] {
flags |= MSG_CMSG_CLOEXEC;
}
let received = match from {
Some(addrbuf) => {
let (received, addr) = UnixSocketAddr::new_from_ffi(|addr, len| {
msg.msg_name = addr as *mut sockaddr as *mut c_void;
msg.msg_namelen = *len;
let received = cvt_r!(recvmsg(socket, &mut msg, flags))? as usize;
*len = msg.msg_namelen;
Ok(received)
})?;
*addrbuf = addr;
received
}
None => cvt_r!(recvmsg(socket, &mut msg, flags))? as usize
};
let ancillary_iterator = Ancillary {
msg,
_ancillary_buf: PhantomData,
#[cfg(not(any(target_os="illumos", target_os="solaris")))]
next_message: CMSG_FIRSTHDR(&msg),
};
Ok((received, ancillary_iterator))
}
}
pub fn recv_fds(
fd: RawFd, from: Option<&mut UnixSocketAddr>,
bufs: &mut[IoSliceMut], fd_buf: &mut[RawFd]
) -> Result<(usize, bool, usize), io::Error> {
let mut ancillary_buf = AncillaryBuf::with_fd_capacity(fd_buf.len());
let (num_bytes, mut ancillary) = recv_ancillary(fd, from, 0, bufs, &mut ancillary_buf)?;
let mut num_fds = 0;
for message in &mut ancillary {
if let AncillaryItem::Fds(fds) = message {
let can_keep = fds.len().min(fd_buf.len()-num_fds);
let mut fd_iter = (&fds).iter();
for i in 0..can_keep {
fd_buf[num_fds + i] = fd_iter.next().unwrap();
}
num_fds += can_keep;
for unwanted in fd_iter {
unsafe { close(unwanted) };
}
}
}
Ok((num_bytes, ancillary.message_truncated(), num_fds))
}