Skip to main content

Crate fork

Crate fork 

Source
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 than close_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§

DaemonCleanup
Owned daemon work that could not be confirmed clean before returning.
DaemonNotifier
Startup token held by the detached daemon until initialization finishes.
DaemonOptions
Fully materialized daemon detachment settings.
DaemonProcess
The detached daemon observed by the original invoking process.
InvalidProcessGroupId
A raw value was not a valid positive process-group identifier.
InvalidProcessId
A raw value was not a valid positive process identifier.
InvalidSignal
A raw signal number was zero or negative.
Pipe
The owned endpoints of a unidirectional pipe.
PreparedCommand
A command whose child-visible data is materialized before fork.
ProcessCredentials
Numeric credentials materialized before fork.
ProcessGroupGuard
An owned fail-closed capability for one reserved process group.
ProcessGroupId
A checked positive Unix process-group identifier.
ProcessId
A checked positive Unix process identifier.
Signal
A checked nonzero Unix signal number.
SocketPair
The owned endpoints of a bidirectional Unix socket pair.
SpawnedChild
A successfully executed direct child of the process broker.

Enums§

CheckedDaemon
Which side returned from checked_daemon.
ChildEvent
One observed state change for a direct child process.
ChildSignalState
Signal-mask and disposition behavior for a new child context.
DaemonError
Failure to detach or initialize a checked daemon.
DaemonStage
One reviewed stage in checked daemon startup.
Fork
Fork result
ProcessFork
Fork result with a checked process identifier in the parent.
ProcessGroup
Process-group setup requested for a spawned program.
SpawnError
Failure to fork, prepare, or execute a child process.
SpawnStage
A reviewed child-side operation that can fail before execve.
SupplementaryGroups
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 process the 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 process into 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 signal to exactly one process.
signal_process_group
Deliver signal to 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)