use super::*;
#[derive(Debug, Clone)]
pub(crate) struct RegistryRow {
pub(crate) pid: Option<u32>,
pub(crate) status: Option<String>,
pub(crate) status_updated_at_ms: Option<i64>,
pub(crate) proc_start: Option<String>,
}
pub(crate) fn registry_row_for(session_id: &str) -> Result<Option<RegistryRow>> {
let dir = crate::path::claude_home()?.join("sessions");
if !dir.is_dir() {
return Ok(None);
}
for entry in std::fs::read_dir(&dir)? {
let entry = entry?;
let p = entry.path();
if p.extension().and_then(|e| e.to_str()) != Some("json") {
continue;
}
let Ok(raw) = std::fs::read_to_string(&p) else {
continue; };
let Ok(v) = serde_json::from_str::<serde_json::Value>(&raw) else {
continue;
};
if v.get("sessionId").and_then(serde_json::Value::as_str) != Some(session_id) {
continue;
}
return Ok(Some(RegistryRow {
pid: v
.get("pid")
.and_then(serde_json::Value::as_u64)
.and_then(|n| u32::try_from(n).ok()),
status: v
.get("status")
.and_then(serde_json::Value::as_str)
.map(str::to_string),
status_updated_at_ms: v.get("statusUpdatedAt").and_then(serde_json::Value::as_i64),
proc_start: v
.get("procStart")
.and_then(serde_json::Value::as_str)
.map(str::to_string),
}));
}
Ok(None)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum PidLiveness {
Alive { reuse_guard: ReuseGuard },
Dead,
Reused,
#[cfg_attr(unix, allow(dead_code))]
Unavailable,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ReuseGuard {
Checked,
Skipped,
}
pub(crate) fn probe_pid(pid: u32, proc_start_utc: Option<&str>) -> PidLiveness {
#[cfg(unix)]
{
match ps_probe(pid) {
PsProbe::NoProcess => PidLiveness::Dead,
PsProbe::Alive(actual) => {
match (proc_start_utc.and_then(parse_registry_proc_start), actual) {
(Some(reg), Some(act)) => {
if (reg.as_second() - act.as_second()).abs() <= 2 {
PidLiveness::Alive {
reuse_guard: ReuseGuard::Checked,
}
} else {
PidLiveness::Reused
}
}
_ => PidLiveness::Alive {
reuse_guard: ReuseGuard::Skipped,
},
}
}
}
}
#[cfg(not(unix))]
{
let _ = (pid, proc_start_utc);
PidLiveness::Unavailable
}
}
#[cfg(unix)]
pub(crate) enum PsProbe {
NoProcess,
Alive(Option<jiff::Timestamp>),
}
pub(crate) fn parse_registry_proc_start(s: &str) -> Option<jiff::Timestamp> {
let bd = jiff::fmt::strtime::parse("%a %b %e %H:%M:%S %Y", s.trim()).ok()?;
let dt = bd.to_datetime().ok()?;
dt.to_zoned(jiff::tz::TimeZone::UTC)
.ok()
.map(|z| z.timestamp())
}
#[cfg(unix)]
pub(crate) fn ps_probe(pid: u32) -> PsProbe {
let Ok(out) = std::process::Command::new("ps")
.args(["-p", &pid.to_string(), "-o", "lstart="])
.output()
else {
return PsProbe::Alive(None); };
let text = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !out.status.success() || text.is_empty() {
if std::path::Path::new(&format!("/proc/{pid}")).is_dir() {
return PsProbe::Alive(None);
}
return PsProbe::NoProcess;
}
let local = crate::timez::local_tz();
for fmt in ["%a %b %e %H:%M:%S %Y", "%a %e %b %H:%M:%S %Y"] {
if let Ok(bd) = jiff::fmt::strtime::parse(fmt, &text) {
if let Ok(dt) = bd.to_datetime() {
if let Ok(z) = dt.to_zoned(local.clone()) {
return PsProbe::Alive(Some(z.timestamp()));
}
}
}
}
PsProbe::Alive(None)
}