use crate::errors::{Context, SdError};
use libc::pid_t;
use nix::sys::socket;
use nix::unistd;
use std::io::{self, IoSlice};
use std::os::unix::io::RawFd;
use std::os::unix::net::UnixDatagram;
use std::os::unix::prelude::AsRawFd;
use std::{env, fmt, fs, time};
pub fn booted() -> bool {
fs::symlink_metadata("/run/systemd/system")
.map(|p| p.is_dir())
.unwrap_or(false)
}
pub fn watchdog_enabled(unset_env: bool) -> Option<time::Duration> {
let env_usec = env::var("WATCHDOG_USEC").ok();
let env_pid = env::var("WATCHDOG_PID").ok();
if unset_env {
env::remove_var("WATCHDOG_USEC");
env::remove_var("WATCHDOG_PID");
};
let timeout = {
if let Some(usec) = env_usec.and_then(|usec_str| usec_str.parse::<u64>().ok()) {
time::Duration::from_millis(usec / 1_000)
} else {
return None;
}
};
let pid = {
if let Some(pid_str) = env_pid {
if let Ok(p) = pid_str.parse::<pid_t>() {
unistd::Pid::from_raw(p)
} else {
return None;
}
} else {
return Some(timeout);
}
};
if unistd::getpid() == pid {
Some(timeout)
} else {
None
}
}
pub fn notify(unset_env: bool, state: &[NotifyState]) -> Result<bool, SdError> {
notify_with_fds(unset_env, state, &[])
}
pub fn notify_with_fds(
unset_env: bool,
state: &[NotifyState],
fds: &[RawFd],
) -> Result<bool, SdError> {
let env_sock = match env::var("NOTIFY_SOCKET").ok() {
None => return Ok(false),
Some(v) => v,
};
if unset_env {
env::remove_var("NOTIFY_SOCKET");
};
sanity_check_state_entries(state)?;
let socket_addr = match env_sock.strip_prefix('@') {
Some(stripped_addr) => socket::UnixAddr::new_abstract(stripped_addr.as_bytes())
.with_context(|| format!("invalid Unix socket abstract address {}", env_sock))?,
None => socket::UnixAddr::new(env_sock.as_str())
.with_context(|| format!("invalid Unix socket path address {}", env_sock))?,
};
let socket = UnixDatagram::unbound().context("failed to open Unix datagram socket")?;
let msg = state
.iter()
.fold(String::new(), |res, s| res + &format!("{}\n", s))
.into_bytes();
let msg_len = msg.len();
let msg_iov = IoSlice::new(&msg);
let ancillary = if !fds.is_empty() {
vec![socket::ControlMessage::ScmRights(fds)]
} else {
vec![]
};
let sent_len = socket::sendmsg(
socket.as_raw_fd(),
&[msg_iov],
&ancillary,
socket::MsgFlags::empty(),
Some(&socket_addr),
)
.map_err(|e| io::Error::from_raw_os_error(e as i32))
.context("failed to send notify datagram")?;
if sent_len != msg_len {
return Err(format!(
"incomplete notify sendmsg, sent {} out of {}",
sent_len, msg_len
)
.into());
}
Ok(true)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum NotifyState {
Buserror(String),
Errno(u8),
Fdname(String),
Fdstore,
FdstoreRemove,
FdpollDisable,
Mainpid(unistd::Pid),
Other(String),
Ready,
Reloading,
Status(String),
Stopping,
Watchdog,
WatchdogUsec(u64),
}
impl fmt::Display for NotifyState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
NotifyState::Buserror(ref s) => write!(f, "BUSERROR={}", s),
NotifyState::Errno(e) => write!(f, "ERRNO={}", e),
NotifyState::Fdname(ref s) => write!(f, "FDNAME={}", s),
NotifyState::Fdstore => write!(f, "FDSTORE=1"),
NotifyState::FdstoreRemove => write!(f, "FDSTOREREMOVE=1"),
NotifyState::FdpollDisable => write!(f, "FDPOLL=0"),
NotifyState::Mainpid(ref p) => write!(f, "MAINPID={}", p),
NotifyState::Other(ref s) => write!(f, "{}", s),
NotifyState::Ready => write!(f, "READY=1"),
NotifyState::Reloading => write!(f, "RELOADING=1"),
NotifyState::Status(ref s) => write!(f, "STATUS={}", s),
NotifyState::Stopping => write!(f, "STOPPING=1"),
NotifyState::Watchdog => write!(f, "WATCHDOG=1"),
NotifyState::WatchdogUsec(u) => write!(f, "WATCHDOG_USEC={}", u),
}
}
}
fn sanity_check_state_entries(state: &[NotifyState]) -> Result<(), SdError> {
for (index, entry) in state.iter().enumerate() {
match entry {
NotifyState::Fdname(ref name) => validate_fdname(name),
_ => Ok(()),
}
.with_context(|| format!("invalid notify state entry #{}", index))?;
}
Ok(())
}
fn validate_fdname(fdname: &str) -> Result<(), SdError> {
if fdname.len() > 255 {
return Err(format!("fdname '{}' longer than 255 characters", fdname).into());
}
for c in fdname.chars() {
if !c.is_ascii() || c == ':' || c.is_ascii_control() {
return Err(format!("invalid character '{}' in fdname '{}'", c, fdname).into());
}
}
Ok(())
}