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
100
101
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()
            .create(true)
            .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()
            .create(true)
            .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() }
    }
}