use std::ffi::{CString, OsStr, OsString};
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::exit;
use nix::sys::stat::{Mode, umask};
#[cfg(not(target_os = "macos"))]
use nix::unistd::{
chdir, chown, fork, ForkResult, getpid, Gid, initgroups, Pid, setgid, setsid,
setuid, Uid,
};
#[cfg(target_os = "macos")]
use nix::unistd::{
chdir, chown, close, dup2, fork, ForkResult, getpid, Gid, Pid, setgid, setsid, setuid, Uid,
};
use crate::{DaemonError, Result};
use crate::ffi::{PasswdRecord, set_proc_name};
use crate::group::Group;
use crate::stdio::{redirect_stdio, Stdio};
use crate::user::User;
pub struct Daemon {
pub chdir: PathBuf,
pub pid_file: Option<PathBuf>,
pub chown_pid_file: bool,
pub user: Option<User>,
pub group: Option<Group>,
pub umask: u16,
pub stdin: Stdio,
pub stdout: Stdio,
pub stderr: Stdio,
pub name: Option<OsString>,
}
impl Daemon {
pub fn new() -> Self {
Daemon {
chdir: Path::new("/").to_owned(),
pid_file: None,
chown_pid_file: false,
user: None,
group: None,
umask: 0o027,
stdin: Stdio::devnull(),
stdout: Stdio::devnull(),
stderr: Stdio::devnull(),
name: None,
}
}
pub fn pid_file<T: AsRef<Path>>(mut self, path: T, chmod: Option<bool>) -> Self {
self.pid_file = Some(path.as_ref().to_owned());
self.chown_pid_file = chmod.unwrap_or(false);
self
}
pub fn work_dir<T: AsRef<Path>>(mut self, path: T) -> Self {
self.chdir = path.as_ref().to_owned();
self
}
pub fn user<T: Into<User>>(mut self, user: T) -> Self {
self.user = Some(user.into());
self
}
pub fn group<T: Into<Group>>(mut self, group: T) -> Self {
self.group = Some(group.into());
self
}
pub fn umask(mut self, mask: u16) -> Self {
self.umask = mask;
self
}
pub fn stdin<T: Into<Stdio>>(mut self, stdio: T) -> Self {
self.stdin = stdio.into();
self
}
pub fn stdout<T: Into<Stdio>>(mut self, stdio: T) -> Self {
self.stdout = stdio.into();
self
}
pub fn stderr<T: Into<Stdio>>(mut self, stdio: T) -> Self {
self.stderr = stdio.into();
self
}
pub fn name(mut self, name: &OsStr) -> Self {
self.name = Some(OsString::from(name));
self
}
pub fn start(self) -> Result<()> {
let pid: Pid;
let has_pid_file = self.pid_file.is_some();
let pid_file_path = match self.pid_file {
Some(path) => path.clone(),
None => Path::new("").to_path_buf(),
};
redirect_stdio(&self.stdin, &self.stdout, &self.stderr)?;
if self.chown_pid_file && (self.user.is_none() || self.group.is_none()) {
return Err(DaemonError::InvalidUserGroupPair);
} else if (self.user.is_some() || self.group.is_some())
&& (self.user.is_none() || self.group.is_none())
{
return Err(DaemonError::InvalidUserGroupPair);
}
unsafe {
match fork() {
Ok(ForkResult::Parent { child: _ }) => exit(0),
Ok(ForkResult::Child) => (),
Err(_) => return Err(DaemonError::Fork),
}
}
if let Some(proc_name) = &self.name {
match set_proc_name(proc_name.as_ref()) {
Ok(()) => (),
Err(e) => return Err(e)
}
}
let umask_mode = match Mode::from_bits(self.umask as _) {
Some(mode) => mode,
None => return Err(DaemonError::InvalidUmaskBits),
};
umask(umask_mode);
if let Err(_) = setsid() {
return Err(DaemonError::SetSid);
};
if let Err(_) = chdir::<Path>(self.chdir.as_path()) {
return Err(DaemonError::ChDir);
};
pid = getpid();
if has_pid_file {
let pid_file = &pid_file_path;
match File::create(pid_file) {
Ok(mut fp) => {
if let Err(_) = fp.write_all(pid.to_string().as_ref()) {
return Err(DaemonError::WritePid);
}
}
Err(_) => return Err(DaemonError::WritePid),
};
}
if self.user.is_some() && self.group.is_some() {
let user = match self.user.unwrap() {
User::Id(id) => Uid::from_raw(id),
};
let uname = match PasswdRecord::get_record_by_id(user.as_raw()) {
Ok(record) => record.pw_name,
Err(_) => return Err(DaemonError::InvalidUser),
};
let gr = match self.group.unwrap() {
Group::Id(id) => Gid::from_raw(id),
};
if self.chown_pid_file && has_pid_file {
match chown(&pid_file_path, Some(user), Some(gr)) {
Ok(_) => (),
Err(_) => return Err(DaemonError::ChownPid),
};
}
match setgid(gr) {
Ok(_) => (),
Err(_) => return Err(DaemonError::SetGid),
};
#[cfg(not(target_os = "macos"))]
{
let u_cstr = match CString::new(uname) {
Ok(cstr) => cstr,
Err(_) => return Err(DaemonError::SetGid),
};
match initgroups(&u_cstr, gr) {
Ok(_) => (),
Err(_) => return Err(DaemonError::InitGroups),
};
}
match setuid(user) {
Ok(_) => (),
Err(_) => return Err(DaemonError::SetUid),
}
}
let chdir_path = self.chdir.to_owned();
match chdir::<Path>(chdir_path.as_ref()) {
Ok(_) => (),
Err(_) => return Err(DaemonError::ChDir),
};
Ok(())
}
}