pub fn daemon(nochdir: bool, noclose: bool) -> Result<Fork>Expand description
The daemon function is for programs wishing to detach themselves from the controlling terminal and run in the background as system daemons.
nochdir = false, changes the current working directory to the root (/).noclose = false, redirects stdin, stdout, and stderr to/dev/null
§Common pitfall: relative paths and hidden diagnostics
With daemon(false, false), code after daemon() runs with cwd / and
stdio attached to /dev/null. A relative path such as
File::create("myapp.pid") is therefore resolved as /myapp.pid, not as a
file in the directory that launched the program. If creating that file fails,
println!, eprintln!, and panic output are also discarded because stderr
points at /dev/null.
Use absolute paths for PID files, logs, sockets, and config files. If your
daemon intentionally depends on the launch directory, pass nochdir = true.
While debugging startup, consider noclose = true or a readiness pipe so
errors can be observed by the launcher.
§Not performed by this function
daemon() is intentionally minimal. It does not perform several hardening
steps that some daemons want; do them yourself if you need them:
umask— the parent’s file-mode creation mask is inherited unchanged. Callunsafe { libc::umask(0) }(or your preferred mask) if file permissions matter.- Closing inherited file descriptors > 2 — only stdin/stdout/stderr are
handled (and only when
noclose = false). Any other descriptor the parent left open is inherited by the daemon; close them before or after forking. - Resetting signal state — inherited signal dispositions and the signal
mask are left as-is. Reset them with
sigaction/sigprocmaskif the parent may have customized them.
§Return Value
This function only ever returns in the daemon (grandchild) process:
Ok(Fork::Child)— You are the daemon. The original process and the intermediate child have already exited via_exit(0).Err(...)— A system call failed before the daemon could be created.
Ok(Fork::Parent(_)) is never returned because both parent processes
call _exit(0) internally. You do not need to match on it:
§Error observability
The original (launching) process calls _exit(0) at the first fork,
before setsid(), chdir(), and redirect_stdio() run. As a result, an
Err(...) from any of those steps is returned only inside the detached
first child — a background process with no controlling terminal, whose
stderr may already point at /dev/null (when noclose == false). The
launching shell, meanwhile, has already observed exit code 0. In other
words, only a failure of the first fork() is reportable to the caller;
later failures cannot be surfaced to the original process. If you need the
launcher to confirm the daemon actually started, implement a readiness
handshake (e.g. a pipe the parent reads before exiting) rather than relying
on this return value. See examples/checked_daemon_pattern.rs for a
low-level pattern built from this crate’s primitives.
use fork::{daemon, Fork};
// Recommended: use `if let` — no dead Parent arm needed
if let Ok(Fork::Child) = daemon(false, false) {
// Only the daemon reaches here
loop {
// daemon work…
std::thread::sleep(std::time::Duration::from_secs(60));
}
}If you prefer match for explicit error handling, mark the parent arm
unreachable:
use fork::{daemon, Fork};
match daemon(false, false) {
Ok(Fork::Child) => {
// daemon work…
}
Ok(Fork::Parent(_)) => unreachable!("daemon() exits both parent processes"),
Err(err) => eprintln!("daemon failed: {err}"),
}§Implementation (double-fork)
- First fork — Parent calls
_exit(0)immediately. - Session setup — Child calls
setsid(), optionallychdir("/"), and optionally redirects stdio. - Second (double) fork — Session-leader child calls
_exit(0)immediately. - Daemon continues — Grandchild (daemon) runs with no controlling terminal.
§Behavior Change in v0.4.0
Previously, noclose = false would close stdio file descriptors.
Now it redirects them to /dev/null instead, which is safer and prevents
file descriptor reuse bugs. This matches industry standard implementations
(libuv, systemd, BSD daemon(3)).
§Errors
Returns an io::Error if any of the underlying system calls fail:
- fork fails (e.g., resource limits)
- setsid fails (e.g., already a session leader)
- chdir fails (when
nochdiris false) redirect_stdiofails (whennocloseis false)
Example:
use fork::{daemon, Fork};
use std::process::Command;
if let Ok(Fork::Child) = daemon(false, false) {
Command::new("sleep")
.arg("3")
.output()
.expect("failed to execute process");
}