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}