use std::env;
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
const CHILD_ENV: &str = "SIGHTINGDB_DAEMON_CHILD";
const PID_FILE_ENV: &str = "SIGHTINGDB_PID_FILE";
pub fn is_child() -> bool {
env::var_os(CHILD_ENV).is_some()
}
pub fn pid_file() -> Option<PathBuf> {
env::var_os(PID_FILE_ENV).map(PathBuf::from)
}
pub fn remove_pid_file() {
let Some(path) = pid_file() else {
return;
};
if let Err(e) = fs::remove_file(&path)
&& e.kind() != std::io::ErrorKind::NotFound
{
log::warn!("Could not remove the pid file {}: {e}", path.display());
}
}
fn pid_file_candidates() -> Vec<PathBuf> {
let mut candidates = vec![PathBuf::from("/var/run/sightingdb.pid")];
if let Some(mut home) = dirs::home_dir() {
home.push(".sightingdb");
home.push("sightingdb.pid");
candidates.push(home);
}
candidates.push(PathBuf::from("./sightingdb.pid"));
candidates
}
fn choose_pid_file() -> Option<PathBuf> {
for candidate in pid_file_candidates() {
if fs::write(&candidate, b"").is_ok() {
return Some(candidate);
}
}
log::warn!("Nowhere writable for a pid file; continuing without one");
None
}
#[cfg(unix)]
pub fn detach(log_out: &std::path::Path, log_err: &std::path::Path) -> Result<u32> {
use anyhow::Context;
use std::fs::File;
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};
let exe = env::current_exe().context("locating our own executable")?;
let stdout =
File::create(log_out).with_context(|| format!("creating {}", log_out.display()))?;
let stderr =
File::create(log_err).with_context(|| format!("creating {}", log_err.display()))?;
let pid_path = choose_pid_file();
let mut command = Command::new(&exe);
command
.args(env::args_os().skip(1))
.env(CHILD_ENV, "1")
.stdin(Stdio::null())
.stdout(stdout)
.stderr(stderr)
.process_group(0);
if let Some(path) = &pid_path {
command.env(PID_FILE_ENV, path);
}
let child = command
.spawn()
.with_context(|| format!("re-executing {}", exe.display()))?;
let pid = child.id();
if let Some(path) = &pid_path
&& let Err(e) = fs::write(path, format!("{pid}\n"))
{
log::warn!("Could not write the pid file {}: {e}", path.display());
}
Ok(pid)
}
#[cfg(not(unix))]
pub fn detach(_log_out: &std::path::Path, _log_err: &std::path::Path) -> Result<u32> {
anyhow::bail!(
"daemonize = true is only supported on Unix. Set daemonize = false and run \
under a service manager instead."
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_launcher_is_not_the_child() {
assert!(!is_child());
assert!(pid_file().is_none());
}
#[test]
fn removing_a_pid_file_we_do_not_have_is_harmless() {
remove_pid_file();
}
#[test]
fn the_candidate_list_always_ends_somewhere_relative() {
let candidates = pid_file_candidates();
assert_eq!(
candidates.first().unwrap(),
&PathBuf::from("/var/run/sightingdb.pid")
);
assert_eq!(
candidates.last().unwrap(),
&PathBuf::from("./sightingdb.pid")
);
}
}