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/// `ssh_url`, when set, is forwarded as `--ssh-url <URL>` so the
63/// spawned daemon boots into an SSH authority instead of the default
64/// `Authority::local()` (see `EditorServerConfig.startup_authority`).
65/// Returns the PID of the spawned server (intermediate, not final daemon PID).
66pub fn spawn_server_detached(session_name: Option<&str>, ssh_url: Option<&str>) -> io::Result<u32> {
67    let exe = std::env::current_exe()?;
68
69    let mut args = vec!["--server".to_string()];
70
71    if let Some(name) = session_name {
72        args.push("--session-name".to_string());
73        args.push(name.to_string());
74    }
75
76    if let Some(url) = ssh_url {
77        args.push("--ssh-url".to_string());
78        args.push(url.to_string());
79    }
80
81    // Use Command to spawn, which properly handles the process
82    let child = std::process::Command::new(&exe)
83        .args(&args)
84        .stdin(std::process::Stdio::null())
85        .stdout(std::process::Stdio::null())
86        .stderr(std::process::Stdio::null())
87        .spawn()?;
88
89    Ok(child.id())
90}
91
92/// Check if a process with the given PID is still running
93pub fn is_process_running(pid: u32) -> bool {
94    // Send signal 0 to check if process exists
95    unsafe { libc::kill(pid as i32, 0) == 0 }
96}