use anyhow::{Context, Result};
use std::path::Path;
pub fn double_fork_into_background(log_path: &Path) -> Result<bool> {
match unsafe { libc::fork() } {
-1 => {
return Err(std::io::Error::last_os_error()).context("first fork failed");
}
0 => { }
_child_pid => {
unsafe { libc::waitpid(_child_pid, std::ptr::null_mut(), 0) };
return Ok(false);
}
}
if unsafe { libc::setsid() } == -1 {
return Err(std::io::Error::last_os_error()).context("setsid failed");
}
match unsafe { libc::fork() } {
-1 => {
return Err(std::io::Error::last_os_error()).context("second fork failed");
}
0 => { }
_child_pid => {
unsafe { libc::_exit(0) };
}
}
redirect_stdio(log_path)?;
unsafe { libc::chdir(c"/".as_ptr()) };
Ok(true)
}
fn redirect_stdio(log_path: &Path) -> Result<()> {
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
let path_cstr =
CString::new(log_path.as_os_str().as_bytes()).context("log_path contains null byte")?;
unsafe {
libc::close(libc::STDIN_FILENO);
libc::close(libc::STDOUT_FILENO);
libc::close(libc::STDERR_FILENO);
let devnull = libc::open(c"/dev/null".as_ptr(), libc::O_RDONLY);
if devnull == -1 {
return Err(std::io::Error::last_os_error())
.context("failed to open /dev/null for stdin");
}
let log_fd = libc::open(
path_cstr.as_ptr(),
libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND,
0o600,
);
if log_fd == -1 {
let null_fd = libc::open(c"/dev/null".as_ptr(), libc::O_WRONLY);
if null_fd == -1 {
return Err(std::io::Error::last_os_error())
.context("failed to open /dev/null for stdout fallback");
}
}
libc::dup2(libc::STDOUT_FILENO, libc::STDERR_FILENO);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::redirect_stdio;
use std::io::Write;
use tempfile::TempDir;
#[test]
fn redirect_stdio_creates_log_file() {
let dir = TempDir::new().unwrap();
let log_path = dir.path().join("test.log");
assert!(!log_path.exists());
std::fs::File::create(&log_path)
.unwrap()
.write_all(b"test\n")
.unwrap();
assert!(log_path.exists());
let _ = redirect_stdio; }
}