cf_daemonize/
lib.rs

1// Copyright (c) 2016 Fedor Gogolev <knsd@knsd.net>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//!
10//! daemonize is a library for writing system daemons. Inspired by the Python library [thesharp/daemonize](https://github.com/thesharp/daemonize).
11//!
12//! The respository is located at https://github.com/knsd/daemonize/.
13//!
14//! Usage example:
15//!
16//! ```
17//! extern crate daemonize;
18//!
19//! use std::fs::File;
20//!
21//! use daemonize::Daemonize;
22//!
23//! fn main() {
24//!     let stdout = File::create("/tmp/daemon.out").unwrap();
25//!     let stderr = File::create("/tmp/daemon.err").unwrap();
26//!
27//!     let daemonize = Daemonize::new()
28//!         .pid_file("/tmp/test.pid") // Every method except `new` and `start`
29//!         .chown_pid_file(true)      // is optional, see `Daemonize` documentation
30//!         .working_directory("/tmp") // for default behaviour.
31//!         .user("nobody")
32//!         .group("daemon") // Group name
33//!         .group(2)        // or group id.
34//!         .umask(0o777)    // Set umask, `0o027` by default.
35//!         .stdout(stdout)  // Redirect stdout to `/tmp/daemon.out`.
36//!         .stderr(stderr)  // Redirect stderr to `/tmp/daemon.err`.
37//!         .privileged_action(|| "Executed before drop privileges");
38//!
39//!     match daemonize.start() {
40//!         Ok(_) => println!("Success, daemonized"),
41//!         Err(e) => eprintln!("Error, {}", e),
42//!     }
43//! }
44//! ```
45
46#![cfg_attr(feature = "clippy", feature(plugin))]
47#![cfg_attr(feature = "clippy", plugin(clippy))]
48
49mod ffi;
50
51extern crate libc;
52
53use std::env::set_current_dir;
54use std::ffi::CString;
55use std::fmt;
56use std::fs::File;
57use std::io;
58use std::mem::transmute;
59use std::os::unix::ffi::OsStringExt;
60use std::os::unix::io::AsRawFd;
61use std::path::{Path, PathBuf};
62use std::process::exit;
63
64use libc::{
65    c_int, close, dup2, fork, ftruncate, getpid, open, setgid, setsid, setuid, umask, write,
66    LOCK_EX, LOCK_NB,
67};
68pub use libc::{gid_t, mode_t, uid_t};
69
70use self::ffi::{chroot, flock, get_gid_by_name, get_uid_by_name};
71
72macro_rules! tryret {
73    ($expr:expr, $ret:expr, $err:expr) => {
74        if $expr == -1 {
75            return Err($err(errno()));
76        } else {
77            $ret
78        }
79    };
80}
81
82pub type Errno = c_int;
83
84/// This error type for `Daemonize` `start` method.
85#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
86pub enum DaemonizeError {
87    /// Unable to fork
88    Fork,
89    /// Unable to create new session
90    DetachSession(Errno),
91    /// Unable to resolve group name to group id
92    GroupNotFound,
93    /// Group option contains NUL
94    GroupContainsNul,
95    /// Unable to set group
96    SetGroup(Errno),
97    /// Unable to resolve user name to user id
98    UserNotFound,
99    /// User option contains NUL
100    UserContainsNul,
101    /// Unable to set user
102    SetUser(Errno),
103    /// Unable to change directory
104    ChangeDirectory,
105    /// pid_file option contains NUL
106    PathContainsNul,
107    /// Unable to open pid file
108    OpenPidfile,
109    /// Unable to lock pid file
110    LockPidfile(Errno),
111    /// Unable to chown pid file
112    ChownPidfile(Errno),
113    /// Unable to redirect standard streams to /dev/null
114    RedirectStreams(Errno),
115    /// Unable to write self pid to pid file
116    WritePid,
117    /// Unable to chroot
118    Chroot(Errno),
119    // Hints that destructuring should not be exhaustive.
120    // This enum may grow additional variants, so this makes sure clients
121    // don't count on exhaustive matching. Otherwise, adding a new variant
122    // could break existing code.
123    #[doc(hidden)]
124    __Nonexhaustive,
125}
126
127impl DaemonizeError {
128    fn __description(&self) -> &str {
129        match *self {
130            DaemonizeError::Fork => "unable to fork",
131            DaemonizeError::DetachSession(_) => "unable to create new session",
132            DaemonizeError::GroupNotFound => "unable to resolve group name to group id",
133            DaemonizeError::GroupContainsNul => "group option contains NUL",
134            DaemonizeError::SetGroup(_) => "unable to set group",
135            DaemonizeError::UserNotFound => "unable to resolve user name to user id",
136            DaemonizeError::UserContainsNul => "user option contains NUL",
137            DaemonizeError::SetUser(_) => "unable to set user",
138            DaemonizeError::ChangeDirectory => "unable to change directory",
139            DaemonizeError::PathContainsNul => "pid_file option contains NUL",
140            DaemonizeError::OpenPidfile => "unable to open pid file",
141            DaemonizeError::LockPidfile(_) => "unable to lock pid file",
142            DaemonizeError::ChownPidfile(_) => "unable to chown pid file",
143            DaemonizeError::RedirectStreams(_) => {
144                "unable to redirect standard streams to /dev/null"
145            }
146            DaemonizeError::WritePid => "unable to write self pid to pid file",
147            DaemonizeError::Chroot(_) => "unable to chroot into directory",
148            DaemonizeError::__Nonexhaustive => unreachable!(),
149        }
150    }
151}
152
153impl std::fmt::Display for DaemonizeError {
154    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
155        self.__description().fmt(f)
156    }
157}
158
159impl std::error::Error for DaemonizeError {
160    fn description(&self) -> &str {
161        self.__description()
162    }
163}
164
165type Result<T> = std::result::Result<T, DaemonizeError>;
166
167/// Expects system user id or name. If name is provided it will be resolved to id later.
168#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
169pub enum User {
170    Name(String),
171    Id(uid_t),
172}
173
174impl<'a> From<&'a str> for User {
175    fn from(t: &'a str) -> User {
176        User::Name(t.to_owned())
177    }
178}
179
180impl From<uid_t> for User {
181    fn from(t: uid_t) -> User {
182        User::Id(t)
183    }
184}
185
186/// Expects system group id or name. If name is provided it will be resolved to id later.
187#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
188pub enum Group {
189    Name(String),
190    Id(gid_t),
191}
192
193impl<'a> From<&'a str> for Group {
194    fn from(t: &'a str) -> Group {
195        Group::Name(t.to_owned())
196    }
197}
198
199impl From<gid_t> for Group {
200    fn from(t: gid_t) -> Group {
201        Group::Id(t)
202    }
203}
204
205#[derive(Debug)]
206enum StdioImp {
207    Devnull,
208    RedirectToFile(File),
209}
210
211/// Describes what to do with a standard I/O stream for a child process.
212#[derive(Debug)]
213pub struct Stdio {
214    inner: StdioImp,
215}
216
217impl Stdio {
218    fn devnull() -> Self {
219        Self {
220            inner: StdioImp::Devnull,
221        }
222    }
223}
224
225impl From<File> for Stdio {
226    fn from(file: File) -> Self {
227        Self {
228            inner: StdioImp::RedirectToFile(file),
229        }
230    }
231}
232
233/// Daemonization options.
234///
235/// Fork the process in the background, disassociate from its process group and the control terminal.
236/// Change umask value to `0o027`, redirect all standard streams to `/dev/null`. Change working
237/// directory to `/` or provided value.
238///
239/// Optionally:
240///
241///   * maintain and lock the pid-file;
242///   * drop user privileges;
243///   * drop group privileges;
244///   * change root directory;
245///   * change the pid-file ownership to provided user (and/or) group;
246///   * execute any provided action just before dropping privileges.
247///
248pub struct Daemonize<T> {
249    directory: PathBuf,
250    pid_file: Option<PathBuf>,
251    chown_pid_file: bool,
252    user: Option<User>,
253    group: Option<Group>,
254    umask: mode_t,
255    root: Option<PathBuf>,
256    privileged_action: Box<Fn() -> T>,
257    exit_action: Box<Fn()>,
258    stdin: Stdio,
259    stdout: Stdio,
260    stderr: Stdio,
261}
262
263impl<T> fmt::Debug for Daemonize<T> {
264    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
265        fmt.debug_struct("Daemonize")
266            .field("directory", &self.directory)
267            .field("pid_file", &self.pid_file)
268            .field("chown_pid_file", &self.chown_pid_file)
269            .field("user", &self.user)
270            .field("group", &self.group)
271            .field("umask", &self.umask)
272            .field("root", &self.root)
273            .field("stdin", &self.stdin)
274            .field("stdout", &self.stdout)
275            .field("stderr", &self.stderr)
276            .finish()
277    }
278}
279
280impl Daemonize<()> {
281    pub fn new() -> Self {
282        Daemonize {
283            directory: Path::new("/").to_owned(),
284            pid_file: None,
285            chown_pid_file: false,
286            user: None,
287            group: None,
288            umask: 0o027,
289            privileged_action: Box::new(|| ()),
290            exit_action: Box::new(|| ()),
291            root: None,
292            stdin: Stdio::devnull(),
293            stdout: Stdio::devnull(),
294            stderr: Stdio::devnull(),
295        }
296    }
297}
298
299impl<T> Daemonize<T> {
300    /// Create pid-file at `path`, lock it exclusive and write daemon pid.
301    pub fn pid_file<F: AsRef<Path>>(mut self, path: F) -> Self {
302        self.pid_file = Some(path.as_ref().to_owned());
303        self
304    }
305
306    /// If `chown` is true, daemonize will change the pid-file ownership, if user or group are provided
307    pub fn chown_pid_file(mut self, chown: bool) -> Self {
308        self.chown_pid_file = chown;
309        self
310    }
311
312    /// Change working directory to `path` or `/` by default.
313    pub fn working_directory<F: AsRef<Path>>(mut self, path: F) -> Self {
314        self.directory = path.as_ref().to_owned();
315        self
316    }
317
318    /// Drop privileges to `user`.
319    pub fn user<U: Into<User>>(mut self, user: U) -> Self {
320        self.user = Some(user.into());
321        self
322    }
323
324    /// Drop privileges to `group`.
325    pub fn group<G: Into<Group>>(mut self, group: G) -> Self {
326        self.group = Some(group.into());
327        self
328    }
329
330    /// Change umask to `mask` or `0o027` by default.
331    pub fn umask(mut self, mask: mode_t) -> Self {
332        self.umask = mask;
333        self
334    }
335
336    /// Change root to `path`
337    pub fn chroot<F: AsRef<Path>>(mut self, path: F) -> Self {
338        self.root = Some(path.as_ref().to_owned());
339        self
340    }
341
342    /// Execute `action` just before dropping privileges. Most common usecase is to open listening socket.
343    /// Result of `action` execution will be returned by `start` method.
344    pub fn privileged_action<N, F: Fn() -> N + Sized + 'static>(self, action: F) -> Daemonize<N> {
345        let mut new: Daemonize<N> = unsafe { transmute(self) };
346        new.privileged_action = Box::new(action);
347        new
348    }
349
350    /// Execute `action` just before exitin from the parent process. Most common usecase is to wait for forked process.
351    pub fn exit_action<F: Fn() + Sized + 'static>(self, action: F) -> Daemonize<T> {
352        let mut new: Daemonize<T> = unsafe { transmute(self) };
353        new.exit_action = Box::new(action);
354        new
355    }
356
357    /// Configuration for the child process's standard output stream.
358    pub fn stdout<S: Into<Stdio>>(mut self, stdio: S) -> Self {
359        self.stdout = stdio.into();
360        self
361    }
362
363    /// Configuration for the child process's standard error stream.
364    pub fn stderr<S: Into<Stdio>>(mut self, stdio: S) -> Self {
365        self.stderr = stdio.into();
366        self
367    }
368
369    /// Start daemonization process.
370    pub fn start(self) -> std::result::Result<T, DaemonizeError> {
371        // Maps an Option<T> to Option<U> by applying a function Fn(T) -> Result<U, DaemonizeError>
372        // to a contained value and try! it's result
373        macro_rules! maptry {
374            ($expr:expr, $f: expr) => {
375                match $expr {
376                    None => None,
377                    Some(x) => Some(try!($f(x))),
378                };
379            };
380        }
381
382        unsafe {
383            let pid_file_fd = maptry!(self.pid_file.clone(), create_pid_file);
384
385            try!(self.perform_fork(true));
386
387            try!(set_current_dir(&self.directory).map_err(|_| DaemonizeError::ChangeDirectory));
388            try!(set_sid());
389            umask(self.umask);
390
391            try!(self.perform_fork(false));
392
393            try!(redirect_standard_streams(
394                self.stdin,
395                self.stdout,
396                self.stderr
397            ));
398
399            let uid = maptry!(self.user, get_user);
400            let gid = maptry!(self.group, get_group);
401
402            if self.chown_pid_file {
403                let args: Option<(PathBuf, uid_t, gid_t)> = match (self.pid_file, uid, gid) {
404                    (Some(pid), Some(uid), Some(gid)) => Some((pid, uid, gid)),
405                    (Some(pid), None, Some(gid)) => Some((pid, uid_t::max_value() - 1, gid)),
406                    (Some(pid), Some(uid), None) => Some((pid, uid, gid_t::max_value() - 1)),
407                    // Or pid file is not provided, or both user and group
408                    _ => None,
409                };
410
411                maptry!(args, |(pid, uid, gid)| chown_pid_file(pid, uid, gid));
412            }
413
414            let privileged_action_result = (self.privileged_action)();
415
416            maptry!(self.root, change_root);
417
418            maptry!(gid, set_group);
419            maptry!(uid, set_user);
420
421            maptry!(pid_file_fd, write_pid_file);
422
423            Ok(privileged_action_result)
424        }
425    }
426
427    unsafe fn perform_fork(&self, perform_action: bool) -> Result<()> {
428        let pid = fork();
429        if pid < 0 {
430            Err(DaemonizeError::Fork)
431        } else if pid == 0 {
432            Ok(())
433        } else {
434            if perform_action {
435                (self.exit_action)();
436            }
437            exit(0)
438        }
439    }
440}
441
442unsafe fn set_sid() -> Result<()> {
443    tryret!(setsid(), Ok(()), DaemonizeError::DetachSession)
444}
445
446unsafe fn redirect_standard_streams(stdin: Stdio, stdout: Stdio, stderr: Stdio) -> Result<()> {
447    let devnull_fd = open(transmute(b"/dev/null\0"), libc::O_RDWR);
448    if -1 == devnull_fd {
449        return Err(DaemonizeError::RedirectStreams(errno()));
450    }
451
452    let process_stdio = |fd, stdio: Stdio| {
453        tryret!(close(fd), (), DaemonizeError::RedirectStreams);
454        match stdio.inner {
455            StdioImp::Devnull => {
456                tryret!(dup2(devnull_fd, fd), (), DaemonizeError::RedirectStreams);
457            }
458            StdioImp::RedirectToFile(file) => {
459                let raw_fd = file.as_raw_fd();
460                tryret!(dup2(raw_fd, fd), (), DaemonizeError::RedirectStreams);
461            }
462        };
463        Ok(())
464    };
465
466    process_stdio(libc::STDIN_FILENO, stdin)?;
467    process_stdio(libc::STDOUT_FILENO, stdout)?;
468    process_stdio(libc::STDERR_FILENO, stderr)?;
469
470    tryret!(close(devnull_fd), (), DaemonizeError::RedirectStreams);
471
472    Ok(())
473}
474
475unsafe fn get_group(group: Group) -> Result<gid_t> {
476    match group {
477        Group::Id(id) => Ok(id),
478        Group::Name(name) => {
479            let s = try!(CString::new(name).map_err(|_| DaemonizeError::GroupContainsNul));
480            match get_gid_by_name(&s) {
481                Some(id) => get_group(Group::Id(id)),
482                None => Err(DaemonizeError::GroupNotFound),
483            }
484        }
485    }
486}
487
488unsafe fn set_group(group: gid_t) -> Result<()> {
489    tryret!(setgid(group), Ok(()), DaemonizeError::SetGroup)
490}
491
492unsafe fn get_user(user: User) -> Result<uid_t> {
493    match user {
494        User::Id(id) => Ok(id),
495        User::Name(name) => {
496            let s = try!(CString::new(name).map_err(|_| DaemonizeError::UserContainsNul));
497            match get_uid_by_name(&s) {
498                Some(id) => get_user(User::Id(id)),
499                None => Err(DaemonizeError::UserNotFound),
500            }
501        }
502    }
503}
504
505unsafe fn set_user(user: uid_t) -> Result<()> {
506    tryret!(setuid(user), Ok(()), DaemonizeError::SetUser)
507}
508
509unsafe fn create_pid_file(path: PathBuf) -> Result<libc::c_int> {
510    let path_c = try!(pathbuf_into_cstring(path));
511
512    let fd = open(path_c.as_ptr(), libc::O_WRONLY | libc::O_CREAT, 0o666);
513    if -1 == fd {
514        return Err(DaemonizeError::OpenPidfile);
515    }
516
517    tryret!(
518        flock(fd, LOCK_EX | LOCK_NB),
519        Ok(fd),
520        DaemonizeError::LockPidfile
521    )
522}
523
524unsafe fn chown_pid_file(path: PathBuf, uid: uid_t, gid: gid_t) -> Result<()> {
525    let path_c = try!(pathbuf_into_cstring(path));
526    tryret!(
527        libc::chown(path_c.as_ptr(), uid, gid),
528        Ok(()),
529        DaemonizeError::ChownPidfile
530    )
531}
532
533unsafe fn write_pid_file(fd: libc::c_int) -> Result<()> {
534    let pid = getpid();
535    let pid_buf = format!("{}", pid).into_bytes();
536    let pid_length = pid_buf.len();
537    let pid_c = CString::new(pid_buf).unwrap();
538    if -1 == ftruncate(fd, 0) {
539        return Err(DaemonizeError::WritePid);
540    }
541    if write(fd, transmute(pid_c.as_ptr()), pid_length) < pid_length as isize {
542        Err(DaemonizeError::WritePid)
543    } else {
544        Ok(())
545    }
546}
547
548unsafe fn change_root(path: PathBuf) -> Result<()> {
549    let path_c = pathbuf_into_cstring(path)?;
550
551    if chroot(path_c.as_ptr()) == 0 {
552        Ok(())
553    } else {
554        Err(DaemonizeError::Chroot(errno()))
555    }
556}
557
558fn pathbuf_into_cstring(path: PathBuf) -> Result<CString> {
559    CString::new(path.into_os_string().into_vec()).map_err(|_| DaemonizeError::PathContainsNul)
560}
561
562fn errno() -> Errno {
563    io::Error::last_os_error().raw_os_error().expect("errno")
564}