Expand description
A correct, full-featured Unix daemon library for Rust.
A blivet is the “impossible fork” optical illusion, also known as the devil’s tuning fork. Daemons are created by forking — and this crate performs the impossible double-fork to do it correctly.
This crate provides a library and CLI tool for daemonizing processes on Unix
systems. It performs a mandatory double-fork, resets signal dispositions and
mask, and uses a notification pipe so the parent can wait for daemon
readiness. Privilege dropping is split-phase: daemonize() returns a
context while still privileged, and the caller explicitly calls
drop_privileges() when ready.
§Example
use blivet::{DaemonConfig, daemonize};
let mut config = DaemonConfig::new();
config.pidfile("/var/run/foo.pid").chdir("/tmp");
let mut ctx = unsafe { daemonize(&config)? };
// ... application initialization ...
ctx.notify_parent()?;
// daemon process continues here§Split-phase design
Many daemons need root privileges during startup — binding to a
privileged port, writing a pidfile to /var/run, opening log files
owned by root — but should run as an unprivileged user afterward.
Rather than coupling privilege dropping into the daemonization call
(which would force callers to choose between “drop before init” and
“never drop at all”), daemonize() returns a DaemonContext while
the process is still running as the original user. The caller
performs any privileged work, then explicitly calls
chown_paths() and
drop_privileges() when ready.
Finally, notify_parent() signals
the original parent that the daemon is up, allowing the parent to
exit with a meaningful status.
This split gives full control over ordering:
- Privileged init — bind sockets, open devices, call
chroot, set resource limits, or acquire any other resources that require elevated permissions. - Ownership transfer —
chown_paths()hands pidfile, lockfile, and log files to the target user/group while still root. - Privilege drop —
drop_privileges()callsinitgroups,setgid, andsetuid. After this point the process runs as the configured unprivileged user. - Readiness signal —
notify_parent()writes a success byte to the notification pipe; the parent reads it and exits 0.
use blivet::{DaemonConfig, daemonize};
let mut config = DaemonConfig::new();
config.pidfile("/var/run/foo.pid").user("nobody").group("nogroup");
let mut ctx = unsafe { daemonize(&config)? };
// 1. Privileged work while still root:
// bind sockets, chroot, set resource limits, etc.
let _listener = std::net::TcpListener::bind("0.0.0.0:80")?;
// 2–3. Transfer file ownership, then drop to unprivileged user
ctx.chown_paths()?;
ctx.drop_privileges()?;
// 4. Tell the parent we're ready
ctx.notify_parent()?;
// Daemon continues as "nobody" with the socket still openStructs§
- Daemon
Config - Configuration for the daemonization process.
- Daemon
Context - Context returned by a successful daemonization.
Enums§
- Daemonize
Error - Errors produced during configuration validation or the daemonization sequence.
Functions§
- daemonize⚠
- Daemonize the current process.
- daemonize_
checked - Safe wrapper for
daemonizethat verifies the process is single-threaded.