unix_daemonize/
lib.rs

1//! Easy to use daemonizing for rust programs in unix enviroment.
2//!
3//! ```
4//! daemonize_redirect(Some("stdout.log"), Some("stderr.log"), ChdirMode::ChdirRoot).unwrap();
5//! ```
6//! See examples for sample program.
7
8extern crate libc;
9
10use std::{io, env, ffi, path, process};
11
12/// The error type for daemonizing related operations.
13///
14/// Most variants holds `Option<i32>` value, where `i32`
15/// stands for `errno` result for corresponding OS functions.
16#[derive(Debug)]
17pub enum Error {
18    FirstFork(Option<i32>),
19    SecondFork(Option<i32>),
20    Setsid(Option<i32>),
21    Chdir(io::Error),
22    FilenameToStr(path::PathBuf),
23    FilenameFFI(path::PathBuf, ffi::NulError),
24    OpenStd(path::PathBuf, Option<i32>),
25    Dup2(Option<i32>),
26}
27
28#[derive(Debug)]
29pub enum ChdirMode {
30    NoChdir,
31    ChdirRoot,
32}
33
34struct Redirected(libc::c_int);
35
36impl Drop for Redirected {
37    fn drop(&mut self) {
38        if self.0 >= 0 {
39            unsafe { libc::close(self.0) };
40            self.0 = -1;
41        }
42    }
43}
44
45fn to_path_buf<P>(path: &Option<P>) -> path::PathBuf where P: AsRef<path::Path> {
46    if let &Some(ref p) = path {
47        p.as_ref().to_owned()
48    } else {
49        let null: &path::Path = "/dev/null".as_ref();
50        null.to_path_buf()
51    }
52}
53
54fn redirect<P>(std: Option<P>) -> Result<Redirected, Error> where P: AsRef<path::Path> {
55    let filename = std.as_ref()
56        .map(|s| s.as_ref().to_str())
57        .unwrap_or(Some("/dev/null"))
58        .ok_or(Error::FilenameToStr(to_path_buf(&std)));
59    let path = try!(ffi::CString::new(try!(filename)).map_err(|e| Error::FilenameFFI(to_path_buf(&std), e)));
60
61    let fd = unsafe { libc::open(path.as_ptr(),
62                                 libc::O_CREAT | libc::O_WRONLY | libc::O_APPEND,
63                                 (libc::S_IRUSR | libc::S_IRGRP | libc::S_IWGRP | libc::S_IWUSR) as libc::c_uint) };
64    if fd < 0 {
65        Err(Error::OpenStd(to_path_buf(&std), io::Error::last_os_error().raw_os_error()))
66    } else {
67        Ok(Redirected(fd))
68    }
69}
70
71/// Performs program daemonizing with optional redirection for STDIN and STDOUT.
72/// If the redirection parameter is `None`, then stream will be redirected to `/dev/null`,
73/// otherwise it will be redirected to the file provided.
74///
75/// Returns the new process id after all forks.
76pub fn daemonize_redirect<PO, PE>(stdout: Option<PO>, stderr: Option<PE>, chdir: ChdirMode) -> Result<libc::pid_t, Error>
77    where PO: AsRef<path::Path>, PE: AsRef<path::Path>
78{
79    daemonize(try!(redirect(stdout)), try!(redirect(stderr)), chdir)
80}
81
82fn daemonize(mut stdout_fd: Redirected, mut stderr_fd: Redirected, chdir: ChdirMode) -> Result<libc::pid_t, Error> {
83    macro_rules! errno {
84        ($err:ident) => ({ return Err(Error::$err(io::Error::last_os_error().raw_os_error())) })
85    }
86
87    let pid = unsafe { libc::fork() };
88    if pid < 0 {
89        errno!(FirstFork)
90    } else if pid != 0 {
91        process::exit(0)
92    }
93
94    if unsafe { libc::setsid() } < 0 {
95        errno!(Setsid)
96    }
97
98    unsafe { libc::signal(libc::SIGHUP, libc::SIG_IGN); }
99    let pid = unsafe { libc::fork() };
100    if pid < 0 {
101        errno!(SecondFork)
102    } else if pid != 0 {
103        process::exit(0)
104    }
105
106    if let ChdirMode::ChdirRoot = chdir {
107        match env::set_current_dir("/") {
108            Ok(()) => (),
109            Err(e) => {
110                return Err(Error::Chdir(e))
111            },
112        }
113    }
114
115    if unsafe { libc::dup2(stdout_fd.0, libc::STDOUT_FILENO) } < 0 {
116        errno!(Dup2)
117    } else {
118        stdout_fd.0 = -1
119    }
120
121    if unsafe { libc::dup2(stderr_fd.0, libc::STDERR_FILENO) } < 0 {
122        errno!(Dup2)
123    } else {
124        stderr_fd.0 = -1
125    }
126
127    Ok(unsafe { libc::getpid() })
128}