fork/lib.rs
1//! Library for creating a new process detached from the controlling terminal (daemon).
2//!
3//! # Quick Start
4//!
5//! ```
6//! use fork::{daemon, Fork};
7//! use std::process::Command;
8//!
9//! if let Ok(Fork::Child) = daemon(false, false) {
10//! Command::new("sleep")
11//! .arg("3")
12//! .output()
13//! .expect("failed to execute process");
14//! }
15//! ```
16//!
17//! # Common Patterns
18//!
19//! ## Process Supervisor
20//!
21//! Track multiple worker processes:
22//!
23//! ```no_run
24//! use fork::{fork, Fork, waitpid_nohang, WIFEXITED};
25//! use std::collections::HashMap;
26//!
27//! # fn main() -> std::io::Result<()> {
28//! let mut workers = HashMap::new();
29//!
30//! // Spawn 3 workers
31//! for i in 0..3 {
32//! match fork()? {
33//! result @ Fork::Parent(_) => {
34//! workers.insert(result, format!("worker-{}", i));
35//! }
36//! Fork::Child => {
37//! // Do work...
38//! std::thread::sleep(std::time::Duration::from_secs(5));
39//! std::process::exit(0);
40//! }
41//! }
42//! }
43//!
44//! // Monitor workers without blocking
45//! while !workers.is_empty() {
46//! workers.retain(|child, name| {
47//! match waitpid_nohang(child.child_pid().unwrap()) {
48//! Ok(Some(status)) if WIFEXITED(status) => {
49//! println!("{} exited", name);
50//! false // Remove from map
51//! }
52//! _ => true // Keep in map
53//! }
54//! });
55//! std::thread::sleep(std::time::Duration::from_millis(100));
56//! }
57//! # Ok(())
58//! # }
59//! ```
60//!
61//! ## Inter-Process Communication (IPC) via Pipe
62//!
63//! ```no_run
64//! use fork::{fork, Fork};
65//! use std::io::{Read, Write};
66//! use std::os::unix::io::FromRawFd;
67//!
68//! # fn main() -> std::io::Result<()> {
69//! // Create pipe before forking
70//! let mut pipe_fds = [0i32; 2];
71//! unsafe { libc::pipe(pipe_fds.as_mut_ptr()) };
72//!
73//! match fork()? {
74//! Fork::Parent(_child) => {
75//! unsafe { libc::close(pipe_fds[1]) }; // Close write end
76//!
77//! let mut reader = unsafe { std::fs::File::from_raw_fd(pipe_fds[0]) };
78//! let mut msg = String::new();
79//! reader.read_to_string(&mut msg)?;
80//! println!("Received: {}", msg);
81//! }
82//! Fork::Child => {
83//! unsafe { libc::close(pipe_fds[0]) }; // Close read end
84//!
85//! let mut writer = unsafe { std::fs::File::from_raw_fd(pipe_fds[1]) };
86//! writer.write_all(b"Hello from child!")?;
87//! std::process::exit(0);
88//! }
89//! }
90//! # Ok(())
91//! # }
92//! ```
93//!
94//! ## Daemon with PID File
95//!
96//! ```no_run
97//! use fork::{daemon, Fork, getpid};
98//! use std::fs::File;
99//! use std::io::Write;
100//!
101//! # fn main() -> std::io::Result<()> {
102//! if let Ok(Fork::Child) = daemon(false, false) {
103//! // Write PID file
104//! let pid = getpid();
105//! let mut file = File::create("/var/run/myapp.pid")?;
106//! writeln!(file, "{}", pid)?;
107//!
108//! // Run daemon logic...
109//! loop {
110//! // Do work
111//! std::thread::sleep(std::time::Duration::from_secs(60));
112//! }
113//! }
114//! # Ok(())
115//! # }
116//! ```
117//!
118//! # Safety and Best Practices
119//!
120//! - **Always check fork result** - Functions marked `#[must_use]` prevent accidents
121//! - **Use `waitpid()`** - Reap child processes to avoid zombies
122//! - **Prefer `redirect_stdio()`** - Safer than `close_fd()` for daemons
123//! - **Fork early** - Before creating threads, locks, or complex state
124//! - **Close unused file descriptors** - Prevent resource leaks in children
125//! - **Handle signals properly** - Consider what happens in both processes
126//!
127//! # Platform Compatibility
128//!
129//! This library uses POSIX system calls and is designed for Unix-like systems:
130//! - Linux (all distributions)
131//! - macOS (10.5+, replacement for deprecated `daemon(3)`)
132//! - FreeBSD, OpenBSD, NetBSD
133//! - Other POSIX-compliant systems
134//!
135//! Windows is **not supported** as it lacks `fork()` system call.
136
137use std::io;
138
139// Re-export libc status inspection macros for convenience
140// This allows users to write `use fork::{waitpid, WIFEXITED, WEXITSTATUS}`
141// instead of importing from libc separately
142pub use libc::{WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG};
143
144/// Fork result
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
146pub enum Fork {
147 Parent(libc::pid_t),
148 Child,
149}
150
151/// Close a file descriptor without retrying on `EINTR`, treating `EBADF` as success.
152///
153/// On Linux, `close()` always releases the fd before returning `EINTR`, so retrying
154/// would risk closing an unrelated fd opened by another thread. On FreeBSD, macOS,
155/// and other Unixes the fd state after `EINTR` is unspecified (POSIX 2008+, Austin
156/// Group defect 529), making retry equally unsafe. The safe portable behavior is to
157/// call `close()` exactly once and treat `EINTR` as success — the same approach used
158/// by Rust's stdlib, Go's runtime, and glibc internals.
159#[inline]
160fn close_once(fd: libc::c_int) -> io::Result<()> {
161 let res = unsafe { libc::close(fd) };
162 if res == 0 {
163 return Ok(());
164 }
165
166 let err = io::Error::last_os_error();
167
168 if err.kind() == io::ErrorKind::Interrupted || err.raw_os_error() == Some(libc::EBADF) {
169 return Ok(());
170 }
171
172 Err(err)
173}
174
175impl Fork {
176 /// Returns `true` if this is the parent process
177 ///
178 /// # Examples
179 ///
180 /// ```
181 /// use fork::{fork, Fork};
182 ///
183 /// match fork() {
184 /// Ok(result) => {
185 /// if result.is_parent() {
186 /// println!("I'm the parent");
187 /// }
188 /// }
189 /// Err(_) => {}
190 /// }
191 /// ```
192 #[must_use]
193 #[inline]
194 pub const fn is_parent(&self) -> bool {
195 matches!(self, Self::Parent(_))
196 }
197
198 /// Returns `true` if this is the child process
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use fork::{fork, Fork};
204 ///
205 /// match fork() {
206 /// Ok(result) => {
207 /// if result.is_child() {
208 /// println!("I'm the child");
209 /// std::process::exit(0);
210 /// }
211 /// }
212 /// Err(_) => {}
213 /// }
214 /// ```
215 #[must_use]
216 #[inline]
217 pub const fn is_child(&self) -> bool {
218 matches!(self, Self::Child)
219 }
220
221 /// Returns the child PID if this is the parent, otherwise `None`
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use fork::{fork, Fork};
227 ///
228 /// match fork() {
229 /// Ok(result) => {
230 /// if let Some(child_pid) = result.child_pid() {
231 /// println!("Child PID: {}", child_pid);
232 /// }
233 /// }
234 /// Err(_) => {}
235 /// }
236 /// ```
237 #[must_use]
238 #[inline]
239 pub const fn child_pid(&self) -> Option<libc::pid_t> {
240 match self {
241 Self::Parent(pid) => Some(*pid),
242 Self::Child => None,
243 }
244 }
245}
246
247/// Change dir to `/` [see chdir(2)](https://www.freebsd.org/cgi/man.cgi?query=chdir&sektion=2)
248///
249/// Upon successful completion, the current working directory is changed to `/`.
250/// Otherwise, an error is returned with the system error code.
251///
252/// Example:
253///
254///```
255///use fork::chdir;
256///use std::env;
257///
258///match chdir() {
259/// Ok(_) => {
260/// let path = env::current_dir().expect("failed current_dir");
261/// assert_eq!(Some("/"), path.to_str());
262/// }
263/// Err(e) => eprintln!("Failed to change directory: {}", e),
264///}
265///```
266///
267/// # Errors
268/// Returns an [`io::Error`] if the system call fails. Common errors include:
269/// - Permission denied
270/// - Path does not exist
271///
272#[inline]
273pub fn chdir() -> io::Result<()> {
274 // SAFETY: c"/" is a valid null-terminated C string literal
275 let res = unsafe { libc::chdir(c"/".as_ptr()) };
276
277 match res {
278 -1 => Err(io::Error::last_os_error()),
279 _ => Ok(()),
280 }
281}
282
283/// Close file descriptors stdin, stdout, stderr
284///
285/// **Warning:** This function closes the file descriptors, making them
286/// available for reuse. If your daemon opens files after calling this,
287/// those files may get fd 0, 1, or 2, causing `println!`, `eprintln!`,
288/// or panic output to corrupt them.
289///
290/// **Use [`redirect_stdio()`] instead**, which is safer and follows
291/// industry best practices by redirecting stdio to `/dev/null` instead
292/// of closing. This keeps fd 0, 1, 2 occupied, ensuring subsequent files
293/// get fd >= 3, preventing silent corruption.
294///
295/// # Errors
296/// Returns an [`io::Error`] if any of the file descriptors fail to close.
297/// Already-closed descriptors (`EBADF`) are treated as success so the
298/// function is idempotent.
299///
300/// # Example
301///
302/// ```no_run
303/// use fork::close_fd;
304///
305/// // Warning: Files opened after this may get fd 0,1,2!
306/// close_fd()?;
307/// # Ok::<(), std::io::Error>(())
308/// ```
309pub fn close_fd() -> io::Result<()> {
310 for fd in 0..=2 {
311 close_once(fd)?;
312 }
313
314 Ok(())
315}
316
317/// Redirect stdin, stdout, stderr to /dev/null
318///
319/// This is the recommended way to detach from the controlling terminal
320/// in daemon processes. Unlike [`close_fd()`], this keeps file descriptors
321/// 0, 1, 2 occupied (pointing to /dev/null), preventing them from being
322/// reused by subsequent `open()` calls.
323///
324/// This prevents bugs where `println!`, `eprintln!`, or panic output
325/// accidentally writes to data files that happened to get assigned fd 0, 1, or 2.
326///
327/// # Implementation
328///
329/// This function:
330/// 1. Opens `/dev/null` with `O_RDWR`
331/// 2. Uses `dup2()` to redirect fds 0, 1, 2 to `/dev/null`
332/// 3. Closes the extra file descriptor if it was > 2
333///
334/// This is the same approach used by libuv, systemd, and BSD `daemon(3)`.
335///
336/// # Errors
337///
338/// Returns an [`io::Error`] if:
339/// - `/dev/null` cannot be opened
340/// - `dup2()` fails to redirect any of the file descriptors
341///
342/// # Example
343///
344/// ```no_run
345/// use fork::redirect_stdio;
346/// use std::fs::File;
347///
348/// redirect_stdio()?;
349///
350/// // Now safe: files will get fd >= 3
351/// let log = File::create("app.log")?;
352///
353/// // This goes to /dev/null (safely discarded), not to app.log
354/// println!("debug message");
355/// # Ok::<(), std::io::Error>(())
356/// ```
357pub fn redirect_stdio() -> io::Result<()> {
358 let null_fd = loop {
359 let fd = unsafe { libc::open(c"/dev/null".as_ptr(), libc::O_RDWR) };
360 if fd == -1 {
361 let err = io::Error::last_os_error();
362 if err.kind() == io::ErrorKind::Interrupted {
363 continue;
364 }
365 return Err(err);
366 }
367 break fd;
368 };
369
370 // Redirect stdin, stdout, stderr to /dev/null
371 for fd in 0..=2 {
372 loop {
373 if unsafe { libc::dup2(null_fd, fd) } == -1 {
374 let err = io::Error::last_os_error();
375 if err.kind() == io::ErrorKind::Interrupted {
376 continue;
377 }
378 // Only close null_fd if it's > 2 (not one of the stdio fds we're duplicating to)
379 // If null_fd was 0, 1, or 2, we're in the process of duping it, so don't close
380 if null_fd > 2 {
381 let _ = close_once(null_fd);
382 }
383 return Err(err);
384 }
385 break;
386 }
387 }
388
389 // Close the extra fd if it's > 2
390 // (if null_fd was 0, 1, or 2, it's now dup'd to all three, so don't close)
391 if null_fd > 2 {
392 close_once(null_fd)?;
393 }
394
395 Ok(())
396}
397
398/// Create a new child process [see fork(2)](https://www.freebsd.org/cgi/man.cgi?fork)
399///
400/// Upon successful completion, `fork()` returns [`Fork::Child`] in the child process
401/// and `Fork::Parent(pid)` with the child's process ID in the parent process.
402///
403/// Example:
404///
405/// ```
406///use fork::{fork, Fork};
407///
408///match fork() {
409/// Ok(Fork::Parent(child)) => {
410/// println!("Continuing execution in parent process, new child has pid: {}", child);
411/// }
412/// Ok(Fork::Child) => println!("I'm a new child process"),
413/// Err(e) => eprintln!("Fork failed: {}", e),
414///}
415///```
416/// This will print something like the following (order indeterministic).
417///
418/// ```text
419/// Continuing execution in parent process, new child has pid: 1234
420/// I'm a new child process
421/// ```
422///
423/// The thing to note is that you end up with two processes continuing execution
424/// immediately after the fork call but with different match arms.
425///
426/// # Safety Considerations
427///
428/// After calling `fork()`, the child process is an exact copy of the parent process.
429/// However, there are important safety considerations:
430///
431/// - **File Descriptors**: Inherited from parent but share the same file offset and status flags.
432/// Changes in one process affect the other.
433/// - **Mutexes and Locks**: May be in an inconsistent state in the child. Only the thread that
434/// called `fork()` exists in the child; other threads disappear mid-execution, potentially
435/// leaving mutexes locked.
436/// - **Async-Signal-Safety**: Between `fork()` and `exec()`, only async-signal-safe functions
437/// should be called. This includes most system calls but excludes most library functions,
438/// memory allocation, and I/O operations.
439/// - **Signal Handlers**: Inherited from parent but should be used carefully in multi-threaded programs.
440/// - **Memory**: Child gets a copy-on-write copy of parent's memory. Large memory usage can impact performance.
441///
442/// For detailed information, see the [fork(2) man page](https://man7.org/linux/man-pages/man2/fork.2.html).
443///
444/// # [`nix::unistd::fork`](https://docs.rs/nix/0.15.0/nix/unistd/fn.fork.html)
445///
446/// The example has been taken from the [`nix::unistd::fork`](https://docs.rs/nix/0.15.0/nix/unistd/fn.fork.html),
447/// please check the [Safety](https://docs.rs/nix/0.15.0/nix/unistd/fn.fork.html#safety) section
448///
449/// # Errors
450/// Returns an [`io::Error`] if the fork system call fails. Common errors include:
451/// - Resource temporarily unavailable (EAGAIN) - process limit reached
452/// - Out of memory (ENOMEM)
453#[must_use = "fork result must be checked to determine parent/child"]
454pub fn fork() -> io::Result<Fork> {
455 let res = unsafe { libc::fork() };
456 match res {
457 -1 => Err(io::Error::last_os_error()),
458 0 => Ok(Fork::Child),
459 res => Ok(Fork::Parent(res)),
460 }
461}
462
463/// Wait for process to change status [see wait(2)](https://man.freebsd.org/cgi/man.cgi?waitpid)
464///
465/// # Behavior
466/// - Retries automatically on `EINTR` (interrupted by signal)
467/// - Returns the raw status (use `libc::WIFEXITED`, `libc::WEXITSTATUS`, etc.)
468///
469/// # Errors
470/// Returns an [`io::Error`] if the waitpid system call fails. Common errors include:
471/// - No child process exists with the given PID
472/// - Invalid options or PID
473///
474/// Example:
475///
476/// ```
477///use fork::{waitpid, Fork};
478///
479///fn main() {
480/// match fork::fork() {
481/// Ok(Fork::Parent(pid)) => {
482/// println!("Child pid: {pid}");
483/// match waitpid(pid) {
484/// Ok(status) => println!("Child exited with status: {status}"),
485/// Err(e) => eprintln!("Failed to wait on child: {e}"),
486/// }
487/// }
488/// Ok(Fork::Child) => {
489/// // Child does trivial work then exits
490/// std::process::exit(0);
491/// }
492/// Err(e) => eprintln!("Failed to fork: {e}"),
493/// }
494///}
495///```
496pub fn waitpid(pid: libc::pid_t) -> io::Result<libc::c_int> {
497 let mut status: libc::c_int = 0;
498 loop {
499 // SAFETY: &raw mut status provides a raw pointer to initialized memory
500 let res = unsafe { libc::waitpid(pid, &raw mut status, 0) };
501
502 if res == -1 {
503 let err = io::Error::last_os_error();
504
505 if err.kind() == io::ErrorKind::Interrupted {
506 continue;
507 }
508
509 return Err(err);
510 }
511
512 return Ok(status);
513 }
514}
515
516/// Wait for process to change status without blocking [see wait(2)](https://man.freebsd.org/cgi/man.cgi?waitpid)
517///
518/// This is the non-blocking variant of [`waitpid()`]. It checks if the child has
519/// changed status and returns immediately without blocking.
520///
521/// # Return Value
522/// - `Ok(Some(status))` - Child has exited/stopped with the given status
523/// - `Ok(None)` - Child is still running (no state change)
524/// - `Err(...)` - Error occurred (e.g., ECHILD if child doesn't exist)
525///
526/// # Behavior
527/// - Returns immediately (does not block)
528/// - Retries automatically on `EINTR` (interrupted by signal)
529/// - Returns the raw status (use `libc::WIFEXITED`, `libc::WEXITSTATUS`, etc.)
530///
531/// # Use Cases
532/// - **Process supervisors** - Monitor multiple children without blocking
533/// - **Event loops** - Check child status while handling other events
534/// - **Polling patterns** - Parent has other work to do while child runs
535/// - **Non-blocking checks** - Determine if child is still running
536///
537/// # Example
538///
539/// ```
540/// use fork::{fork, Fork, waitpid_nohang};
541/// use std::time::Duration;
542///
543/// match fork::fork() {
544/// Ok(Fork::Parent(child)) => {
545/// // Do work while child runs
546/// for i in 0..5 {
547/// println!("Parent working... iteration {}", i);
548/// std::thread::sleep(Duration::from_millis(100));
549///
550/// match waitpid_nohang(child) {
551/// Ok(Some(status)) => {
552/// println!("Child exited with status: {}", status);
553/// break;
554/// }
555/// Ok(None) => {
556/// println!("Child still running...");
557/// }
558/// Err(e) => {
559/// eprintln!("Error checking child: {}", e);
560/// break;
561/// }
562/// }
563/// }
564/// }
565/// Ok(Fork::Child) => {
566/// // Child does work
567/// std::thread::sleep(Duration::from_millis(250));
568/// std::process::exit(0);
569/// }
570/// Err(e) => eprintln!("Fork failed: {}", e),
571/// }
572/// ```
573///
574/// # Errors
575/// Returns an [`io::Error`] if the waitpid system call fails. Common errors include:
576/// - No child process exists with the given PID (ECHILD)
577/// - Invalid options or PID
578pub fn waitpid_nohang(pid: libc::pid_t) -> io::Result<Option<libc::c_int>> {
579 let mut status: libc::c_int = 0;
580 loop {
581 // SAFETY: &raw mut status provides a raw pointer to initialized memory
582 let res = unsafe { libc::waitpid(pid, &raw mut status, libc::WNOHANG) };
583
584 if res == 0 {
585 // Child has not changed state (still running)
586 return Ok(None);
587 }
588
589 if res == -1 {
590 let err = io::Error::last_os_error();
591
592 if err.kind() == io::ErrorKind::Interrupted {
593 continue; // Retry on EINTR
594 }
595
596 return Err(err);
597 }
598
599 // Child changed state (exited, stopped, continued, etc.)
600 return Ok(Some(status));
601 }
602}
603
604/// Create session and set process group ID [see setsid(2)](https://www.freebsd.org/cgi/man.cgi?setsid)
605///
606/// Upon successful completion, the `setsid()` system call returns the value of the
607/// process group ID of the new process group, which is the same as the process ID
608/// of the calling process.
609///
610/// # Errors
611/// Returns an [`io::Error`] if the setsid system call fails. Common errors include:
612/// - The calling process is already a process group leader (EPERM)
613///
614/// # Example
615///
616/// ```
617/// use fork::{fork, Fork, setsid};
618///
619/// match fork::fork() {
620/// Ok(Fork::Parent(child)) => {
621/// println!("Parent process, child PID: {}", child);
622/// }
623/// Ok(Fork::Child) => {
624/// // Create new session
625/// match setsid() {
626/// Ok(sid) => {
627/// println!("New session ID: {}", sid);
628/// std::process::exit(0);
629/// }
630/// Err(e) => {
631/// eprintln!("Failed to create session: {}", e);
632/// std::process::exit(1);
633/// }
634/// }
635/// }
636/// Err(e) => eprintln!("Fork failed: {}", e),
637/// }
638/// ```
639#[inline]
640#[must_use = "session ID should be used or checked for errors"]
641pub fn setsid() -> io::Result<libc::pid_t> {
642 let res = unsafe { libc::setsid() };
643
644 match res {
645 -1 => Err(io::Error::last_os_error()),
646 res => Ok(res),
647 }
648}
649
650/// Get the process group ID of the current process [see getpgrp(2)](https://www.freebsd.org/cgi/man.cgi?query=getpgrp)
651///
652/// Returns the process group ID of the calling process. This function is always successful
653/// and cannot fail according to POSIX specification.
654///
655/// # Example
656///
657/// ```
658/// use fork::getpgrp;
659///
660/// let pgid = getpgrp();
661/// println!("Current process group ID: {}", pgid);
662/// ```
663#[inline]
664#[must_use = "process group ID should be used"]
665pub fn getpgrp() -> libc::pid_t {
666 // SAFETY: getpgrp() has no preconditions and always succeeds per POSIX
667 unsafe { libc::getpgrp() }
668}
669
670/// Get the current process ID [see getpid(2)](https://man.freebsd.org/cgi/man.cgi?getpid)
671///
672/// Returns the process ID of the calling process. This function is always successful.
673///
674/// # Example
675///
676/// ```
677/// use fork::getpid;
678///
679/// let my_pid = getpid();
680/// println!("My process ID: {}", my_pid);
681/// ```
682#[inline]
683#[must_use = "process ID should be used"]
684pub fn getpid() -> libc::pid_t {
685 // SAFETY: getpid() has no preconditions and always succeeds
686 unsafe { libc::getpid() }
687}
688
689/// Get the parent process ID [see getppid(2)](https://man.freebsd.org/cgi/man.cgi?getppid)
690///
691/// Returns the process ID of the parent of the calling process. This function is always successful.
692///
693/// # Example
694///
695/// ```
696/// use fork::getppid;
697///
698/// let parent_pid = getppid();
699/// println!("My parent's process ID: {}", parent_pid);
700/// ```
701#[inline]
702#[must_use = "process ID should be used"]
703pub fn getppid() -> libc::pid_t {
704 // SAFETY: getppid() has no preconditions and always succeeds
705 unsafe { libc::getppid() }
706}
707
708/// The daemon function is for programs wishing to detach themselves from the
709/// controlling terminal and run in the background as system daemons.
710///
711/// * `nochdir = false`, changes the current working directory to the root (`/`).
712/// * `noclose = false`, redirects stdin, stdout, and stderr to `/dev/null`
713///
714/// # Return Value
715///
716/// This function only ever returns in the **daemon (grandchild) process**:
717///
718/// - `Ok(Fork::Child)` — You are the daemon. The original process and the
719/// intermediate child have already exited via `_exit(0)`.
720/// - `Err(...)` — A system call failed before the daemon could be created.
721///
722/// **`Ok(Fork::Parent(_))` is never returned** because both parent processes
723/// call `_exit(0)` internally. You do not need to match on it:
724///
725/// ```no_run
726/// use fork::{daemon, Fork};
727///
728/// // Recommended: use `if let` — no dead Parent arm needed
729/// if let Ok(Fork::Child) = daemon(false, false) {
730/// // Only the daemon reaches here
731/// loop {
732/// // daemon work…
733/// std::thread::sleep(std::time::Duration::from_secs(60));
734/// }
735/// }
736/// ```
737///
738/// If you prefer `match` for explicit error handling, mark the parent arm
739/// unreachable:
740///
741/// ```no_run
742/// use fork::{daemon, Fork};
743///
744/// match daemon(false, false) {
745/// Ok(Fork::Child) => {
746/// // daemon work…
747/// }
748/// Ok(Fork::Parent(_)) => unreachable!("daemon() exits both parent processes"),
749/// Err(err) => eprintln!("daemon failed: {err}"),
750/// }
751/// ```
752///
753/// # Implementation (double-fork)
754///
755/// 1. **First fork** — Parent calls `_exit(0)` immediately.
756/// 2. **Session setup** — Child calls `setsid()`, optionally `chdir("/")`, and optionally redirects stdio.
757/// 3. **Second (double) fork** — Session-leader child calls `_exit(0)` immediately.
758/// 4. **Daemon continues** — Grandchild (daemon) runs with no controlling terminal.
759///
760/// # Behavior Change in v0.4.0
761///
762/// Previously, `noclose = false` would close stdio file descriptors.
763/// Now it redirects them to `/dev/null` instead, which is safer and prevents
764/// file descriptor reuse bugs. This matches industry standard implementations
765/// (libuv, systemd, BSD daemon(3)).
766///
767/// # Errors
768/// Returns an [`io::Error`] if any of the underlying system calls fail:
769/// - fork fails (e.g., resource limits)
770/// - setsid fails (e.g., already a session leader)
771/// - chdir fails (when `nochdir` is false)
772/// - `redirect_stdio` fails (when `noclose` is false)
773///
774/// Example:
775///
776///```
777///// The parent forks the child
778///// The parent exits
779///// The child calls setsid() to start a new session with no controlling terminals
780///// The child forks a grandchild
781///// The child exits
782///// The grandchild is now the daemon
783///use fork::{daemon, Fork};
784///use std::process::Command;
785///
786///if let Ok(Fork::Child) = daemon(false, false) {
787/// Command::new("sleep")
788/// .arg("3")
789/// .output()
790/// .expect("failed to execute process");
791///}
792///```
793#[must_use = "daemon() only returns Ok(Fork::Child) in the daemon process; check the result"]
794pub fn daemon(nochdir: bool, noclose: bool) -> io::Result<Fork> {
795 // 1. First fork: detach from original parent; parent exits immediately
796 match fork()? {
797 // SAFETY: _exit is async-signal-safe and avoids running any Rust/CRT destructors
798 Fork::Parent(_) => unsafe { libc::_exit(0) },
799 Fork::Child => {
800 // 2. Session setup in first child
801 setsid()?;
802 if !nochdir {
803 chdir()?;
804 }
805 if !noclose {
806 redirect_stdio()?;
807 }
808
809 // 3. Second Fork (Double-fork): drop session leader, keep only the daemon
810 match fork()? {
811 // SAFETY: _exit avoids invoking non-async-signal-safe destructors in the forked process
812 Fork::Parent(_) => unsafe { libc::_exit(0) },
813 Fork::Child => Ok(Fork::Child),
814 }
815 }
816 }
817}
818
819#[cfg(test)]
820#[allow(clippy::expect_used)]
821#[allow(clippy::panic)]
822#[allow(clippy::match_wild_err_arm)]
823#[allow(clippy::ignored_unit_patterns)]
824#[allow(clippy::uninlined_format_args)]
825mod tests {
826 use super::*;
827 use libc::{WEXITSTATUS, WIFEXITED};
828 use std::{
829 env,
830 os::unix::io::FromRawFd,
831 process::{Command, exit},
832 };
833
834 #[test]
835 fn test_fork() {
836 match fork() {
837 Ok(Fork::Parent(child)) => {
838 assert!(child > 0);
839 // Wait for child to complete
840 let status = waitpid(child).expect("waitpid failed");
841 assert!(WIFEXITED(status));
842 }
843 Ok(Fork::Child) => {
844 // Child process exits immediately
845 exit(0);
846 }
847 Err(_) => panic!("Fork failed"),
848 }
849 }
850
851 #[test]
852 fn test_fork_with_waitpid() {
853 match fork() {
854 Ok(Fork::Parent(child)) => {
855 assert!(child > 0);
856 let status = waitpid(child).expect("waitpid failed");
857 assert!(WIFEXITED(status));
858 assert_eq!(WEXITSTATUS(status), 0);
859 }
860 Ok(Fork::Child) => {
861 let _ = Command::new("true").output();
862 exit(0);
863 }
864 Err(_) => panic!("Fork failed"),
865 }
866 }
867
868 #[test]
869 fn test_chdir() {
870 match fork() {
871 Ok(Fork::Parent(child)) => {
872 let status = waitpid(child).expect("waitpid failed");
873 assert!(WIFEXITED(status));
874 }
875 Ok(Fork::Child) => {
876 // Test changing directory to root
877 match chdir() {
878 Ok(_) => {
879 let path = env::current_dir().expect("failed current_dir");
880 assert_eq!(Some("/"), path.to_str());
881 exit(0);
882 }
883 Err(_) => exit(1),
884 }
885 }
886 Err(_) => panic!("Fork failed"),
887 }
888 }
889
890 #[test]
891 fn test_getpgrp() {
892 match fork() {
893 Ok(Fork::Parent(child)) => {
894 let status = waitpid(child).expect("waitpid failed");
895 assert!(WIFEXITED(status));
896 }
897 Ok(Fork::Child) => {
898 // Get process group and verify it's valid
899 let pgrp = getpgrp();
900 assert!(pgrp > 0);
901 exit(0);
902 }
903 Err(_) => panic!("Fork failed"),
904 }
905 }
906
907 #[test]
908 fn test_setsid() {
909 match fork() {
910 Ok(Fork::Parent(child)) => {
911 let status = waitpid(child).expect("waitpid failed");
912 assert!(WIFEXITED(status));
913 }
914 Ok(Fork::Child) => {
915 // Create new session
916 match setsid() {
917 Ok(sid) => {
918 assert!(sid > 0);
919 // Verify we're the session leader
920 let pgrp = getpgrp();
921 assert_eq!(sid, pgrp);
922 exit(0);
923 }
924 Err(_) => exit(1),
925 }
926 }
927 Err(_) => panic!("Fork failed"),
928 }
929 }
930
931 #[test]
932 fn test_daemon_pattern_with_chdir() {
933 // Test the daemon pattern manually without calling daemon()
934 // to avoid exit(0) killing the test process
935 match fork() {
936 Ok(Fork::Parent(child)) => {
937 // Parent waits for child
938 let status = waitpid(child).expect("waitpid failed");
939 assert!(WIFEXITED(status));
940 }
941 Ok(Fork::Child) => {
942 // Child creates new session and forks again
943 setsid().expect("Failed to setsid");
944 chdir().expect("Failed to chdir");
945
946 match fork() {
947 Ok(Fork::Parent(_)) => {
948 // Middle process exits
949 exit(0);
950 }
951 Ok(Fork::Child) => {
952 // Grandchild (daemon) - verify state
953 let path = env::current_dir().expect("failed current_dir");
954 assert_eq!(Some("/"), path.to_str());
955
956 let pgrp = getpgrp();
957 assert!(pgrp > 0);
958
959 exit(0);
960 }
961 Err(_) => exit(1),
962 }
963 }
964 Err(_) => panic!("Fork failed"),
965 }
966 }
967
968 #[test]
969 fn test_daemon_pattern_no_chdir() {
970 // Test daemon pattern preserving current directory
971 let original_dir = env::current_dir().expect("failed to get current dir");
972
973 match fork() {
974 Ok(Fork::Parent(child)) => {
975 let status = waitpid(child).expect("waitpid failed");
976 assert!(WIFEXITED(status));
977 }
978 Ok(Fork::Child) => {
979 setsid().expect("Failed to setsid");
980 // Don't call chdir - preserve directory
981
982 match fork() {
983 Ok(Fork::Parent(_)) => exit(0),
984 Ok(Fork::Child) => {
985 let current_dir = env::current_dir().expect("failed current_dir");
986 // Directory should be preserved
987 if original_dir.to_str() != Some("/") {
988 assert!(current_dir.to_str().is_some());
989 }
990 exit(0);
991 }
992 Err(_) => exit(1),
993 }
994 }
995 Err(_) => panic!("Fork failed"),
996 }
997 }
998
999 #[test]
1000 fn test_daemon_pattern_with_close_fd() {
1001 // Test daemon pattern with file descriptor closure
1002 match fork() {
1003 Ok(Fork::Parent(child)) => {
1004 let status = waitpid(child).expect("waitpid failed");
1005 assert!(WIFEXITED(status));
1006 }
1007 Ok(Fork::Child) => {
1008 setsid().expect("Failed to setsid");
1009 chdir().expect("Failed to chdir");
1010 close_fd().expect("Failed to close fd");
1011
1012 match fork() {
1013 Ok(Fork::Parent(_)) => exit(0),
1014 Ok(Fork::Child) => {
1015 // Daemon process with closed fds
1016 exit(0);
1017 }
1018 Err(_) => exit(1),
1019 }
1020 }
1021 Err(_) => panic!("Fork failed"),
1022 }
1023 }
1024
1025 #[test]
1026 fn test_close_fd_functionality() {
1027 match fork() {
1028 Ok(Fork::Parent(child)) => {
1029 let status = waitpid(child).expect("waitpid failed");
1030 assert!(WIFEXITED(status));
1031 }
1032 Ok(Fork::Child) => {
1033 // Close standard file descriptors
1034 match close_fd() {
1035 Ok(_) => exit(0),
1036 Err(_) => exit(1),
1037 }
1038 }
1039 Err(_) => panic!("Fork failed"),
1040 }
1041 }
1042
1043 #[test]
1044 fn test_close_fd_idempotent() {
1045 match fork() {
1046 Ok(Fork::Parent(child)) => {
1047 let status = waitpid(child).expect("waitpid failed");
1048 assert!(WIFEXITED(status));
1049 assert_eq!(WEXITSTATUS(status), 0);
1050 }
1051 Ok(Fork::Child) => {
1052 // First close should succeed
1053 close_fd().expect("first close_fd failed");
1054 // Second close should treat EBADF as success
1055 close_fd().expect("second close_fd failed");
1056 exit(0);
1057 }
1058 Err(_) => panic!("Fork failed"),
1059 }
1060 }
1061
1062 #[test]
1063 fn test_double_fork_pattern() {
1064 // Test the double-fork pattern commonly used for daemons
1065 match fork() {
1066 Ok(Fork::Parent(child1)) => {
1067 assert!(child1 > 0);
1068 let status = waitpid(child1).expect("waitpid failed");
1069 assert!(WIFEXITED(status));
1070 }
1071 Ok(Fork::Child) => {
1072 // First child creates new session
1073 setsid().expect("Failed to setsid");
1074
1075 // Second fork to ensure we're not session leader
1076 match fork() {
1077 Ok(Fork::Parent(_)) => {
1078 // First child exits
1079 exit(0);
1080 }
1081 Ok(Fork::Child) => {
1082 // Grandchild - the daemon process
1083 let pgrp = getpgrp();
1084 assert!(pgrp > 0);
1085 exit(0);
1086 }
1087 Err(_) => exit(1),
1088 }
1089 }
1090 Err(_) => panic!("Fork failed"),
1091 }
1092 }
1093
1094 #[test]
1095 fn test_waitpid_with_child() {
1096 match fork() {
1097 Ok(Fork::Parent(child)) => {
1098 assert!(child > 0);
1099 // Wait for child with timeout to prevent hanging
1100 // Simple approach: just call waitpid, the child exits immediately
1101 let status = waitpid(child).expect("waitpid failed");
1102 assert!(WIFEXITED(status));
1103 }
1104 Ok(Fork::Child) => {
1105 // Child exits immediately to prevent any hanging issues
1106 exit(0);
1107 }
1108 Err(_) => panic!("Fork failed"),
1109 }
1110 }
1111
1112 #[test]
1113 fn test_fork_child_execution() {
1114 match fork() {
1115 Ok(Fork::Parent(child)) => {
1116 assert!(child > 0);
1117 // Wait for child to finish its work
1118 let status = waitpid(child).expect("waitpid failed");
1119 assert!(WIFEXITED(status));
1120 }
1121 Ok(Fork::Child) => {
1122 // Child executes a simple command
1123 let output = Command::new("echo")
1124 .arg("test")
1125 .output()
1126 .expect("Failed to execute command");
1127 assert!(output.status.success());
1128 exit(0);
1129 }
1130 Err(_) => panic!("Fork failed"),
1131 }
1132 }
1133
1134 #[test]
1135 fn test_multiple_forks() {
1136 // Test creating multiple child processes
1137 for i in 0..3 {
1138 match fork() {
1139 Ok(Fork::Parent(child)) => {
1140 assert!(child > 0);
1141 let status = waitpid(child).expect("waitpid failed");
1142 assert!(WIFEXITED(status));
1143 assert_eq!(WEXITSTATUS(status), i);
1144 }
1145 Ok(Fork::Child) => {
1146 // Each child exits with its index
1147 exit(i);
1148 }
1149 Err(_) => panic!("Fork {} failed", i),
1150 }
1151 }
1152 }
1153
1154 #[test]
1155 fn test_getpgrp_in_parent() {
1156 // Test getpgrp in parent process
1157 let parent_pgrp = getpgrp();
1158 assert!(parent_pgrp > 0);
1159 }
1160
1161 #[test]
1162 fn test_close_once_ok_and_ebadf() {
1163 // Create a pipe to obtain valid fds
1164 let mut fds = [0; 2];
1165 assert_eq!(unsafe { libc::pipe(&raw mut fds[0]) }, 0);
1166
1167 // Close write end via close_once (should succeed)
1168 close_once(fds[1]).expect("close_once should close valid fd");
1169
1170 // Wrap read end in File to close it once; drop immediately
1171 let read_fd = fds[0];
1172 unsafe { std::fs::File::from_raw_fd(read_fd) };
1173
1174 // Second close should be treated as success (EBADF path)
1175 close_once(read_fd).expect("EBADF should be treated as success");
1176 }
1177}