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}