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}