fresh/server/daemon/unix.rs
1//! Unix-specific daemonization support
2
3use std::io;
4use std::os::unix::io::AsRawFd;
5
6/// Daemonize the current process
7///
8/// This function:
9/// 1. Forks the process (first fork)
10/// 2. Creates a new session with setsid()
11/// 3. Forks again (second fork) to prevent acquiring a controlling terminal
12/// 4. Redirects stdin/stdout/stderr to /dev/null
13/// 5. Changes working directory to /
14///
15/// Returns Ok(()) in the daemon process, or an error if daemonization fails.
16/// The parent process exits immediately.
17pub fn daemonize() -> io::Result<()> {
18 // First fork
19 match unsafe { libc::fork() } {
20 -1 => return Err(io::Error::last_os_error()),
21 0 => {} // Child continues
22 _ => std::process::exit(0), // Parent exits
23 }
24
25 // Create new session, become session leader
26 if unsafe { libc::setsid() } == -1 {
27 return Err(io::Error::last_os_error());
28 }
29
30 // Second fork to prevent acquiring controlling terminal
31 match unsafe { libc::fork() } {
32 -1 => return Err(io::Error::last_os_error()),
33 0 => {} // Child continues
34 _ => std::process::exit(0), // Parent exits
35 }
36
37 // Redirect stdin/stdout/stderr to /dev/null
38 let devnull = std::fs::File::open("/dev/null")?;
39 let devnull_fd = devnull.as_raw_fd();
40
41 unsafe {
42 libc::dup2(devnull_fd, 0); // stdin
43 libc::dup2(devnull_fd, 1); // stdout
44 libc::dup2(devnull_fd, 2); // stderr
45 }
46
47 // Change to root directory to avoid holding mount points
48 std::env::set_current_dir("/")?;
49
50 // Clear umask
51 unsafe {
52 libc::umask(0);
53 }
54
55 Ok(())
56}
57
58/// Spawn the server as a detached background process
59///
60/// This is used when the client starts and no server is running.
61/// The server inherits the current working directory.
62/// Returns the PID of the spawned server (intermediate, not final daemon PID).
63pub fn spawn_server_detached(session_name: Option<&str>) -> io::Result<u32> {
64 let exe = std::env::current_exe()?;
65
66 let mut args = vec!["--server".to_string()];
67
68 if let Some(name) = session_name {
69 args.push("--session-name".to_string());
70 args.push(name.to_string());
71 }
72
73 // Use Command to spawn, which properly handles the process
74 let child = std::process::Command::new(&exe)
75 .args(&args)
76 .stdin(std::process::Stdio::null())
77 .stdout(std::process::Stdio::null())
78 .stderr(std::process::Stdio::null())
79 .spawn()?;
80
81 Ok(child.id())
82}
83
84/// Check if a process with the given PID is still running
85pub fn is_process_running(pid: u32) -> bool {
86 // Send signal 0 to check if process exists
87 unsafe { libc::kill(pid as i32, 0) == 0 }
88}