prismtty 0.2.2

Fast terminal output highlighter focused on network devices and Unix systems
Documentation
use std::fs::{self, OpenOptions};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::time::SystemTime;

use nix::libc;

pub(super) struct RuntimeRegistration {
    pid: u32,
    path: PathBuf,
}

impl RuntimeRegistration {
    pub(super) fn register() -> io::Result<Self> {
        let dir = runtime_dir();
        fs::create_dir_all(&dir)?;
        let path = pid_registry_path();
        let pid = std::process::id();
        let mut file = OpenOptions::new().create(true).append(true).open(&path)?;
        writeln!(file, "{pid}")?;
        Ok(Self { pid, path })
    }
}

impl Drop for RuntimeRegistration {
    fn drop(&mut self) {
        let Ok(input) = fs::read_to_string(&self.path) else {
            return;
        };
        let retained = input
            .lines()
            .filter_map(|line| line.trim().parse::<u32>().ok())
            .filter(|pid| *pid != self.pid && process_is_alive(*pid))
            .map(|pid| pid.to_string())
            .collect::<Vec<_>>()
            .join("\n");
        let output = if retained.is_empty() {
            String::new()
        } else {
            format!("{retained}\n")
        };
        let _ = fs::write(&self.path, output);
    }
}

pub(super) struct ReloadWatcher {
    marker: PathBuf,
    last_seen: Option<SystemTime>,
}

impl ReloadWatcher {
    pub(super) fn new() -> Self {
        let marker = reload_marker_path();
        let last_seen = reload_marker_time(&marker);
        Self { marker, last_seen }
    }

    pub(super) fn reload_requested(&mut self) -> bool {
        let current = reload_marker_time(&self.marker);
        if current.is_some() && current != self.last_seen {
            self.last_seen = current;
            return true;
        }
        false
    }
}

pub(super) fn request_reload() -> io::Result<usize> {
    let dir = runtime_dir();
    fs::create_dir_all(&dir)?;
    fs::write(reload_marker_path(), format!("{:?}\n", SystemTime::now()))?;

    let path = pid_registry_path();
    let input = fs::read_to_string(&path).unwrap_or_default();
    let mut count = 0usize;
    let mut retained = Vec::new();
    for pid in input
        .lines()
        .filter_map(|line| line.trim().parse::<u32>().ok())
    {
        if process_is_alive(pid) {
            count += 1;
            retained.push(pid.to_string());
        }
    }

    let output = if retained.is_empty() {
        String::new()
    } else {
        format!("{}\n", retained.join("\n"))
    };
    fs::write(path, output)?;
    Ok(count)
}

fn runtime_dir() -> PathBuf {
    if let Some(path) = std::env::var_os("PRISMTTY_RUNTIME_DIR") {
        return PathBuf::from(path);
    }
    std::env::temp_dir().join(format!("prismtty-{}", current_uid()))
}

fn current_uid() -> u32 {
    unsafe { libc::getuid() }
}

fn pid_registry_path() -> PathBuf {
    runtime_dir().join("pids")
}

fn reload_marker_path() -> PathBuf {
    runtime_dir().join("reload")
}

fn reload_marker_time(path: &Path) -> Option<SystemTime> {
    fs::metadata(path)
        .and_then(|metadata| metadata.modified())
        .ok()
}

fn process_is_alive(pid: u32) -> bool {
    if pid == 0 {
        return false;
    }
    unsafe { libc::kill(pid as libc::pid_t, 0) == 0 }
}