proses 0.1.1

Proses – Professional Secure Execution System
//! CLI-facing daemon lifecycle management.
//!
//! All functions here use only `std` (no Tokio) and `libc` for signal
//! operations, so they can be called from the synchronous CLI binary.

pub mod fork;
pub mod ipc;
pub mod server;

use crate::config;
use std::{fs, thread, time::Duration};

// ─── State helpers ────────────────────────────────────────────────────────────

/// Returns `true` if the daemon's PID file exists **and** the recorded PID
/// corresponds to a live OS process (`kill(pid, 0) == 0`).
pub fn is_running() -> bool {
    let cfg = config::get();

    let pid_str = match fs::read_to_string(&cfg.pid_path) {
        Ok(s) => s,
        Err(_) => return false,
    };

    let pid: libc::pid_t = match pid_str.trim().parse() {
        Ok(p) => p,
        Err(_) => return false,
    };

    if pid <= 0 {
        return false;
    }

    // kill(pid, 0) succeeds (returns 0) when the process exists.
    unsafe { libc::kill(pid, 0) == 0 }
}

// ─── Lifecycle commands ───────────────────────────────────────────────────────

/// Auto-start the daemon if it is not already running.
///
/// After forking, waits up to 3 s (30 × 100 ms) for the Unix socket to
/// appear, which indicates that the daemon is ready to accept connections.
/// Exits the process with an error message if the daemon never comes up.
pub fn ensure_running() {
    if is_running() {
        return;
    }

    fork::spawn_daemon(|| server::run());

    let cfg = config::get();
    for _ in 0..30 {
        thread::sleep(Duration::from_millis(100));
        if cfg.sock_path.exists() {
            return;
        }
    }

    eprintln!(
        "error: daemon did not start within 3 seconds \
         (socket {:?} never appeared)",
        cfg.sock_path
    );
    std::process::exit(1);
}

/// Handler for `proses daemon start`.
///
/// Starts the daemon if not already running, then waits for the socket to
/// appear.  Prints a status message and exits cleanly.
pub fn start_cmd() {
    config::init();

    if is_running() {
        let pid = read_pid().unwrap_or(0);
        println!("Daemon is already running (pid {}).", pid);
        return;
    }

    println!("Starting daemon…");
    fork::spawn_daemon(|| server::run());

    let cfg = config::get();
    for _ in 0..30 {
        thread::sleep(Duration::from_millis(100));
        if cfg.sock_path.exists() {
            let pid = read_pid().unwrap_or(0);
            println!("Daemon started (pid {}).", pid);
            return;
        }
    }

    eprintln!(
        "error: daemon did not start within 3 seconds \
         (socket {:?} never appeared)",
        cfg.sock_path
    );
    std::process::exit(1);
}

/// Handler for `proses daemon stop`.
///
/// Reads the PID file, sends `SIGTERM`, waits up to 2 s for the process to
/// exit, then cleans up the PID and socket files.
pub fn stop_cmd() {
    config::init();

    let pid: libc::pid_t = match read_pid() {
        Some(p) => p,
        None => {
            println!("Daemon is not running (no PID file).");
            return;
        }
    };

    // Check the process is actually alive before trying to kill it.
    if unsafe { libc::kill(pid, 0) } != 0 {
        println!("Daemon is not running (pid {} not found).", pid);
        cleanup_files();
        return;
    }

    println!("Stopping daemon (pid {})…", pid);
    unsafe { libc::kill(pid, libc::SIGTERM) };

    // Wait up to 2 s for the process to exit.
    for _ in 0..20 {
        thread::sleep(Duration::from_millis(100));
        if unsafe { libc::kill(pid, 0) } != 0 {
            break;
        }
    }

    cleanup_files();
    println!("Daemon stopped.");
}

/// Handler for `proses daemon status`.
///
/// Prints whether the daemon is running and, if so, its PID.
pub fn status_cmd() {
    config::init();

    match read_pid() {
        None => println!("Daemon: stopped (no PID file)"),
        Some(pid) => {
            if unsafe { libc::kill(pid, 0) } == 0 {
                println!("Daemon: running (pid {})", pid);
            } else {
                println!("Daemon: stopped (pid {} not found)", pid);
                cleanup_files();
            }
        }
    }
}

// ─── Private helpers ──────────────────────────────────────────────────────────

/// Read and parse the daemon PID from the PID file.
/// Returns `None` if the file is missing or contains an invalid integer.
fn read_pid() -> Option<libc::pid_t> {
    let cfg = config::get();
    let content = fs::read_to_string(&cfg.pid_path).ok()?;
    content.trim().parse::<libc::pid_t>().ok()
}

/// Remove the PID and socket files, ignoring errors (they may already be gone).
fn cleanup_files() {
    let cfg = config::get();
    let _ = fs::remove_file(&cfg.pid_path);
    let _ = fs::remove_file(&cfg.sock_path);
}