microsd 0.2.0

Light‐weight systemd auxiliars
Documentation
//! Convenience wrappers for [`std`] types.
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};

/// File descriptor wrapper for [`std`] types.
#[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),
}

/// Listen file descriptor iterator adapter for [`std`] types.
#[must_use]
#[derive(Debug)]
pub struct ListenFds(crate::ListenFds);

/// Retrieve listen file descriptors as [`std`] types.
///
/// # Example
///
/// ```
/// use microsd::std::Fd;
///
/// for fd in microsd::std::listen_fds() {
///     match fd? {
///         Fd::TcpListener(tcp_listener) => {
///             // …
///         },
///         _ => unreachable!("unknown listen fd type"),
///     }
/// }
/// # Ok::<(), std::io::Error>(())
/// ```
#[inline]
pub fn listen_fds() -> ListenFds {
	crate::listen_fds().into()
}

/// Connect notification socket.
///
/// This is a convenience wrapper around [`crate::notify_socket`]
/// returning a connected [`UnixDatagram`] socket.
///
/// # Example
///
/// ```,ignore
/// let notify = microsd::std::notify_socket()?.unwrap();
/// notify.send(b"READY=1")?;
/// # Ok::<(), std::io::Error>(())
/// ```
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!(),
			}
		})
	}
}