libsystemd/
daemon.rs

1use crate::errors::{Context, SdError};
2use libc::pid_t;
3use nix::sys::socket;
4use nix::unistd;
5use std::io::{self, IoSlice};
6use std::os::unix::io::RawFd;
7use std::os::unix::net::UnixDatagram;
8use std::os::unix::prelude::AsRawFd;
9use std::{env, fmt, fs, time};
10
11/// Check for systemd presence at runtime.
12///
13/// Return true if the system was booted with systemd.
14/// This check is based on the presence of the systemd
15/// runtime directory.
16pub fn booted() -> bool {
17    fs::symlink_metadata("/run/systemd/system")
18        .map(|p| p.is_dir())
19        .unwrap_or(false)
20}
21
22/// Check for watchdog support at runtime.
23///
24/// Return a timeout before which the watchdog expects a
25/// response from the process, or `None` if watchdog support is
26/// not enabled. If `unset_env` is true, environment will be cleared.
27pub fn watchdog_enabled(unset_env: bool) -> Option<time::Duration> {
28    let env_usec = env::var("WATCHDOG_USEC").ok();
29    let env_pid = env::var("WATCHDOG_PID").ok();
30
31    if unset_env {
32        env::remove_var("WATCHDOG_USEC");
33        env::remove_var("WATCHDOG_PID");
34    };
35
36    let timeout = {
37        if let Some(usec) = env_usec.and_then(|usec_str| usec_str.parse::<u64>().ok()) {
38            time::Duration::from_millis(usec / 1_000)
39        } else {
40            return None;
41        }
42    };
43
44    let pid = {
45        if let Some(pid_str) = env_pid {
46            if let Ok(p) = pid_str.parse::<pid_t>() {
47                unistd::Pid::from_raw(p)
48            } else {
49                return None;
50            }
51        } else {
52            return Some(timeout);
53        }
54    };
55
56    if unistd::getpid() == pid {
57        Some(timeout)
58    } else {
59        None
60    }
61}
62
63/// Notify service manager about status changes.
64///
65/// Send a notification to the manager about service status changes.
66/// The returned boolean show whether notifications are supported for
67/// this service. If `unset_env` is true, environment will be cleared
68/// and no further notifications are possible.
69/// Also see [`notify_with_fds`] which can send file descriptors to the
70/// service manager.
71pub fn notify(unset_env: bool, state: &[NotifyState]) -> Result<bool, SdError> {
72    notify_with_fds(unset_env, state, &[])
73}
74
75/// Notify service manager about status changes and send file descriptors.
76///
77/// Use this together with [`NotifyState::Fdstore`]. Otherwise works like [`notify`].
78pub fn notify_with_fds(
79    unset_env: bool,
80    state: &[NotifyState],
81    fds: &[RawFd],
82) -> Result<bool, SdError> {
83    let env_sock = match env::var("NOTIFY_SOCKET").ok() {
84        None => return Ok(false),
85        Some(v) => v,
86    };
87
88    if unset_env {
89        env::remove_var("NOTIFY_SOCKET");
90    };
91
92    sanity_check_state_entries(state)?;
93
94    // If the first character of `$NOTIFY_SOCKET` is '@', the string
95    // is understood as Linux abstract namespace socket.
96    let socket_addr = match env_sock.strip_prefix('@') {
97        Some(stripped_addr) => socket::UnixAddr::new_abstract(stripped_addr.as_bytes())
98            .with_context(|| format!("invalid Unix socket abstract address {}", env_sock))?,
99        None => socket::UnixAddr::new(env_sock.as_str())
100            .with_context(|| format!("invalid Unix socket path address {}", env_sock))?,
101    };
102
103    let socket = UnixDatagram::unbound().context("failed to open Unix datagram socket")?;
104    let msg = state
105        .iter()
106        .fold(String::new(), |res, s| res + &format!("{}\n", s))
107        .into_bytes();
108    let msg_len = msg.len();
109    let msg_iov = IoSlice::new(&msg);
110
111    let ancillary = if !fds.is_empty() {
112        vec![socket::ControlMessage::ScmRights(fds)]
113    } else {
114        vec![]
115    };
116
117    let sent_len = socket::sendmsg(
118        socket.as_raw_fd(),
119        &[msg_iov],
120        &ancillary,
121        socket::MsgFlags::empty(),
122        Some(&socket_addr),
123    )
124    .map_err(|e| io::Error::from_raw_os_error(e as i32))
125    .context("failed to send notify datagram")?;
126
127    if sent_len != msg_len {
128        return Err(format!(
129            "incomplete notify sendmsg, sent {} out of {}",
130            sent_len, msg_len
131        )
132        .into());
133    }
134
135    Ok(true)
136}
137
138#[derive(Clone, Debug, PartialEq, Eq, Hash)]
139/// Status changes, see `sd_notify(3)`.
140pub enum NotifyState {
141    /// D-Bus error-style error code.
142    Buserror(String),
143    /// errno-style error code.
144    Errno(u8),
145    /// A name for the submitted file descriptors.
146    Fdname(String),
147    /// Stores additional file descriptors in the service manager. Use [`notify_with_fds`] with this.
148    Fdstore,
149    /// Remove stored file descriptors. Must be used together with [`NotifyState::Fdname`].
150    FdstoreRemove,
151    /// Tell the service manager to not poll the filedescriptors for errors. This causes
152    /// systemd to hold on to broken file descriptors which must be removed manually.
153    /// Must be used together with [`NotifyState::Fdstore`].
154    FdpollDisable,
155    /// The main process ID of the service, in case of forking applications.
156    Mainpid(unistd::Pid),
157    /// Custom state change, as a `KEY=VALUE` string.
158    Other(String),
159    /// Service startup is finished.
160    Ready,
161    /// Service is reloading.
162    Reloading,
163    /// Custom status change.
164    Status(String),
165    /// Service is beginning to shutdown.
166    Stopping,
167    /// Tell the service manager to update the watchdog timestamp.
168    Watchdog,
169    /// Reset watchdog timeout value during runtime.
170    WatchdogUsec(u64),
171}
172
173impl fmt::Display for NotifyState {
174    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175        match *self {
176            NotifyState::Buserror(ref s) => write!(f, "BUSERROR={}", s),
177            NotifyState::Errno(e) => write!(f, "ERRNO={}", e),
178            NotifyState::Fdname(ref s) => write!(f, "FDNAME={}", s),
179            NotifyState::Fdstore => write!(f, "FDSTORE=1"),
180            NotifyState::FdstoreRemove => write!(f, "FDSTOREREMOVE=1"),
181            NotifyState::FdpollDisable => write!(f, "FDPOLL=0"),
182            NotifyState::Mainpid(ref p) => write!(f, "MAINPID={}", p),
183            NotifyState::Other(ref s) => write!(f, "{}", s),
184            NotifyState::Ready => write!(f, "READY=1"),
185            NotifyState::Reloading => write!(f, "RELOADING=1"),
186            NotifyState::Status(ref s) => write!(f, "STATUS={}", s),
187            NotifyState::Stopping => write!(f, "STOPPING=1"),
188            NotifyState::Watchdog => write!(f, "WATCHDOG=1"),
189            NotifyState::WatchdogUsec(u) => write!(f, "WATCHDOG_USEC={}", u),
190        }
191    }
192}
193
194/// Perform some basic sanity checks against state entries.
195fn sanity_check_state_entries(state: &[NotifyState]) -> Result<(), SdError> {
196    for (index, entry) in state.iter().enumerate() {
197        match entry {
198            NotifyState::Fdname(ref name) => validate_fdname(name),
199            _ => Ok(()),
200        }
201        .with_context(|| format!("invalid notify state entry #{}", index))?;
202    }
203
204    Ok(())
205}
206
207/// Validate an `FDNAME` according to systemd rules.
208///
209/// The name may consist of arbitrary ASCII characters except control
210/// characters or ":". It may not be longer than 255 characters.
211fn validate_fdname(fdname: &str) -> Result<(), SdError> {
212    if fdname.len() > 255 {
213        return Err(format!("fdname '{}' longer than 255 characters", fdname).into());
214    }
215
216    for c in fdname.chars() {
217        if !c.is_ascii() || c == ':' || c.is_ascii_control() {
218            return Err(format!("invalid character '{}' in fdname '{}'", c, fdname).into());
219        }
220    }
221
222    Ok(())
223}