Skip to main content

daemon

Function daemon 

Source
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. Call unsafe { 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/sigprocmask if 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)

  1. First fork — Parent calls _exit(0) immediately.
  2. Session setup — Child calls setsid(), optionally chdir("/"), and optionally redirects stdio.
  3. Second (double) fork — Session-leader child calls _exit(0) immediately.
  4. 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 nochdir is false)
  • redirect_stdio fails (when noclose is 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");
}