use super::ffi::{c_int, pid_t, size_t};
use super::{Error, Result};
use crate::ffi_result;
use ::ffi::daemon as ffi;
use cstr_argument::CStrArgument;
use libc::{c_char, c_uint};
use libc::{SOCK_DGRAM, SOCK_RAW, SOCK_STREAM};
use std::io::ErrorKind;
use std::net::TcpListener;
use std::os::unix::io::FromRawFd;
use std::os::unix::io::RawFd as Fd;
use std::ptr::null;
use std::{env, ptr};
pub enum SocketType {
Stream,
Datagram,
Raw,
}
pub enum Listening {
IsListening,
IsNotListening,
NoListeningCheck,
}
const LISTEN_FDS_START: Fd = 3;
pub const STATE_READY: &str = "READY";
pub const STATE_RELOADING: &str = "RELOADING";
pub const STATE_STOPPING: &str = "STOPPING";
pub const STATE_STATUS: &str = "STATUS";
pub const STATE_ERRNO: &str = "ERRNO";
pub const STATE_BUSERROR: &str = "BUSERROR";
pub const STATE_MAINPID: &str = "MAINPID";
pub const STATE_WATCHDOG: &str = "WATCHDOG";
pub const STATE_WATCHDOG_USEC: &str = "WATCHDOG_USEC";
pub const STATE_EXTEND_TIMEOUT_USEC: &str = "EXTEND_TIMEOUT_USEC";
pub const STATE_FDSTORE: &str = "FDSTORE";
pub const STATE_FDSTOREREMOVE: &str = "FDSTOREREMOVE";
pub const STATE_FDNAME: &str = "FDNAME";
#[derive(Debug)]
pub struct ListenFds {
num_fds: c_int,
}
impl ListenFds {
fn new(unset_environment: bool) -> Result<Self> {
let num_fds = ffi_result(unsafe { ffi::sd_listen_fds(0) })?;
if unset_environment {
env::remove_var("LISTEN_FDS");
env::remove_var("LISTEN_PID");
env::remove_var("LISTEN_FDNAMES");
}
Ok(Self { num_fds })
}
pub fn len(&self) -> c_int {
self.num_fds
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> ListenFdsRange {
ListenFdsRange {
current: 0,
num_fds: self.num_fds,
}
}
}
#[derive(Clone, Debug)]
pub struct ListenFdsRange {
current: c_int,
num_fds: c_int,
}
impl Iterator for ListenFdsRange {
type Item = Fd;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.num_fds {
let fd = LISTEN_FDS_START + self.current;
self.current += 1;
Some(fd)
} else {
None
}
}
}
pub fn listen_fds(unset_environment: bool) -> Result<ListenFds> {
ListenFds::new(unset_environment)
}
pub fn is_fifo<S: CStrArgument>(fd: Fd, path: Option<S>) -> Result<bool> {
let path = path.map(|x| x.into_cstr());
let result =
ffi_result(unsafe { ffi::sd_is_fifo(fd, path.map_or(null(), |x| x.as_ref().as_ptr())) })?;
Ok(result != 0)
}
pub fn is_special<S: CStrArgument>(fd: Fd, path: Option<S>) -> Result<bool> {
let path = path.map(|x| x.into_cstr());
let result = ffi_result(unsafe {
ffi::sd_is_special(fd, path.map_or(null(), |x| x.as_ref().as_ptr()))
})?;
Ok(result != 0)
}
#[inline]
fn get_c_socktype(socktype: Option<SocketType>) -> c_int {
match socktype {
Some(SocketType::Stream) => SOCK_STREAM,
Some(SocketType::Datagram) => SOCK_DGRAM,
Some(SocketType::Raw) => SOCK_RAW,
None => 0,
}
}
#[inline]
fn get_c_listening(listening: Listening) -> c_int {
match listening {
Listening::IsListening => 1,
Listening::IsNotListening => 0,
Listening::NoListeningCheck => -1,
}
}
pub fn is_socket(
fd: Fd,
family: Option<c_uint>,
socktype: Option<SocketType>,
listening: Listening,
) -> Result<bool> {
let c_family = family.unwrap_or(0) as c_int;
let c_socktype = get_c_socktype(socktype);
let c_listening = get_c_listening(listening);
let result = ffi_result(unsafe { ffi::sd_is_socket(fd, c_family, c_socktype, c_listening) })?;
Ok(result != 0)
}
pub fn is_socket_inet(
fd: Fd,
family: Option<c_uint>,
socktype: Option<SocketType>,
listening: Listening,
port: Option<u16>,
) -> Result<bool> {
let c_family = family.unwrap_or(0) as c_int;
let c_socktype = get_c_socktype(socktype);
let c_listening = get_c_listening(listening);
let c_port = port.unwrap_or(0);
let result = ffi_result(unsafe {
ffi::sd_is_socket_inet(fd, c_family, c_socktype, c_listening, c_port)
})?;
Ok(result != 0)
}
pub fn tcp_listener(fd: Fd) -> Result<TcpListener> {
if !is_socket_inet(
fd,
None,
Some(SocketType::Stream),
Listening::IsListening,
None,
)? {
Err(Error::new(
ErrorKind::InvalidInput,
"Socket type was not as expected",
))
} else {
Ok(unsafe { TcpListener::from_raw_fd(fd) })
}
}
pub fn is_socket_unix<S: CStrArgument>(
fd: Fd,
socktype: Option<SocketType>,
listening: Listening,
path: Option<S>,
) -> Result<bool> {
let path_cstr = path.map(|p| p.into_cstr());
let c_socktype = get_c_socktype(socktype);
let c_listening = get_c_listening(listening);
let c_path: *const c_char;
let c_length: size_t;
match path_cstr.as_ref() {
Some(p) => {
let path_ref = p.as_ref();
c_length = path_ref.to_bytes().len() as size_t;
c_path = path_ref.as_ptr() as *const c_char;
}
None => {
c_path = ptr::null();
c_length = 0;
}
}
let result = ffi_result(unsafe {
ffi::sd_is_socket_unix(fd, c_socktype, c_listening, c_path, c_length)
})?;
Ok(result != 0)
}
pub fn is_mq<S: CStrArgument>(fd: Fd, path: Option<S>) -> Result<bool> {
let path = path.map(|x| x.into_cstr());
let result =
ffi_result(unsafe { ffi::sd_is_mq(fd, path.map_or(null(), |x| x.as_ref().as_ptr())) })?;
Ok(result != 0)
}
fn state_to_c_string<'a, I, K, V>(state: I) -> ::std::ffi::CString
where
I: Iterator<Item = &'a (K, V)>,
K: AsRef<str> + 'a,
V: AsRef<str> + 'a,
{
let mut state_vec = Vec::new();
for (key, value) in state {
state_vec.push([key.as_ref(), value.as_ref()].join("="));
}
let state_str = state_vec.join("\n");
::std::ffi::CString::new(state_str.as_bytes()).unwrap()
}
pub fn notify<'a, I, K, V>(unset_environment: bool, state: I) -> Result<bool>
where
I: Iterator<Item = &'a (K, V)>,
K: AsRef<str> + 'a,
V: AsRef<str> + 'a,
{
let c_state = state_to_c_string(state);
let result =
ffi_result(unsafe { ffi::sd_notify(unset_environment as c_int, c_state.as_ptr()) })?;
Ok(result != 0)
}
pub fn pid_notify<'a, I, K, V>(pid: pid_t, unset_environment: bool, state: I) -> Result<bool>
where
I: Iterator<Item = &'a (K, V)>,
K: AsRef<str> + 'a,
V: AsRef<str> + 'a,
{
let c_state = state_to_c_string(state);
let result = ffi_result(unsafe {
ffi::sd_pid_notify(pid, unset_environment as c_int, c_state.as_ptr())
})?;
Ok(result != 0)
}
pub fn pid_notify_with_fds<'a, I, K, V>(
pid: pid_t,
unset_environment: bool,
state: I,
fds: &[Fd],
) -> Result<bool>
where
I: Iterator<Item = &'a (K, V)>,
K: AsRef<str> + 'a,
V: AsRef<str> + 'a,
{
let c_state = state_to_c_string(state);
let result = ffi_result(unsafe {
ffi::sd_pid_notify_with_fds(
pid,
unset_environment as c_int,
c_state.as_ptr(),
fds.as_ptr(),
fds.len() as c_uint,
)
})?;
Ok(result != 0)
}
pub fn booted() -> Result<bool> {
let result = ffi_result(unsafe { ffi::sd_booted() })?;
Ok(result != 0)
}
pub fn watchdog_enabled(unset_environment: bool) -> Result<u64> {
let mut timeout: u64 = 0;
ffi_result(unsafe { ffi::sd_watchdog_enabled(unset_environment as c_int, &mut timeout) })?;
Ok(timeout)
}