1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use std::env::set_current_dir;
use std::ffi::CString;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;

#[derive(Default, Debug, Clone)]
pub struct Daemonize {
    pub chdir: Option<PathBuf>,
    pub pid_file: Option<PathBuf>,
    pub stdin_file: Option<PathBuf>,
    pub stdout_file: Option<PathBuf>,
    pub stderr_file: Option<PathBuf>,
    pub umask: Option<libc::mode_t>,
    pub chroot: bool,
}

impl Daemonize {
    unsafe fn _doit(self) -> Result<(), &'static str> {
        match libc::fork() {
            -1 => return Err("fork() failed"),
            0 => {}
            _ => {
                libc::_exit(0);
            }
        }
        libc::setsid();
        match libc::fork() {
            -1 => return Err("fork() failed"),
            0 => {}
            _ => {
                libc::_exit(0);
            }
        };
        if let Some(umask) = self.umask {
            libc::umask(umask);
        }
        if let Some(chdir) = &self.chdir {
            set_current_dir(chdir).map_err(|_| "chdir() failed")?;
        }
        let stdin_file = self.stdin_file.unwrap_or_else(|| "/dev/null".into());
        let fd = OpenOptions::new()
            .read(true)
            .open(stdin_file)
            .map_err(|_| "Unable to open the stdin file")?;
        if libc::dup2(fd.as_raw_fd(), 0) == -1 {
            return Err("dup2(stdin) failed");
        }
        let stdout_file = self.stdout_file.unwrap_or_else(|| "/dev/null".into());
        let fd = OpenOptions::new()
            .write(true)
            .open(stdout_file)
            .map_err(|_| "Unable to open the stdout file")?;
        if libc::dup2(fd.as_raw_fd(), 1) == -1 {
            return Err("dup2(stdout) failed");
        }
        let stderr_file = self.stderr_file.unwrap_or_else(|| "/dev/null".into());
        let fd = OpenOptions::new()
            .write(true)
            .open(stderr_file)
            .map_err(|_| "Unable to open the stderr file")?;
        if libc::dup2(fd.as_raw_fd(), 2) == -1 {
            return Err("dup2(stderr) failed");
        }
        if let Some(pid_file) = self.pid_file {
            let pid = match libc::getpid() {
                -1 => return Err("getpid() failed"),
                pid => pid,
            };
            let pid_str = format!("{}", pid);
            File::create(pid_file)
                .map_err(|_| "Creating the PID file failed")?
                .write_all(pid_str.as_bytes())
                .map_err(|_| "Writing to the PID file failed")?;
        }
        if let Some(chdir) = &self.chdir {
            if self.chroot {
                let chdir = CString::new(
                    chdir
                        .as_os_str()
                        .to_str()
                        .ok_or("Unexpected characters in chdir path")?,
                )
                .map_err(|_| "Unexpected chdir path")?;
                if libc::chroot(chdir.as_ptr()) != 0 {
                    return Err("chroot failed");
                }
                set_current_dir("/").map_err(|_| "chdir(\"/\") failed")?;
            }
        }
        Ok(())
    }

    pub fn doit(self) -> Result<(), &'static str> {
        unsafe { self._doit() }
    }
}