Skip to main content

running_process/cleanup/
verify_basic.rs

1use std::path::Path;
2
3use crate::broker::{host_identity, manifest};
4use crate::cleanup::json_escape;
5
6/// One manifest verification finding.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct VerifyFinding {
9    /// Manifest path.
10    pub path: std::path::PathBuf,
11    /// Finding severity.
12    pub severity: &'static str,
13    /// Human-readable message.
14    pub message: String,
15}
16
17/// Basic v1 verification result.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct VerifyReport {
20    /// Number of `.pb` entries scanned.
21    pub scanned: usize,
22    /// Findings generated during verification.
23    pub findings: Vec<VerifyFinding>,
24}
25
26/// Run basic verification over the central registry.
27pub fn run(registry_dir: &Path) -> VerifyReport {
28    let current = host_identity::current();
29    let entries = manifest::scan_central(registry_dir);
30    let mut findings = Vec::new();
31    let scanned = entries.len();
32
33    for entry in entries {
34        match entry.result {
35            Ok(manifest) => {
36                if let Some(host) = manifest.host.as_ref() {
37                    if !host.machine_id.is_empty() && host.machine_id != current.machine_id {
38                        findings.push(VerifyFinding {
39                            path: entry.path.clone(),
40                            severity: "stale",
41                            message: "manifest belongs to another machine".to_string(),
42                        });
43                    }
44                    if !host.boot_id.is_empty() && host.boot_id != current.boot_id {
45                        findings.push(VerifyFinding {
46                            path: entry.path.clone(),
47                            severity: "stale",
48                            message: "manifest belongs to a prior boot".to_string(),
49                        });
50                    }
51                }
52                if let Some(daemon) = manifest.current_daemon.as_ref() {
53                    if !process_is_alive(daemon.pid) {
54                        findings.push(VerifyFinding {
55                            path: entry.path,
56                            severity: "stale",
57                            message: format!("daemon pid {} is not alive", daemon.pid),
58                        });
59                    }
60                }
61            }
62            Err(err) => findings.push(VerifyFinding {
63                path: entry.path,
64                severity: "error",
65                message: err.to_string(),
66            }),
67        }
68    }
69
70    VerifyReport { scanned, findings }
71}
72
73/// Render `running-process-cleanup verify --json`.
74pub fn render_json(report: &VerifyReport) -> String {
75    let findings = report
76        .findings
77        .iter()
78        .map(|finding| {
79            format!(
80                "{{\"path\":\"{}\",\"severity\":\"{}\",\"message\":\"{}\"}}",
81                json_escape(&finding.path.to_string_lossy()),
82                finding.severity,
83                json_escape(&finding.message)
84            )
85        })
86        .collect::<Vec<_>>()
87        .join(",");
88    format!(
89        "{{\"schema_version\":1,\"scanned\":{},\"findings\":[{}]}}",
90        report.scanned, findings
91    )
92}
93
94#[cfg(unix)]
95fn process_is_alive(pid: u32) -> bool {
96    if pid == 0 {
97        return false;
98    }
99    let result = unsafe { libc::kill(pid as i32, 0) };
100    result == 0 || std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH)
101}
102
103#[cfg(windows)]
104fn process_is_alive(pid: u32) -> bool {
105    if pid == 0 {
106        return false;
107    }
108    let handle = unsafe {
109        winapi::um::processthreadsapi::OpenProcess(
110            winapi::um::winnt::PROCESS_QUERY_LIMITED_INFORMATION,
111            0,
112            pid,
113        )
114    };
115    if handle.is_null() {
116        return false;
117    }
118    unsafe {
119        winapi::um::handleapi::CloseHandle(handle);
120    }
121    true
122}