nyra 0.1.1

Network Yield Runtime Assistant : system assistant for process control and monitoring.
use std::fs;
use std::process::{Command, ExitStatus};

use humansize::{BINARY, format_size};
use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;

// Run a program and wait for it to finish
pub fn run_program(program: &str) -> Result<ExitStatus, std::io::Error> {
    Command::new(program).spawn()?.wait()
}

// List running processes by scanning /proc
pub fn list_processes() -> Vec<(String, String)> {
    let mut results = Vec::new();

    if let Ok(entries) = fs::read_dir("/proc") {
        for entry in entries.flatten() {
            let name = entry.file_name().into_string().unwrap_or_default();

            if name.chars().all(|c| c.is_numeric()) {
                let pid = name.clone();
                let cmd_path = format!("/proc/{}/cmdline", pid);

                let cmdline = fs::read_to_string(&cmd_path).unwrap_or_default();
                let display = if cmdline.is_empty() {
                    "[kernel/system process]".to_string()
                } else {
                    cmdline.replace('\0', " ")
                };

                results.push((pid, display));
            }
        }
    }

    results
}

// Stop a running process using a polite SIGTERM signal
pub fn stop_process(pid: u32) -> Result<(), String> {
    let pid = Pid::from_raw(pid as i32);

    match signal::kill(pid, Signal::SIGTERM) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to stop process {}: {}", pid, e)),
    }
}

pub fn process_info(pid: u32) -> String {
    let status_path = format!("/proc/{}/status", pid);

    let content = std::fs::read_to_string(status_path).unwrap_or_default();

    let mut name = "unknown".to_string();
    let mut uid = "unknown".to_string();
    let mut ppid = "unknown".to_string();

    for line in content.lines() {
        if line.starts_with("Name:") {
            name = line.replace("Name:\t", "").to_string();
        } else if line.starts_with("Uid:") {
            uid = line
                .replace("Uid:\t", "")
                .split_whitespace()
                .next()
                .unwrap_or("unknown")
                .to_string();
        } else if line.starts_with("PPid:") {
            ppid = line.replace("PPid:\t", "").to_string();
        }
    }

    let uid_num: u32 = uid.parse().unwrap_or(99999);

    let risk = if pid == 1 {
        "❌ Critical system process (NEVER stop)"
    } else if uid_num == 0 {
        "⚠️ System/root process (dangerous)"
    } else {
        "✔ Safe user-level process"
    };

    format!("PID: {pid}\nName: {name}\nUserID: {uid}\nParentPID: {ppid}\nStatus: {risk}")
}

pub fn process_stats(pid: u32) -> Result<String, String> {
    let stat_path = format!("/proc/{}/stat", pid);
    let status_path = format!("/proc/{}/status", pid);

    // Read memory usage
    let status =
        std::fs::read_to_string(&status_path).map_err(|_| "Process not found".to_string())?;

    let mut name = "unknown".to_string();
    let mut memory = 0;

    for line in status.lines() {
        if line.starts_with("Name:") {
            name = line.replace("Name:\t", "");
        }
        if line.starts_with("VmRSS:") {
            let parts: Vec<&str> = line.split_whitespace().collect();
            memory = parts[1].parse::<usize>().unwrap_or(0) * 1024;
        }
    }

    // Read CPU stat
    let stat1 = std::fs::read_to_string(&stat_path).unwrap_or_default();
    std::thread::sleep(std::time::Duration::from_millis(100));
    let stat2 = std::fs::read_to_string(&stat_path).unwrap_or_default();

    let cpu1: Vec<&str> = stat1.split_whitespace().collect();
    let cpu2: Vec<&str> = stat2.split_whitespace().collect();

    let utime1: f64 = cpu1[13].parse().unwrap_or(0.0);
    let stime1: f64 = cpu1[14].parse().unwrap_or(0.0);

    let utime2: f64 = cpu2[13].parse().unwrap_or(0.0);
    let stime2: f64 = cpu2[14].parse().unwrap_or(0.0);

    let cpu_usage = ((utime2 + stime2) - (utime1 + stime1)) * 10.0;

    let memory_fmt = format_size(memory, BINARY);

    Ok(format!(
        "PID: {pid}\nName: {name}\nCPU: {:.2}%\nMemory: {}\n",
        cpu_usage, memory_fmt
    ))
}