Expand description
Library for creating a new process detached from the controlling terminal (daemon).
§Quick Start
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");
}§Common Patterns
§Process Supervisor
Track multiple worker processes by durable worker id, with a PID lookup for wait results:
use fork::{fork, wait_any_nohang, Fork, WIFEXITED};
use std::collections::HashMap;
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct WorkerId(u64);
struct Worker {
id: WorkerId,
pid: libc::pid_t,
name: String,
}
let mut workers = HashMap::new();
let mut by_pid = HashMap::new();
// Spawn 3 workers
for i in 0..3 {
let id = WorkerId(i);
match fork()? {
Fork::Parent(pid) => {
workers.insert(
id,
Worker {
id,
pid,
name: format!("worker-{}", i),
},
);
by_pid.insert(pid, id);
}
Fork::Child => {
// Do work...
std::thread::sleep(std::time::Duration::from_secs(5));
std::process::exit(0);
}
}
}
// Monitor workers without blocking
while !workers.is_empty() {
loop {
match wait_any_nohang()? {
Some((pid, status)) => {
if let Some(id) = by_pid.remove(&pid) {
let worker = workers.remove(&id).expect("pid map points to worker");
if WIFEXITED(status) {
println!(
"{} (id {}, pid {}) exited",
worker.name, worker.id.0, worker.pid
);
}
}
if workers.is_empty() {
break;
}
}
None => break,
}
}
std::thread::sleep(std::time::Duration::from_millis(100));
}§Inter-Process Communication (IPC) via Pipe
use fork::{fork, Fork};
use std::io::{Read, Write};
use std::os::unix::io::FromRawFd;
// Create pipe before forking
let mut pipe_fds = [0i32; 2];
unsafe { libc::pipe(pipe_fds.as_mut_ptr()) };
match fork()? {
Fork::Parent(_child) => {
unsafe { libc::close(pipe_fds[1]) }; // Close write end
let mut reader = unsafe { std::fs::File::from_raw_fd(pipe_fds[0]) };
let mut msg = String::new();
reader.read_to_string(&mut msg)?;
println!("Received: {}", msg);
}
Fork::Child => {
unsafe { libc::close(pipe_fds[0]) }; // Close read end
let mut writer = unsafe { std::fs::File::from_raw_fd(pipe_fds[1]) };
writer.write_all(b"Hello from child!")?;
std::process::exit(0);
}
}§Daemon with PID File
use fork::{daemon, Fork, getpid};
use std::fs::File;
use std::io::Write;
if let Ok(Fork::Child) = daemon(false, false) {
// Write PID file
let pid = getpid();
// Use an absolute path: daemon(false, false) changes cwd to `/`.
let mut file = File::create("/var/run/myapp.pid")?;
writeln!(file, "{}", pid)?;
// Run daemon logic...
loop {
// Do work
std::thread::sleep(std::time::Duration::from_secs(60));
}
}§Process Broker with Typed Events (Safe Supervisor)
The safe broker API (PreparedCommand and wait_any_event) ensures that file descriptors are managed securely via close-on-exec, and provides rich typed events (ChildEvent) that clearly distinguish between termination and suspension without confusing integer logic.
use fork::{PreparedCommand, wait_any_event, ChildEvent, ProcessGroup};
// 1. Prepare a command that runs in its own process group
let mut cmd = PreparedCommand::new("/bin/sleep")?;
cmd.arg("3")?;
cmd.process_group(ProcessGroup::New);
// 2. Spawn the child (this ensures all strings, arrays, and descriptors
// are materialized safely before the fork)
match cmd.spawn(std::time::Duration::from_secs(3)) {
Ok(child) => {
println!("Spawned child with PID: {}", child.process().get());
// 3. Monitor using typed events
loop {
// Block until an event occurs
match wait_any_event() {
Ok(event) => {
match event {
ChildEvent::Exited { pid, code } => {
println!("PID {} exited with code {}", pid.get(), code);
break;
}
ChildEvent::Signalled { pid, signal } => {
println!("PID {} terminated by signal {}", pid.get(), signal);
break;
}
ChildEvent::Stopped { pid, signal } => {
println!("PID {} was stopped by {}", pid.get(), signal);
}
ChildEvent::Continued { pid } => {
println!("PID {} continued", pid.get());
}
}
}
Err(e) => {
eprintln!("Wait failed: {}", e);
break;
}
}
}
}
Err(e) => eprintln!("Failed to spawn child: {}", e),
}§Checked Daemon Pattern
Unlike a traditional “fire and forget” double-fork, the checked_daemon pattern allows the intermediate process to wait until the daemon has fully initialized its resources before returning success to the original caller.
use fork::{checked_daemon, DaemonOptions};
use std::time::Duration;
// Set a timeout to prevent the original caller from hanging indefinitely
// if the daemon gets stuck during initialization.
let timeout = Duration::from_secs(5);
match checked_daemon(DaemonOptions::new(), timeout) {
Ok(fork::CheckedDaemon::Parent(_)) => {
// The original invoker returns successfully ONLY after the daemon
// invokes `notifier.notify_ready()` below.
println!("Daemon started and initialized successfully.");
}
Ok(fork::CheckedDaemon::Daemon(notifier)) => {
// We are now the detached daemon process (session leader).
// Perform initialization (bind ports, allocate memory, etc.)
let initialized = true;
if initialized {
// Notify the parent that we've started successfully
let _ = notifier.notify_ready();
// Run background service loop...
loop {
std::thread::sleep(Duration::from_secs(60));
}
} else {
// If initialization fails, bubble the error back to the parent and exit
notifier.fail_and_exit(&std::io::Error::last_os_error());
}
}
Err(e) => {
eprintln!("Failed to launch daemon: {}", e);
}
}§Safety and Best Practices
- Always check fork result - Functions marked
#[must_use]prevent accidents - Use
waitpid()- Reap child processes to avoid zombies - Prefer
redirect_stdio()- Safer thanclose_fd()for daemons - Fork early - Before creating threads, locks, or complex state
- Close unused file descriptors - Prevent resource leaks in children
- Use durable supervisor ids - Treat PIDs as live process handles, not historical identity, because operating systems reuse PIDs after reaping
- Handle signals properly - Consider what happens in both processes
§Platform Compatibility
This library uses POSIX system calls and is designed for Unix-like systems:
- Linux (all distributions)
- macOS (10.5+, replacement for deprecated
daemon(3)) - FreeBSD, OpenBSD, NetBSD
- Other POSIX-compliant systems
Windows is not supported as it lacks fork() system call.
Structs§
- Daemon
Cleanup - Owned daemon work that could not be confirmed clean before returning.
- Daemon
Notifier - Startup token held by the detached daemon until initialization finishes.
- Daemon
Options - Fully materialized daemon detachment settings.
- Daemon
Process - The detached daemon observed by the original invoking process.
- Invalid
Process Group Id - A raw value was not a valid positive process-group identifier.
- Invalid
Process Id - A raw value was not a valid positive process identifier.
- Invalid
Signal - A raw signal number was zero or negative.
- Pipe
- The owned endpoints of a unidirectional pipe.
- Prepared
Command - A command whose child-visible data is materialized before
fork. - Process
Credentials - Numeric credentials materialized before
fork. - Process
Group Guard - An owned fail-closed capability for one reserved process group.
- Process
Group Id - A checked positive Unix process-group identifier.
- Process
Id - A checked positive Unix process identifier.
- Signal
- A checked nonzero Unix signal number.
- Socket
Pair - The owned endpoints of a bidirectional Unix socket pair.
- Spawned
Child - A successfully executed direct child of the process broker.
Enums§
- Checked
Daemon - Which side returned from
checked_daemon. - Child
Event - One observed state change for a direct child process.
- Child
Signal State - Signal-mask and disposition behavior for a new child context.
- Daemon
Error - Failure to detach or initialize a checked daemon.
- Daemon
Stage - One reviewed stage in checked daemon startup.
- Fork
- Fork result
- Process
Fork - Fork result with a checked process identifier in the parent.
- Process
Group - Process-group setup requested for a spawned program.
- Spawn
Error - Failure to fork, prepare, or execute a child process.
- Spawn
Stage - A reviewed child-side operation that can fail before
execve. - Supplementary
Groups - Supplementary-group behavior for a child identity transition.
Functions§
- WEXITSTATUS
- WIFEXITED
- WIFSIGNALED
- WTERMSIG
- acquire_
subreaper - Register the current process as a subreaper.
- chdir
- Change dir to
/see chdir(2) - checked_
daemon - Detach with a double fork and wait for an explicit bounded readiness result.
- close_
fd - Close file descriptors stdin, stdout, stderr
- create_
current_ process_ group - Make the calling process the leader of a new process group.
- create_
process_ group - Make
processthe leader of a new process group with the same numeric ID. - current_
process_ group_ id - Return the calling process’s checked process-group identifier.
- current_
process_ id - Return the calling process’s checked identifier.
- daemon
- The daemon function is for programs wishing to detach themselves from the controlling terminal and run in the background as system daemons.
- fork
- Create a new child process see fork(2)
- fork_
process - Create a child and return a checked process identifier to the parent.
- getpgrp
- Get the process group ID of the current process see getpgrp(2)
- getpid
- Get the current process ID see getpid(2)
- getppid
- Get the parent process ID see getppid(2)
- is_
subreaper - Report whether the current process explicitly acquired the subreaper role.
- join_
process_ group - Move
processinto an existing process group. - pipe_
cloexec - Create an owned pipe whose endpoints are closed by
exec. - process_
group - Return the current process group of
process. - redirect_
stdio - Redirect stdin, stdout, stderr to /dev/null
- release_
subreaper - Relinquish the current process’s explicitly acquired subreaper role.
- setsid
- Create session and set process group ID see setsid(2)
- signal_
process - Deliver
signalto exactly one process. - signal_
process_ group - Deliver
signalto every member of one process group. - socket_
pair_ cloexec - Create an owned bidirectional Unix socket pair closed by
exec. - wait_
any - Wait for any child process to terminate see wait(2)
- wait_
any_ event - Wait for a state change from any direct child.
- wait_
any_ event_ nohang - Poll every direct child for one state change without blocking.
- wait_
any_ nohang - Wait for any child process to terminate without blocking see wait(2)
- wait_
event - Wait for a state change from one direct child.
- wait_
event_ nohang - Poll one direct child for a state change without blocking.
- waitpid
- Wait for process to change status see wait(2)
- waitpid_
nohang - Wait for process to change status without blocking see wait(2)