spirit_daemonize/lib.rs
1#![doc(test(attr(deny(warnings))))]
2#![warn(missing_docs)]
3
4//! A spirit extension for daemonization.
5//!
6//! The configuration in here extends the [`spirit`](https://crates.io/crates/spirit) configuration
7//! framework to automatically go into background based on user's configuration and command line
8//! options.
9//!
10//! # Examples
11//!
12//! ```rust
13//! use serde::Deserialize;
14//! use spirit::Spirit;
15//! use spirit::prelude::*;
16//! use spirit_daemonize::{Daemon, Opts as DaemonOpts};
17//! use structopt::StructOpt;
18//!
19//! // From config files
20//! #[derive(Default, Deserialize)]
21//! struct Cfg {
22//! #[serde(default)]
23//! daemon: Daemon,
24//! }
25//!
26//! // From command line
27//! #[derive(Debug, StructOpt)]
28//! struct Opts {
29//! #[structopt(flatten)]
30//! daemon: DaemonOpts,
31//! }
32//!
33//! fn main() {
34//! Spirit::<Opts, Cfg>::new()
35//! .with(unsafe {
36//! spirit_daemonize::extension(|c: &Cfg, o: &Opts| {
37//! (c.daemon.clone(), o.daemon.clone())
38//! })
39//! })
40//! .run(|_spirit| {
41//! // Possibly daemonized program goes here
42//! Ok(())
43//! });
44//! }
45//! ```
46//!
47//! # Added options
48//!
49//! The program above gets the `-d` command line option, which enables daemonization. Furthermore,
50//! the configuration now understands a new `daemon` section, with these options:
51//!
52//! * `user`: The user to become. Either a numeric ID or name. If not present, it doesn't change the
53//! user.
54//! * `group`: Similar as user, but with group.
55//! * `pid-file`: A pid file to write on startup. If not present, nothing is stored.
56//! * `workdir`: A working directory it'll switch into. If not set, defaults to `/`.
57//! * `daemonize`: Should this go into background or not? If combined with the
58//! [`Opts`](struct.Opts.html), it can be overridden on command line.
59//!
60//! # Multithreaded applications
61//!
62//! As daemonization is done by using `fork`, you should start any threads *after* you initialize
63//! the `spirit`. Otherwise you'll lose the threads (and further bad things will happen).
64//!
65//! The daemonization happens inside the application of [validator
66//! actions][spirit::validation::Action] of `config_validator` callback. If other config validators
67//! need to start any threads, they should be plugged in after the daemonization callback. However,
68//! the safer option is to start them inside the `run` method.
69
70use std::env;
71use std::fs::OpenOptions;
72use std::io::Write;
73use std::os::unix::fs::OpenOptionsExt;
74use std::os::unix::io::AsRawFd;
75use std::path::{Path, PathBuf};
76use std::process;
77use std::sync::Arc;
78
79use err_context::prelude::*;
80use log::{debug, trace, warn};
81use nix::sys::stat::{self, Mode};
82use nix::unistd::{self, ForkResult, Gid, Uid};
83use serde::de::DeserializeOwned;
84use serde::{Deserialize, Serialize};
85use spirit::extension::{Extensible, Extension};
86use spirit::validation::Action;
87
88use spirit::AnyError;
89use structdoc::StructDoc;
90#[cfg(feature = "cfg-help")]
91use structopt::StructOpt;
92
93/// Intermediate plumbing type.
94///
95/// This is passed through the [`Pipeline`][spirit::Pipeline] as a way of signalling the next
96/// actions. Users are not expected to interact directly with this.
97#[derive(Clone, Debug, Eq, PartialEq, Hash)]
98#[non_exhaustive]
99pub struct Daemonize {
100 /// Go into background.
101 pub daemonize: bool,
102
103 /// Store our own PID into this file.
104 ///
105 /// The one after we double-fork, so it is the real daemon's one.
106 pub pid_file: Option<PathBuf>,
107}
108
109impl Daemonize {
110 /// Goes into background according to the configuration.
111 ///
112 /// This does the actual work of daemonization. This can be used manually.
113 ///
114 /// This is not expected to fail in practice, as all checks are performed in advance. It can
115 /// fail for rare race conditions (eg. the directory where the PID file should go disappears
116 /// between the check and now) or if there are not enough PIDs available to fork.
117 ///
118 /// # Safety
119 ///
120 /// This is safe to call only if either the application is yet single-threaded (it's OK to
121 /// start more threads afterwards) or if `daemonize` is `false`.
122 pub unsafe fn daemonize(&self) -> Result<(), AnyError> {
123 if self.daemonize {
124 trace!("Redirecting stdio");
125 let devnull = OpenOptions::new()
126 .read(true)
127 .write(true)
128 .create(true)
129 .open("/dev/null")
130 .context("Failed to open /dev/null")?;
131 for fd in &[0, 1, 2] {
132 unistd::dup2(devnull.as_raw_fd(), *fd)
133 .with_context(|_| format!("Failed to redirect FD {}", fd))?;
134 }
135 trace!("Doing double fork");
136 if let ForkResult::Parent { .. } = unistd::fork().context("Failed to fork")? {
137 process::exit(0);
138 }
139 unistd::setsid()?;
140 if let ForkResult::Parent { .. } = unistd::fork().context("Failed to fork")? {
141 process::exit(0);
142 }
143 } else {
144 trace!("Not going to background");
145 }
146 if let Some(file) = self.pid_file.as_ref() {
147 let mut f = OpenOptions::new()
148 .write(true)
149 .create(true)
150 .truncate(true)
151 .mode(0o644)
152 .open(file)
153 .with_context(|_| format!("Failed to write PID file {}", file.display()))?;
154 writeln!(f, "{}", unistd::getpid())?;
155 }
156 Ok(())
157 }
158}
159
160/// Configuration of either user or a group.
161///
162/// This is used to load the configuration into which user and group to drop privileges.
163#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
164#[cfg_attr(feature = "cfg-help", derive(StructDoc))]
165#[serde(untagged)]
166#[non_exhaustive]
167pub enum SecId {
168 /// Look up based on the name.
169 Name(String),
170 /// Use the numerical value directly.
171 Id(u32),
172 /// Don't drop privileges.
173 ///
174 /// This is not read from configuration, but it is the default value available if nothing is
175 /// listed in configuration.
176 #[serde(skip)]
177 Nothing,
178}
179
180impl SecId {
181 fn is_nothing(&self) -> bool {
182 self == &SecId::Nothing
183 }
184}
185
186impl Default for SecId {
187 fn default() -> Self {
188 SecId::Nothing
189 }
190}
191
192/// A configuration fragment for configuration of daemonization.
193///
194/// This describes how to go into background with some details.
195///
196/// The fields can be manipulated by the user of this crate. However, it is not possible to create
197/// the struct manually. This is on purpose, some future versions might add more fields. If you
198/// want to create one, use `Daemon::default` and modify certain fields as needed.
199///
200/// # Examples
201///
202/// ```rust
203/// # use spirit_daemonize::Daemon;
204/// let mut daemon = Daemon::default();
205/// daemon.workdir = Some("/".into());
206/// ```
207///
208/// # See also
209///
210/// If you want to daemonize, but not to switch users (or allow switching users), either because
211/// the daemon needs to keep root privileges or because it is expected to be already started as
212/// ordinary user, use the [`UserDaemon`](struct.UserDaemon.html) instead.
213#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
214#[cfg_attr(feature = "cfg-help", derive(StructDoc))]
215#[serde(rename_all = "kebab-case")]
216#[non_exhaustive]
217pub struct Daemon {
218 /// The user to drop privileges to.
219 ///
220 /// The user is not changed if not provided.
221 #[serde(default, skip_serializing_if = "SecId::is_nothing")]
222 pub user: SecId,
223
224 /// The group to drop privileges to.
225 ///
226 /// The group is not changed if not provided.
227 #[serde(default, skip_serializing_if = "SecId::is_nothing")]
228 pub group: SecId,
229
230 /// Where to store a PID file.
231 ///
232 /// If not set, no PID file is created.
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub pid_file: Option<PathBuf>,
235
236 /// Switch to this working directory at startup.
237 ///
238 /// If not set, working directory is not switched.
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub workdir: Option<PathBuf>,
241
242 // This is overwritten by [`Opts::transform`](struct.Opts.html#method.transform).
243 //
244 /// Enable the daemonization.
245 ///
246 /// Even if this is false, some activity (changing users, setting PID file, etc) is still done,
247 /// but it doesn't go to background.
248 #[serde(default)]
249 pub daemonize: bool,
250}
251
252impl Daemon {
253 /// Prepare for daemonization.
254 ///
255 /// This does the fist (fallible) phase of daemonization and schedules the rest, though the
256 /// [`Daemonize`]. This is mostly used through [`extension'], but can also be used manually.
257 pub fn prepare(&self) -> Result<Daemonize, AnyError> {
258 // TODO: Tests for this
259 debug!("Preparing to daemonize with {:?}", self);
260
261 stat::umask(Mode::empty()); // No restrictions on write modes
262 let workdir = self
263 .workdir
264 .as_ref()
265 .map(|pb| pb as &Path)
266 .unwrap_or_else(|| Path::new("/"));
267 trace!("Changing working directory to {:?}", workdir);
268 env::set_current_dir(workdir)
269 .with_context(|_| format!("Failed to switch to workdir {}", workdir.display()))?;
270
271 // PrivDrop implements the necessary libc lookups to find the group and
272 // user entries matching the given names. If these queries fail,
273 // because the user or group names are invalid, the function will fail.
274 match self.group {
275 SecId::Id(id) => {
276 unistd::setgid(Gid::from_raw(id)).context("Failed to change the group")?
277 }
278 SecId::Name(ref name) => privdrop::PrivDrop::default()
279 .group(&name)
280 .apply()
281 .context("Failed to change the group")?,
282 SecId::Nothing => (),
283 }
284 match self.user {
285 SecId::Id(id) => {
286 unistd::setuid(Uid::from_raw(id)).context("Failed to change the user")?
287 }
288 SecId::Name(ref name) => privdrop::PrivDrop::default()
289 .user(&name)
290 .apply()
291 .context("Failed to change the user")?,
292 SecId::Nothing => (),
293 }
294
295 // We try to check the PID file is writable, as we still can reasonably report errors now.
296 if let Some(file) = self.pid_file.as_ref() {
297 let _ = OpenOptions::new()
298 .write(true)
299 .create(true)
300 .truncate(false)
301 .mode(0o644)
302 .open(file)
303 .with_context(|_| format!("Writing the PID file {}", file.display()))?;
304 }
305
306 Ok(Daemonize {
307 daemonize: self.daemonize,
308 pid_file: self.pid_file.clone(),
309 })
310 }
311
312 /// Perform the daemonization according to the setup inside.
313 ///
314 /// This is the routine one would call if using the fragments manually, not through the spirit
315 /// management and [`extension`].
316 ///
317 /// # Safety
318 ///
319 /// This is safe to call only if either the application is yet single-threaded (it's OK to
320 /// start more threads afterwards) or if `daemonize` is `false`.
321 pub unsafe fn daemonize(&self) -> Result<(), AnyError> {
322 self.prepare()?.daemonize()?;
323 Ok(())
324 }
325}
326
327impl From<UserDaemon> for Daemon {
328 fn from(ud: UserDaemon) -> Daemon {
329 Daemon {
330 pid_file: ud.pid_file,
331 workdir: ud.workdir,
332 daemonize: ud.daemonize,
333 ..Daemon::default()
334 }
335 }
336}
337
338impl From<(Daemon, Opts)> for Daemon {
339 fn from((d, o): (Daemon, Opts)) -> Self {
340 o.transform(d)
341 }
342}
343
344impl From<(UserDaemon, Opts)> for Daemon {
345 fn from((d, o): (UserDaemon, Opts)) -> Self {
346 o.transform(d.into())
347 }
348}
349
350// Workaround for https://github.com/TeXitoi/structopt/issues/333
351#[cfg_attr(not(doc), allow(missing_docs))]
352#[cfg_attr(
353 doc,
354 doc = r#"
355Command line options fragment.
356
357This adds the `-d` (`--daemonize`) and `-f` (`--foreground`) flag to command line. These
358override whatever is written in configuration (if merged together with the configuration).
359
360This can be used to transform the [`Daemon`] before daemonization.
361
362See the [`extension`] for how to use it.
363"#
364)]
365#[derive(Clone, Debug, StructOpt)]
366#[non_exhaustive]
367pub struct Opts {
368 /// Daemonize ‒ go to background (override the config).
369 #[structopt(short, long)]
370 pub daemonize: bool,
371
372 /// Stay in foreground (don't go to background even if config says so).
373 #[structopt(short, long)]
374 pub foreground: bool,
375}
376
377impl Opts {
378 /// Returns if daemonization is enabled.
379 pub fn daemonize(&self) -> bool {
380 self.daemonize && !self.foreground
381 }
382
383 /// Modifies the [`daemon`](struct.Daemon.html) according to daemonization set.
384 pub fn transform(&self, daemon: Daemon) -> Daemon {
385 Daemon {
386 daemonize: self.daemonize(),
387 ..daemon
388 }
389 }
390}
391
392/// A stripped-down version of [`Daemon`](struct.Daemon.html) without the user-switching options.
393///
394/// Sometimes, the daemon either needs to keep the root privileges or is started with the
395/// appropriate user right away, therefore the user should not be able to configure the `user` and
396/// `group` options.
397///
398/// This configuration fragment serves the role. Convert it to [`Daemon`] first, by
399/// [`into_daemon`][UserDaemon::into_daemon] (or using the [`Into`] trait).
400///
401/// # Examples
402///
403/// ```rust
404/// use spirit_daemonize::{Daemon, UserDaemon};
405///
406/// // No way to access the `pid_file` and others inside this thing and can't call `.daemonize()`.
407/// let user_daemon = UserDaemon::default();
408///
409/// let daemon: Daemon = user_daemon.into();
410/// assert!(daemon.pid_file.is_none());
411/// assert_eq!(daemon, Daemon::default());
412/// ```
413#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
414#[cfg_attr(feature = "cfg-help", derive(StructDoc))]
415#[serde(rename_all = "kebab-case")]
416#[non_exhaustive]
417pub struct UserDaemon {
418 /// Where to store a PID file.
419 ///
420 /// If not set, no PID file is created.
421 #[serde(skip_serializing_if = "Option::is_none")]
422 pub pid_file: Option<PathBuf>,
423
424 /// Switch to this working directory at startup.
425 ///
426 /// If not set, working directory is not switched.
427 #[serde(skip_serializing_if = "Option::is_none")]
428 pub workdir: Option<PathBuf>,
429
430 // This is overwritten by [`Opts::transform`](struct.Opts.html#method.transform).
431 //
432 /// Enable the daemonization.
433 ///
434 /// Even if this is false, some activity (setting PID file, etc) is still done,
435 /// but it doesn't go to background.
436 #[serde(default)]
437 pub daemonize: bool,
438}
439
440impl UserDaemon {
441 /// Converts to full-featured [`Daemon`].
442 ///
443 /// All the useful functionality is on [`Daemon`], therefore to use it, convert it first.
444 ///
445 /// It can be also converted using the usual [`From`]/[`Into`] traits.
446 pub fn into_daemon(self) -> Daemon {
447 self.into()
448 }
449}
450
451/// Creates an extension for daemonization as part of the spirit.
452///
453/// Goes into background (or not) as configured by configuration and options (see the crate
454/// example).
455///
456/// # Safety
457///
458/// Daemonization uses `fork`. Therefore, the caller must ensure this is run only before any
459/// threads are started.
460///
461/// This runs in the validation phase of the config validator.
462///
463/// Note that many thing may start threads. Specifically, logging with a background thread and the
464/// Tokio runtime do.
465///
466/// If you want to have logging available before daemonization (to have some output during/after
467/// daemonization), it is possible to do a two-phase initialization ‒ once before daemonization,
468/// but without a background thread, and then after. See the [relevant chapter in the
469/// guide][spirit::guide::daemonization].
470pub unsafe fn extension<E, C, D>(extractor: C) -> impl Extension<E>
471where
472 E: Extensible<Ok = E>,
473 E::Config: DeserializeOwned + Send + Sync + 'static,
474 E::Opts: StructOpt + Send + Sync + 'static,
475 C: Fn(&E::Config, &E::Opts) -> D + Send + Sync + 'static,
476 D: Into<Daemon>,
477{
478 move |e: E| {
479 let init = move |_: &_, cfg: &Arc<_>, opts: &_| -> Result<Action, AnyError> {
480 let d = extractor(cfg, opts);
481 d.into().daemonize()?;
482 Ok(Action::new())
483 };
484 e.config_validator(init)
485 }
486}