laburnum 1.17.1

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! Process spawning and daemonization for daemon mode.

#[cfg(unix)]
use std::os::unix::process::CommandExt;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use std::{
  io,
  path::Path,
  process::{
    Command,
    Stdio,
  },
};

#[cfg(unix)]
pub fn spawn_daemon(
  executable: &Path,
  workspace_id: &str,
  args: &[&str],
) -> io::Result<()> {
  let mut cmd = Command::new(executable);
  cmd
    .arg("launch-daemon")
    .arg(workspace_id)
    .arg("--daemonize")
    .args(args)
    .stdin(Stdio::null())
    .stdout(Stdio::null())
    .stderr(Stdio::null())
    .process_group(0);

  let child = cmd.spawn()?;

  let ws_id = workspace_id.to_string();
  let child_pid = child.id() as i64;
  otel::event!(
    "daemon_spawn_started",
    "workspace_id" = ws_id,
    "child_pid" = child_pid
  );

  Ok(())
}

#[cfg(windows)]
pub fn spawn_daemon(
  executable: &Path,
  workspace_id: &str,
  args: &[&str],
) -> io::Result<()> {
  const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
  const DETACHED_PROCESS: u32 = 0x00000008;
  const CREATE_NO_WINDOW: u32 = 0x08000000;

  let mut cmd = Command::new(executable);
  cmd
    .arg("launch-daemon")
    .arg(workspace_id)
    .args(args)
    .stdin(Stdio::null())
    .stdout(Stdio::null())
    .stderr(Stdio::null())
    .creation_flags(
      CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS | CREATE_NO_WINDOW,
    );

  let child = cmd.spawn()?;

  let ws_id = workspace_id.to_string();
  let child_pid = child.id() as i64;
  otel::event!(
    "daemon_spawn_started",
    "workspace_id" = ws_id,
    "child_pid" = child_pid
  );

  Ok(())
}

#[cfg(unix)]
pub fn daemonize() -> io::Result<()> {
  use std::os::unix::io::AsRawFd;

  let pid = unsafe { libc::fork() };

  if pid < 0 {
    return Err(io::Error::last_os_error());
  }

  if pid > 0 {
    std::process::exit(0);
  }

  if unsafe { libc::setsid() } < 0 {
    return Err(io::Error::last_os_error());
  }

  let pid = unsafe { libc::fork() };

  if pid < 0 {
    return Err(io::Error::last_os_error());
  }

  if pid > 0 {
    std::process::exit(0);
  }

  if unsafe { libc::chdir(c"/".as_ptr()) } < 0 {
    return Err(io::Error::last_os_error());
  }

  let null_fd = std::fs::File::open("/dev/null")?;
  let null_raw = null_fd.as_raw_fd();

  unsafe {
    libc::dup2(null_raw, libc::STDIN_FILENO);
    libc::dup2(null_raw, libc::STDOUT_FILENO);
    libc::dup2(null_raw, libc::STDERR_FILENO);
  }

  Ok(())
}

#[cfg(windows)]
pub fn daemonize() -> io::Result<()> {
  Ok(())
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  #[cfg(unix)]
  fn test_spawn_daemon_creates_command() {
    let executable = Path::new("/usr/bin/test");
    let workspace_id = "test-workspace";
    let result = spawn_daemon(executable, workspace_id, &[]);
    assert!(result.is_err());
  }
}