use std::fs::File;
use std::path::Path;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use anyhow::{Context, Result};
use super::registry::{self, DaemonInfo};
pub fn is_process_alive(pid: u32) -> bool {
#[cfg(unix)]
{
#[allow(clippy::cast_possible_wrap)]
let rc = unsafe { libc::kill(pid as libc::pid_t, 0) };
rc == 0
}
#[cfg(windows)]
{
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::System::Threading::{
OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
};
let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
if handle.is_null() {
return false;
}
unsafe { CloseHandle(handle) };
true
}
#[cfg(not(any(unix, windows)))]
{
let _ = pid;
true
}
}
pub fn spawn_daemon(
exe_path: &Path,
firefox_host: &str,
firefox_port: u16,
timeout_secs: u64,
) -> Result<()> {
let log_path = registry::log_path()?;
let log_file = File::create(&log_path)
.with_context(|| format!("creating daemon log file {}", log_path.display()))?;
let stderr_file = log_file
.try_clone()
.context("cloning log file handle for stderr")?;
let mut cmd = Command::new(exe_path);
cmd.args([
"_daemon",
"--host",
firefox_host,
"--port",
&firefox_port.to_string(),
"--daemon-timeout",
&timeout_secs.to_string(),
])
.stdout(log_file)
.stderr(stderr_file)
.stdin(Stdio::null());
#[cfg(unix)]
{
use std::os::unix::process::CommandExt as _;
unsafe {
cmd.pre_exec(|| {
libc::setsid();
Ok(())
});
}
}
#[cfg(windows)]
{
use std::os::windows::process::CommandExt as _;
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
cmd.creation_flags(CREATE_NO_WINDOW);
}
cmd.spawn().context("failed to spawn daemon process")?;
Ok(())
}
pub fn wait_for_registry(
timeout: Duration,
expected_host: &str,
expected_port: u16,
) -> Result<DaemonInfo> {
let deadline = Instant::now() + timeout;
loop {
match registry::read_registry() {
Ok(Some(info)) => {
anyhow::ensure!(
info.firefox_host == expected_host && info.firefox_port == expected_port,
"registry targets {}:{} but expected {expected_host}:{expected_port}",
info.firefox_host,
info.firefox_port,
);
return Ok(info);
}
Ok(None) => {}
Err(e) => return Err(e).context("reading daemon registry while waiting"),
}
if Instant::now() >= deadline {
anyhow::bail!("timed out after {timeout:?} waiting for daemon to write registry");
}
std::thread::sleep(Duration::from_millis(50));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_process_is_alive() {
let pid = std::process::id();
assert!(
is_process_alive(pid),
"current process (PID {pid}) should be detected as alive"
);
}
#[test]
fn very_large_pid_is_dead() {
assert!(
!is_process_alive(999_999_999),
"PID 999_999_999 should be detected as dead"
);
}
}