fork
Library for creating a new process detached from the controlling terminal (daemon) on Unix-like systems.
Features
- ✅ Minimal - Small, focused library for process forking and daemonization
- ✅ Safe - Comprehensive test coverage across all APIs and edge cases
- ✅ Well-documented - Extensive documentation with real-world examples
- ✅ Unix-first - Built specifically for Unix-like systems (Linux, macOS, BSD)
- ✅ Edition 2024 - Uses latest Rust edition features
Why?
- Minimal library to daemonize, fork, double-fork a process
- daemon(3) has been
deprecated in macOS 10.5. By using
forkandsetsidsyscalls, new methods can be created to achieve the same goal - Provides the building blocks for creating proper Unix daemons
Installation
Add fork to your Cargo.toml:
[]
= "0.8.0"
Or use cargo-add:
Quick Start
Basic Daemon Example
use ;
use Command;
Simple Fork Example
use ;
match fork
Error Handling with Rich Diagnostics
use ;
match fork
API Overview
Main Functions
fork()- Creates a new child processdaemon(nochdir, noclose)- Creates a daemon using double-fork patternnochdir: iffalse, changes working directory to/noclose: iffalse, redirects stdin/stdout/stderr to/dev/null
setsid()- Creates a new session and sets the process group IDwaitpid(pid)- Waits for child process to change state (blocking; returns raw status; retries on signals)waitpid_nohang(pid)- Checks child status without blocking (returnsOption<status>; for supervisors/polling)wait_any()- Waits for any child process and returns(pid, status)wait_any_nohang()- Checks any child without blocking and returnsOption<(pid, status)>getpgrp()- Returns the process group IDgetpid()- Returns the current process IDgetppid()- Returns the parent process IDchdir()- Changes current directory to/redirect_stdio()- Redirects stdin/stdout/stderr to/dev/null(recommended)close_fd()- Closes stdin, stdout, and stderr (legacy, useredirect_stdio()instead)
Status Inspection Macros (re-exported from libc)
WIFEXITED(status)- Check if child exited normallyWEXITSTATUS(status)- Get exit code (if exited normally)WIFSIGNALED(status)- Check if child was terminated by signalWTERMSIG(status)- Get terminating signal (if signaled)
See the documentation for detailed usage.
Process Tree Example
When using daemon(false, false), it will change directory to / and redirect stdin/stdout/stderr to /dev/null.
This matters for daemon startup code. Use absolute paths for PID files, logs,
sockets, and config files: after chdir("/"), File::create("myapp.pid")
tries to create /myapp.pid, not a file in the launch directory. With
noclose = false, any println!, eprintln!, or panic output from that
failure goes to /dev/null, which can make it look like the daemon block never
ran. Use daemon(true, false) if relative paths should stay relative to the
launch directory, and use noclose = true or a readiness pipe while debugging
startup failures.
Test running:
Use ps to check the process:
|
Output:
PPID PID PGID SESS TTY TPGID STAT UID %MEM %CPU COMMAND
1 48738 48737 0 ?? 0 S 501 0.0 0.0 target/debug/myapp
48738 48753 48737 0 ?? 0 S 501 0.0 0.0 sleep 300
Key points:
PPID == 1- Parent is init/systemd (orphaned process)TTY = ??- No controlling terminal- New
PGID = 48737- Own process group
Process hierarchy:
1 - root (init/systemd)
└── 48738 myapp PGID - 48737
└── 48753 sleep PGID - 48737
Double-Fork Daemon Pattern
The daemon() function implements the classic double-fork pattern:
- First fork - Creates child process
- setsid() - Child becomes session leader
- Second fork - Grandchild is created (not a session leader)
- First child exits - Leaves grandchild orphaned
- Grandchild continues - As daemon (no controlling terminal)
This prevents the daemon from ever acquiring a controlling terminal.
Checked Startup
daemon() intentionally follows the classic daemon pattern: after the first
fork succeeds, the original parent exits before later setup steps run. If the
launcher must observe setup success or failure, build a readiness handshake
around the primitives in this crate. The usual pattern is to create a pipe
before forking, keep the original parent blocked on the read end, and have the
daemon child write success or an errno-style failure before the parent exits.
See examples/checked_daemon_pattern.rs for a complete low-level example using
fork(), setsid(), chdir(), redirect_stdio(), and a pre-fork pipe.
Safety Notes
daemon()uses_exitin the forked parents to avoid running non-async-signal-safe destructors between fork/exec (POSIX-safe on Linux/macOS/BSD).redirect_stdio()retriesopen()anddup2()onEINTR;close_fd()callsclose()once and treatsEINTRas success to avoid closing a reused fd.- Prefer
redirect_stdio()overclose_fd()so file descriptors 0,1,2 stay occupied (avoids accidental log/data corruption). - For supervisors,
HashMapis appropriate: use raw PIDs orFork::Parent(pid)as live child handles that are removed after reaping. Use your own monotonic id for durable state across restarts/history because operating systems reuse PIDs.
Testing
Run tests:
See tests/README.md for detailed information about integration tests.
Platform Support
This library is designed for Unix-like operating systems:
- ✅ Linux
- ✅ macOS
- ✅ FreeBSD
- ✅ NetBSD
- ✅ OpenBSD
- ❌ Windows (not supported)
Documentation
Examples
See the examples/ directory for more usage examples:
example_daemon.rs- Daemon creationchecked_daemon_pattern.rs- Checked startup pattern with a pre-fork pipeexample_pipe.rs- Fork with pipe communicationexample_touch_pid.rs- PID file creation
Run an example:
Contributing
Contributions are welcome! Please ensure:
- All tests pass:
cargo test - Code is formatted:
cargo fmt - No clippy warnings:
cargo clippy -- -D warnings - Documentation is updated
License
BSD 3-Clause License - see LICENSE file for details.