Skip to main content

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}