Skip to main content

Crate blivet

Crate blivet 

Source
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:

  1. Privileged init — bind sockets, open devices, call chroot, set resource limits, or acquire any other resources that require elevated permissions.
  2. Ownership transferchown_paths() hands pidfile, lockfile, and log files to the target user/group while still root.
  3. Privilege dropdrop_privileges() calls initgroups, setgid, and setuid. After this point the process runs as the configured unprivileged user.
  4. Readiness signalnotify_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 open

Structs§

DaemonConfig
Configuration for the daemonization process.
DaemonContext
Context returned by a successful daemonization.

Enums§

DaemonizeError
Errors produced during configuration validation or the daemonization sequence.

Functions§

daemonize
Daemonize the current process.
daemonize_checked
Safe wrapper for daemonize that verifies the process is single-threaded.