use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::os::unix::fs::FileTypeExt;
use nix::fcntl::{FcntlArg, FdFlag, OFlag, fcntl};
use nix::sys::socket::{GetSockOpt, sockopt};
use nix::sys::stat::{SFlag, fstat};
use nix::{getsockopt_impl, sockopt_impl};
use crate::Error;
#[doc(no_inline)]
pub use nix::sys::socket::{AddressFamily, SockProtocol, SockType};
#[must_use]
#[derive(Debug)]
pub struct Fd(OwnedFd, SFlag);
impl Fd {
#[inline]
pub fn socket_domain(&self) -> Result<AddressFamily, Error> {
sockopt_impl!(SockDomain, GetOnly, libc::SOL_SOCKET, libc::SO_DOMAIN, AddressFamily);
SockDomain.get(self)
}
#[inline]
pub fn socket_type(&self) -> Result<SockType, Error> {
sockopt::SockType.get(self)
}
#[inline]
pub fn socket_protocol(&self) -> Result<Option<SockProtocol>, Error> {
sockopt_impl!(
SockProtocol,
GetOnly,
libc::SOL_SOCKET,
libc::SO_PROTOCOL,
nix::sys::socket::SockProtocol
);
SockProtocol.get(self).map(|prot| {
if prot as i32 == 0 {
None
} else {
Some(prot)
}
})
}
#[inline]
pub fn is_listening(&self) -> Result<bool, Error> {
sockopt::AcceptConn.get(self)
}
#[inline]
pub fn is_writeable(&self) -> Result<bool, Error> {
Ok(OFlag::from_bits_retain(fcntl(self, FcntlArg::F_GETFL)?).intersects(OFlag::O_WRONLY | OFlag::O_RDWR))
}
pub unsafe fn try_from(raw: impl IntoRawFd) -> Result<Self, Error> {
let raw = raw.into_raw_fd();
let fd = unsafe { BorrowedFd::borrow_raw(raw) };
let flags = FdFlag::from_bits_retain(fcntl(fd, FcntlArg::F_GETFD)?);
if !flags.contains(FdFlag::FD_CLOEXEC) {
fcntl(fd, FcntlArg::F_SETFD(flags | FdFlag::FD_CLOEXEC))?;
}
Ok(Self(
unsafe { OwnedFd::from_raw_fd(raw) },
SFlag::from_bits_retain(fstat(fd)?.st_mode),
))
}
#[inline]
pub fn into<T: FromRawFd>(self) -> T {
unsafe { T::from_raw_fd(self.into_raw_fd()) }
}
}
impl FileTypeExt for Fd {
#[inline]
fn is_block_device(&self) -> bool {
self.1.contains(SFlag::S_IFBLK)
}
#[inline]
fn is_char_device(&self) -> bool {
self.1.contains(SFlag::S_IFCHR)
}
#[inline]
fn is_fifo(&self) -> bool {
self.1.contains(SFlag::S_IFIFO)
}
#[inline]
fn is_socket(&self) -> bool {
self.1.contains(SFlag::S_IFSOCK)
}
}
impl AsFd for Fd {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl AsRawFd for Fd {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for Fd {
#[inline]
fn into_raw_fd(self) -> RawFd {
self.0.into_raw_fd()
}
}
impl From<Fd> for OwnedFd {
#[inline]
fn from(fd: Fd) -> Self {
fd.0
}
}
#[cfg(test)]
mod tests {
use std::fs::File;
use std::os::unix::fs::FileTypeExt;
use nix::sys::socket::{SockFlag, socket};
use crate::tests::no_env;
use super::{AddressFamily, Fd, SockProtocol, SockType};
#[test]
fn char_device() {
no_env(|| {
let fd = unsafe { Fd::try_from(File::open("/dev/null").unwrap()) }.unwrap();
assert!(fd.is_char_device());
});
}
#[test]
fn fifo() {
no_env(|| {
let pipe = std::io::pipe().unwrap();
let rd = unsafe { Fd::try_from(pipe.0) }.unwrap();
let wr = unsafe { Fd::try_from(pipe.1) }.unwrap();
assert!(rd.is_fifo());
assert!(wr.is_fifo());
assert!(!rd.is_writeable().unwrap());
assert!(wr.is_writeable().unwrap());
});
}
#[test]
fn socket_unix() {
for stype in [SockType::Stream, SockType::Datagram, SockType::SeqPacket] {
no_env(|| {
let fd = unsafe { Fd::try_from(socket(AddressFamily::Unix, stype, SockFlag::empty(), None).unwrap()) }.unwrap();
assert!(fd.is_socket());
assert_eq!(fd.socket_domain().unwrap(), AddressFamily::Unix);
assert_eq!(fd.socket_type().unwrap(), stype);
assert!(fd.socket_protocol().unwrap().is_none());
});
}
}
#[test]
fn socket_inet() {
for family in [AddressFamily::Inet, AddressFamily::Inet6] {
for (stype, proto) in [(SockType::Stream, SockProtocol::Tcp), (SockType::Datagram, SockProtocol::Udp)] {
no_env(|| {
let fd = unsafe { Fd::try_from(socket(family, stype, SockFlag::empty(), proto).unwrap()) }.unwrap();
assert!(fd.is_socket());
assert_eq!(fd.socket_domain().unwrap(), family);
assert_eq!(fd.socket_type().unwrap(), stype);
assert_eq!(fd.socket_protocol().unwrap().unwrap(), proto);
});
}
}
}
}