use crate::{
fork::{self, Parent},
fs, pidfile,
user::Privileges,
};
use nix::sys::stat::{self, Mode};
use std::{
env,
path::{Path, PathBuf},
process::exit,
};
pub const DEFAULT_UMASK: Mode = Mode::from_bits(0o0027).unwrap();
#[derive(Clone, Debug)]
pub struct Daemon {
user: Option<Privileges>,
stdout: PathBuf,
stderr: PathBuf,
pidfile: Option<PathBuf>,
umask: Mode,
workdir: PathBuf,
}
impl Default for Daemon {
fn default() -> Self {
Self {
user: None,
stdout: "/dev/null".into(),
stderr: "/dev/null".into(),
pidfile: None,
umask: DEFAULT_UMASK,
workdir: "/".into(),
}
}
}
impl Daemon {
pub fn new() -> Self {
Default::default()
}
pub fn user(mut self, user: Option<Privileges>) -> Self {
self.user = user;
self
}
pub fn working_directory<P: AsRef<Path>>(
mut self,
workdir: Option<P>,
) -> Self {
self.workdir = workdir
.as_ref()
.map(|path| path.as_ref())
.unwrap_or(Path::new("/"))
.to_path_buf();
self
}
pub fn pidfile<P: AsRef<Path>>(mut self, path: Option<P>) -> Self {
self.pidfile = path.map(|path| path.as_ref().to_path_buf());
self
}
pub fn stdout<P: AsRef<Path>>(mut self, path: Option<P>) -> Self {
self.stdout = path
.map(|path| path.as_ref().to_path_buf())
.unwrap_or_else(|| PathBuf::from("/dev/null"));
self
}
pub fn stderr<P: AsRef<Path>>(mut self, path: Option<P>) -> Self {
self.stderr = path
.map(|path| path.as_ref().to_path_buf())
.unwrap_or_else(|| PathBuf::from("/dev/null"));
self
}
pub fn umask(mut self, mode: Option<Mode>) -> Self {
self.umask = mode.unwrap_or(DEFAULT_UMASK);
self
}
fn prepare(self) -> Result<(), String> {
if let Some(pidfile) = self.pidfile {
pidfile::create(&pidfile)?;
}
if let Some(user) = self.user {
user.drop_privileges()?;
unsafe { user.set_env() };
}
env::set_current_dir(&self.workdir).map_err(|err| {
format!(
"failed to change working directory to '{}': {err}",
self.workdir.display()
)
})?;
stat::umask(self.umask);
fs::redirect_stdin()?;
fs::redirect_stdout(&self.stdout)?;
fs::redirect_stderr(&self.stderr)?;
Ok(())
}
pub fn daemonize(self) -> Parent {
let parent = fork::fork();
if let Err(err) = self.prepare() {
eprintln!("{err}");
exit(1);
}
parent
}
}