Skip to main content

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}