fork/
lib.rs

1//! Library for creating a new process detached from the controlling terminal (daemon).
2//!
3//! Example:
4//! ```
5//!use fork::{daemon, Fork};
6//!use std::process::Command;
7//!
8//!if let Ok(Fork::Child) = daemon(false, false) {
9//!    Command::new("sleep")
10//!        .arg("3")
11//!        .output()
12//!        .expect("failed to execute process");
13//!}
14//!```
15
16use std::ffi::CString;
17use std::io;
18use std::process::exit;
19
20/// Fork result
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Fork {
23    Parent(libc::pid_t),
24    Child,
25}
26
27/// Change dir to `/` [see chdir(2)](https://www.freebsd.org/cgi/man.cgi?query=chdir&sektion=2)
28///
29/// Upon successful completion, the current working directory is changed to `/`.
30/// Otherwise, an error is returned with the system error code.
31///
32/// Example:
33///
34///```
35///use fork::chdir;
36///use std::env;
37///
38///match chdir() {
39///    Ok(_) => {
40///       let path = env::current_dir().expect("failed current_dir");
41///       assert_eq!(Some("/"), path.to_str());
42///    }
43///    Err(e) => eprintln!("Failed to change directory: {}", e),
44///}
45///```
46///
47/// # Errors
48/// Returns an [`io::Error`] if the system call fails. Common errors include:
49/// - Permission denied
50/// - Path does not exist
51///
52/// # Panics
53/// Panics if `CString::new` fails
54pub fn chdir() -> io::Result<()> {
55    let dir = CString::new("/").expect("CString::new failed");
56    let res = unsafe { libc::chdir(dir.as_ptr()) };
57    match res {
58        -1 => Err(io::Error::last_os_error()),
59        _ => Ok(()),
60    }
61}
62
63/// Close file descriptors stdin, stdout, stderr
64///
65/// **Warning:** This function closes the file descriptors, making them
66/// available for reuse. If your daemon opens files after calling this,
67/// those files may get fd 0, 1, or 2, causing `println!`, `eprintln!`,
68/// or panic output to corrupt them.
69///
70/// **Use [`redirect_stdio()`] instead**, which is safer and follows
71/// industry best practices by redirecting stdio to `/dev/null` instead
72/// of closing. This keeps fd 0, 1, 2 occupied, ensuring subsequent files
73/// get fd >= 3, preventing silent corruption.
74///
75/// # Errors
76/// Returns an [`io::Error`] if any of the file descriptors fail to close.
77///
78/// # Example
79///
80/// ```no_run
81/// use fork::close_fd;
82///
83/// // Warning: Files opened after this may get fd 0,1,2!
84/// close_fd()?;
85/// # Ok::<(), std::io::Error>(())
86/// ```
87pub fn close_fd() -> io::Result<()> {
88    for fd in 0..=2 {
89        if unsafe { libc::close(fd) } == -1 {
90            return Err(io::Error::last_os_error());
91        }
92    }
93    Ok(())
94}
95
96/// Redirect stdin, stdout, stderr to /dev/null
97///
98/// This is the recommended way to detach from the controlling terminal
99/// in daemon processes. Unlike [`close_fd()`], this keeps file descriptors
100/// 0, 1, 2 occupied (pointing to /dev/null), preventing them from being
101/// reused by subsequent `open()` calls.
102///
103/// This prevents bugs where `println!`, `eprintln!`, or panic output
104/// accidentally writes to data files that happened to get assigned fd 0, 1, or 2.
105///
106/// # Implementation
107///
108/// This function:
109/// 1. Opens `/dev/null` with O_RDWR
110/// 2. Uses `dup2()` to redirect fds 0, 1, 2 to `/dev/null`
111/// 3. Closes the extra file descriptor if it was > 2
112///
113/// This is the same approach used by libuv, systemd, and BSD `daemon(3)`.
114///
115/// # Errors
116///
117/// Returns an [`io::Error`] if:
118/// - `/dev/null` cannot be opened
119/// - `dup2()` fails to redirect any of the file descriptors
120///
121/// # Example
122///
123/// ```no_run
124/// use fork::redirect_stdio;
125/// use std::fs::File;
126///
127/// redirect_stdio()?;
128///
129/// // Now safe: files will get fd >= 3
130/// let log = File::create("app.log")?;
131///
132/// // This goes to /dev/null (safely discarded), not to app.log
133/// println!("debug message");
134/// # Ok::<(), std::io::Error>(())
135/// ```
136pub fn redirect_stdio() -> io::Result<()> {
137    use std::ffi::CString;
138
139    let dev_null = CString::new("/dev/null")
140        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "CString::new failed"))?;
141
142    let null_fd = unsafe { libc::open(dev_null.as_ptr(), libc::O_RDWR) };
143
144    if null_fd == -1 {
145        return Err(io::Error::last_os_error());
146    }
147
148    // Redirect stdin, stdout, stderr to /dev/null
149    for fd in 0..=2 {
150        if unsafe { libc::dup2(null_fd, fd) } == -1 {
151            let err = io::Error::last_os_error();
152            // Clean up the opened fd before returning error
153            if null_fd > 2 {
154                unsafe { libc::close(null_fd) };
155            }
156            return Err(err);
157        }
158    }
159
160    // Close the extra fd if it's > 2
161    // (if null_fd was 0, 1, or 2, it's now dup'd to all three, so don't close)
162    if null_fd > 2 {
163        unsafe { libc::close(null_fd) };
164    }
165
166    Ok(())
167}
168
169/// Create a new child process [see fork(2)](https://www.freebsd.org/cgi/man.cgi?fork)
170///
171/// Upon successful completion, `fork()` returns [`Fork::Child`] in the child process
172/// and `Fork::Parent(pid)` with the child's process ID in the parent process.
173///
174/// Example:
175///
176/// ```
177///use fork::{fork, Fork};
178///
179///match fork() {
180///    Ok(Fork::Parent(child)) => {
181///        println!("Continuing execution in parent process, new child has pid: {}", child);
182///    }
183///    Ok(Fork::Child) => println!("I'm a new child process"),
184///    Err(e) => eprintln!("Fork failed: {}", e),
185///}
186///```
187/// This will print something like the following (order indeterministic).
188///
189/// ```text
190/// Continuing execution in parent process, new child has pid: 1234
191/// I'm a new child process
192/// ```
193///
194/// The thing to note is that you end up with two processes continuing execution
195/// immediately after the fork call but with different match arms.
196///
197/// # [`nix::unistd::fork`](https://docs.rs/nix/0.15.0/nix/unistd/fn.fork.html)
198///
199/// The example has been taken from the [`nix::unistd::fork`](https://docs.rs/nix/0.15.0/nix/unistd/fn.fork.html),
200/// please check the [Safety](https://docs.rs/nix/0.15.0/nix/unistd/fn.fork.html#safety) section
201///
202/// # Errors
203/// Returns an [`io::Error`] if the fork system call fails. Common errors include:
204/// - Resource temporarily unavailable (EAGAIN) - process limit reached
205/// - Out of memory (ENOMEM)
206pub fn fork() -> io::Result<Fork> {
207    let res = unsafe { libc::fork() };
208    match res {
209        -1 => Err(io::Error::last_os_error()),
210        0 => Ok(Fork::Child),
211        res => Ok(Fork::Parent(res)),
212    }
213}
214
215/// Wait for process to change status [see wait(2)](https://man.freebsd.org/cgi/man.cgi?waitpid)
216///
217/// # Errors
218/// Returns an [`io::Error`] if the waitpid system call fails. Common errors include:
219/// - No child process exists with the given PID
220/// - Invalid options or PID
221///
222/// Example:
223///
224/// ```
225///use fork::{waitpid, Fork};
226///use std::process::Command;
227///
228///fn main() {
229///  match fork::fork() {
230///     Ok(Fork::Parent(pid)) => {
231///
232///         println!("Child pid: {pid}");
233///
234///         match waitpid(pid) {
235///             Ok(_) => println!("Child exited"),
236///             Err(e) => eprintln!("Failed to wait on child: {}", e),
237///         }
238///     }
239///     Ok(Fork::Child) => {
240///         Command::new("sleep")
241///             .arg("1")
242///             .output()
243///             .expect("failed to execute process");
244///     }
245///     Err(e) => eprintln!("Failed to fork: {}", e),
246///  }
247///}
248///```
249pub fn waitpid(pid: i32) -> io::Result<()> {
250    let mut status: i32 = 0;
251    let res = unsafe { libc::waitpid(pid, &mut status, 0) };
252    match res {
253        -1 => Err(io::Error::last_os_error()),
254        _ => Ok(()),
255    }
256}
257
258/// Create session and set process group ID [see setsid(2)](https://www.freebsd.org/cgi/man.cgi?setsid)
259///
260/// Upon successful completion, the `setsid()` system call returns the value of the
261/// process group ID of the new process group, which is the same as the process ID
262/// of the calling process.
263///
264/// # Errors
265/// Returns an [`io::Error`] if the setsid system call fails. Common errors include:
266/// - The calling process is already a process group leader (EPERM)
267pub fn setsid() -> io::Result<libc::pid_t> {
268    let res = unsafe { libc::setsid() };
269    match res {
270        -1 => Err(io::Error::last_os_error()),
271        res => Ok(res),
272    }
273}
274
275/// The process group of the current process [see getpgrp(2)](https://www.freebsd.org/cgi/man.cgi?query=getpgrp)
276///
277/// # Errors
278/// This function should not fail under normal circumstances, but returns
279/// an [`io::Error`] if the system call fails.
280pub fn getpgrp() -> io::Result<libc::pid_t> {
281    let res = unsafe { libc::getpgrp() };
282    match res {
283        -1 => Err(io::Error::last_os_error()),
284        res => Ok(res),
285    }
286}
287
288/// The daemon function is for programs wishing to detach themselves from the
289/// controlling terminal and run in the background as system daemons.
290///
291/// * `nochdir = false`, changes the current working directory to the root (`/`).
292/// * `noclose = false`, redirects stdin, stdout, and stderr to `/dev/null`
293///
294/// # Behavior Change in v0.4.0
295///
296/// Previously, `noclose = false` would close stdio file descriptors.
297/// Now it redirects them to `/dev/null` instead, which is safer and prevents
298/// file descriptor reuse bugs. This matches industry standard implementations
299/// (libuv, systemd, BSD daemon(3)).
300///
301/// # Errors
302/// Returns an [`io::Error`] if any of the underlying system calls fail:
303/// - fork fails (e.g., resource limits)
304/// - setsid fails (e.g., already a session leader)
305/// - chdir fails (when `nochdir` is false)
306/// - redirect_stdio fails (when `noclose` is false)
307///
308/// Example:
309///
310///```
311///// The parent forks the child
312///// The parent exits
313///// The child calls setsid() to start a new session with no controlling terminals
314///// The child forks a grandchild
315///// The child exits
316///// The grandchild is now the daemon
317///use fork::{daemon, Fork};
318///use std::process::Command;
319///
320///if let Ok(Fork::Child) = daemon(false, false) {
321///    Command::new("sleep")
322///        .arg("3")
323///        .output()
324///        .expect("failed to execute process");
325///}
326///```
327pub fn daemon(nochdir: bool, noclose: bool) -> io::Result<Fork> {
328    match fork() {
329        Ok(Fork::Parent(_)) => exit(0),
330        Ok(Fork::Child) => setsid().and_then(|_| {
331            if !nochdir {
332                chdir()?;
333            }
334            if !noclose {
335                redirect_stdio()?;
336            }
337            fork()
338        }),
339        Err(e) => Err(e),
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346    use std::env;
347    use std::process::{Command, exit};
348
349    #[test]
350    fn test_fork() {
351        match fork() {
352            Ok(Fork::Parent(child)) => {
353                assert!(child > 0);
354                // Wait for child to complete
355                let _ = waitpid(child);
356            }
357            Ok(Fork::Child) => {
358                // Child process exits immediately
359                exit(0);
360            }
361            Err(_) => panic!("Fork failed"),
362        }
363    }
364
365    #[test]
366    fn test_fork_with_waitpid() {
367        match fork() {
368            Ok(Fork::Parent(child)) => {
369                assert!(child > 0);
370                // Wait for child and verify it succeeds
371                assert!(waitpid(child).is_ok());
372            }
373            Ok(Fork::Child) => {
374                // Child does some work then exits
375                let _ = Command::new("true").output();
376                exit(0);
377            }
378            Err(_) => panic!("Fork failed"),
379        }
380    }
381
382    #[test]
383    fn test_chdir() {
384        match fork() {
385            Ok(Fork::Parent(child)) => {
386                let _ = waitpid(child);
387            }
388            Ok(Fork::Child) => {
389                // Test changing directory to root
390                match chdir() {
391                    Ok(_) => {
392                        let path = env::current_dir().expect("failed current_dir");
393                        assert_eq!(Some("/"), path.to_str());
394                        exit(0);
395                    }
396                    Err(_) => exit(1),
397                }
398            }
399            Err(_) => panic!("Fork failed"),
400        }
401    }
402
403    #[test]
404    fn test_getpgrp() {
405        match fork() {
406            Ok(Fork::Parent(child)) => {
407                let _ = waitpid(child);
408            }
409            Ok(Fork::Child) => {
410                // Get process group and verify it's valid
411                match getpgrp() {
412                    Ok(pgrp) => {
413                        assert!(pgrp > 0);
414                        exit(0);
415                    }
416                    Err(_) => exit(1),
417                }
418            }
419            Err(_) => panic!("Fork failed"),
420        }
421    }
422
423    #[test]
424    fn test_setsid() {
425        match fork() {
426            Ok(Fork::Parent(child)) => {
427                let _ = waitpid(child);
428            }
429            Ok(Fork::Child) => {
430                // Create new session
431                match setsid() {
432                    Ok(sid) => {
433                        assert!(sid > 0);
434                        // Verify we're the session leader
435                        let pgrp = getpgrp().expect("Failed to get process group");
436                        assert_eq!(sid, pgrp);
437                        exit(0);
438                    }
439                    Err(_) => exit(1),
440                }
441            }
442            Err(_) => panic!("Fork failed"),
443        }
444    }
445
446    #[test]
447    fn test_daemon_pattern_with_chdir() {
448        // Test the daemon pattern manually without calling daemon()
449        // to avoid exit(0) killing the test process
450        match fork() {
451            Ok(Fork::Parent(child)) => {
452                // Parent waits for child
453                let _ = waitpid(child);
454            }
455            Ok(Fork::Child) => {
456                // Child creates new session and forks again
457                setsid().expect("Failed to setsid");
458                chdir().expect("Failed to chdir");
459
460                match fork() {
461                    Ok(Fork::Parent(_)) => {
462                        // Middle process exits
463                        exit(0);
464                    }
465                    Ok(Fork::Child) => {
466                        // Grandchild (daemon) - verify state
467                        let path = env::current_dir().expect("failed current_dir");
468                        assert_eq!(Some("/"), path.to_str());
469
470                        let pgrp = getpgrp().expect("Failed to get process group");
471                        assert!(pgrp > 0);
472
473                        exit(0);
474                    }
475                    Err(_) => exit(1),
476                }
477            }
478            Err(_) => panic!("Fork failed"),
479        }
480    }
481
482    #[test]
483    fn test_daemon_pattern_no_chdir() {
484        // Test daemon pattern preserving current directory
485        let original_dir = env::current_dir().expect("failed to get current dir");
486
487        match fork() {
488            Ok(Fork::Parent(child)) => {
489                let _ = waitpid(child);
490            }
491            Ok(Fork::Child) => {
492                setsid().expect("Failed to setsid");
493                // Don't call chdir - preserve directory
494
495                match fork() {
496                    Ok(Fork::Parent(_)) => exit(0),
497                    Ok(Fork::Child) => {
498                        let current_dir = env::current_dir().expect("failed current_dir");
499                        // Directory should be preserved
500                        if original_dir.to_str() != Some("/") {
501                            assert!(current_dir.to_str().is_some());
502                        }
503                        exit(0);
504                    }
505                    Err(_) => exit(1),
506                }
507            }
508            Err(_) => panic!("Fork failed"),
509        }
510    }
511
512    #[test]
513    fn test_daemon_pattern_with_close_fd() {
514        // Test daemon pattern with file descriptor closure
515        match fork() {
516            Ok(Fork::Parent(child)) => {
517                let _ = waitpid(child);
518            }
519            Ok(Fork::Child) => {
520                setsid().expect("Failed to setsid");
521                chdir().expect("Failed to chdir");
522                close_fd().expect("Failed to close fd");
523
524                match fork() {
525                    Ok(Fork::Parent(_)) => exit(0),
526                    Ok(Fork::Child) => {
527                        // Daemon process with closed fds
528                        exit(0);
529                    }
530                    Err(_) => exit(1),
531                }
532            }
533            Err(_) => panic!("Fork failed"),
534        }
535    }
536
537    #[test]
538    fn test_close_fd_functionality() {
539        match fork() {
540            Ok(Fork::Parent(child)) => {
541                let _ = waitpid(child);
542            }
543            Ok(Fork::Child) => {
544                // Close standard file descriptors
545                match close_fd() {
546                    Ok(_) => exit(0),
547                    Err(_) => exit(1),
548                }
549            }
550            Err(_) => panic!("Fork failed"),
551        }
552    }
553
554    #[test]
555    fn test_double_fork_pattern() {
556        // Test the double-fork pattern commonly used for daemons
557        match fork() {
558            Ok(Fork::Parent(child1)) => {
559                assert!(child1 > 0);
560                let _ = waitpid(child1);
561            }
562            Ok(Fork::Child) => {
563                // First child creates new session
564                setsid().expect("Failed to setsid");
565
566                // Second fork to ensure we're not session leader
567                match fork() {
568                    Ok(Fork::Parent(_)) => {
569                        // First child exits
570                        exit(0);
571                    }
572                    Ok(Fork::Child) => {
573                        // Grandchild - the daemon process
574                        let pgrp = getpgrp().expect("Failed to get process group");
575                        assert!(pgrp > 0);
576                        exit(0);
577                    }
578                    Err(_) => exit(1),
579                }
580            }
581            Err(_) => panic!("Fork failed"),
582        }
583    }
584
585    #[test]
586    fn test_waitpid_with_child() {
587        match fork() {
588            Ok(Fork::Parent(child)) => {
589                assert!(child > 0);
590                // Wait for child with timeout to prevent hanging
591                // Simple approach: just call waitpid, the child exits immediately
592                let result = waitpid(child);
593                assert!(result.is_ok(), "waitpid should succeed");
594            }
595            Ok(Fork::Child) => {
596                // Child exits immediately to prevent any hanging issues
597                exit(0);
598            }
599            Err(_) => panic!("Fork failed"),
600        }
601    }
602
603    #[test]
604    fn test_fork_child_execution() {
605        match fork() {
606            Ok(Fork::Parent(child)) => {
607                assert!(child > 0);
608                // Wait for child to finish its work
609                assert!(waitpid(child).is_ok());
610            }
611            Ok(Fork::Child) => {
612                // Child executes a simple command
613                let output = Command::new("echo")
614                    .arg("test")
615                    .output()
616                    .expect("Failed to execute command");
617                assert!(output.status.success());
618                exit(0);
619            }
620            Err(_) => panic!("Fork failed"),
621        }
622    }
623
624    #[test]
625    fn test_multiple_forks() {
626        // Test creating multiple child processes
627        for i in 0..3 {
628            match fork() {
629                Ok(Fork::Parent(child)) => {
630                    assert!(child > 0);
631                    let _ = waitpid(child);
632                }
633                Ok(Fork::Child) => {
634                    // Each child exits with its index
635                    exit(i);
636                }
637                Err(_) => panic!("Fork {} failed", i),
638            }
639        }
640    }
641
642    #[test]
643    fn test_getpgrp_in_parent() {
644        // Test getpgrp in parent process
645        let parent_pgrp = getpgrp().expect("getpgrp should succeed");
646        assert!(parent_pgrp > 0);
647    }
648}