use std::fs::File;
use std::io::{PipeReader, PipeWriter};
use std::iter::FusedIterator;
use std::net::{TcpListener, TcpStream, UdpSocket};
use std::os::unix::fs::FileTypeExt;
use std::os::unix::net::{UnixDatagram, UnixListener, UnixStream};
use crate::{AddressFamily, Error, SockType};
#[must_use]
#[derive(Debug)]
pub enum Fd {
UnixStream(UnixStream),
UnixListener(UnixListener),
UnixDatagram(UnixDatagram),
TcpStream(TcpStream),
TcpListener(TcpListener),
UdpSocket(UdpSocket),
PipeReader(PipeReader),
PipeWriter(PipeWriter),
File(File),
Other(crate::Fd),
}
#[must_use]
#[derive(Debug)]
pub struct ListenFds(crate::ListenFds);
#[inline]
pub fn listen_fds() -> ListenFds {
crate::listen_fds().into()
}
pub fn notify_socket() -> Result<Option<UnixDatagram>, std::io::Error> {
crate::notify_socket()
.map(|addr| {
let notify = UnixDatagram::unbound()?;
notify.connect_addr(&addr)?;
Ok(notify)
})
.transpose()
}
impl TryFrom<crate::Fd> for Fd {
type Error = Error;
fn try_from(fd: crate::Fd) -> Result<Self, Self::Error> {
Ok(if fd.is_socket() {
let family = fd.socket_domain()?;
match fd.socket_type()? {
SockType::Stream | SockType::SeqPacket => match (family, fd.is_listening()?) {
(AddressFamily::Unix, false) => Fd::UnixStream(fd.into()),
(AddressFamily::Unix, true) => Fd::UnixListener(fd.into()),
(_, false) => Fd::TcpStream(fd.into()),
(_, true) => Fd::TcpListener(fd.into()),
},
SockType::Datagram => match family {
AddressFamily::Unix => Fd::UnixDatagram(fd.into()),
_ => Fd::UdpSocket(fd.into()),
},
_ => Fd::Other(fd),
}
} else if fd.is_fifo() {
if fd.is_writeable()? {
Fd::PipeWriter(fd.into())
} else {
Fd::PipeReader(fd.into())
}
} else if fd.is_char_device() {
Fd::File(fd.into())
} else {
Fd::Other(fd)
})
}
}
impl From<crate::ListenFds> for ListenFds {
#[inline]
fn from(fds: crate::ListenFds) -> Self {
Self(fds)
}
}
impl Iterator for ListenFds {
type Item = Result<Fd, Error>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|fd| fd?.try_into())
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl DoubleEndedIterator for ListenFds {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back().map(|fd| fd?.try_into())
}
}
impl ExactSizeIterator for ListenFds {}
impl FusedIterator for ListenFds {}
#[cfg(test)]
mod tests {
use std::fs::File;
use std::os::fd::{AsRawFd, IntoRawFd};
use crate::tests::with_env;
use super::Fd;
#[test]
fn listen_fds() {
with_env([
("LISTEN_PID", format!("{}", std::process::id()).as_str()),
("LISTEN_FDS", "1"),
], || {
assert_eq!(File::open("/dev/null").unwrap().into_raw_fd(), crate::ListenFds::START);
match super::listen_fds().next().unwrap().unwrap() {
Fd::File(file) => assert_eq!(file.as_raw_fd(), crate::ListenFds::START),
_ => unreachable!(),
}
})
}
}