use crate::claim_time::current_boot_id;
use crate::process_identity::{ProcessIdentityError, current_birth_identity, process_birth_identity};
use crate::service_index_marker::{Marked, marker_of};
use std::path::Path;
use std::sync::OnceLock;
pub const BOOT_SEPARATOR: char = '#';
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HolderState {
Gone,
Running,
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ReaderView<'a> {
pub boot: &'a str,
pub scope: Option<&'a str>,
pub procfs_agrees: bool,
}
pub fn scope_of_link(text: &str) -> Option<String> {
let number: &str = text.strip_prefix("pid:[")?.strip_suffix(']')?;
return (!number.is_empty() && number.as_bytes().iter().all(u8::is_ascii_digit)).then_some(number.to_string());
}
pub fn current_pid_scope() -> Option<&'static str> {
static SCOPE: OnceLock<Option<String>> = OnceLock::new();
return SCOPE
.get_or_init(|| {
let target = std::fs::read_link("/proc/self/ns/pid").ok()?;
return scope_of_link(target.to_string_lossy().as_ref());
})
.as_deref();
}
pub fn procfs_agrees_with(read: std::io::Result<Vec<u8>>, own: u32) -> bool {
let Ok(stat) = read else { return false };
return check_procfs_view(stat.as_slice(), own).is_ok();
}
fn current_procfs_agrees() -> bool {
static AGREES: OnceLock<bool> = OnceLock::new();
return *AGREES.get_or_init(read_procfs_agreement);
}
fn read_procfs_agreement() -> bool {
return procfs_agrees_with(std::fs::read("/proc/self/stat"), std::process::id());
}
impl ReaderView<'static> {
pub fn current() -> ReaderView<'static> {
return ReaderView { boot: current_boot_id(), scope: current_pid_scope(), procfs_agrees: current_procfs_agrees() };
}
}
pub fn pid_of_self_stat(stat: &[u8]) -> Option<i64> {
let digits: &[u8] = stat.split(|byte| { return *byte == b' '; }).next()?;
return std::str::from_utf8(digits).ok()?.parse::<i64>().ok();
}
pub fn own_identity() -> Result<String, ProcessIdentityError> {
let own: u32 = std::process::id();
#[cfg(target_os = "linux")]
let stat: Vec<u8> = std::fs::read("/proc/self/stat").map_err(|error| { return ProcessIdentityError::Io { pid: i64::from(own), error }; })?;
#[cfg(not(target_os = "linux"))]
let stat: Vec<u8> = format!("{own} ").into_bytes();
return identity_in_view(stat.as_slice(), own, current_boot_id(), current_pid_scope());
}
pub fn identity_in_view(stat: &[u8], own: u32, boot: &str, scope: Option<&str>) -> Result<String, ProcessIdentityError> {
check_procfs_view(stat, own)?;
let birth: String = current_birth_identity()?;
#[cfg(target_os = "linux")]
{
if boot.is_empty() {
return Err(ProcessIdentityError::BootUnavailable);
}
if scope.is_none() {
return Err(ProcessIdentityError::ScopeUnavailable);
}
}
let mut identity: String = birth;
for part in [Some(boot).filter(|text| { return !text.is_empty(); }), scope] {
if let Some(text) = part {
identity.push(BOOT_SEPARATOR);
identity.push_str(text);
}
}
return Ok(identity);
}
pub fn check_procfs_view(stat: &[u8], own: u32) -> Result<(), ProcessIdentityError> {
let procfs: i64 = pid_of_self_stat(stat).ok_or(ProcessIdentityError::MalformedStat { pid: i64::from(own) })?;
if procfs != i64::from(own) {
return Err(ProcessIdentityError::ProcMismatch { procfs, own });
}
return Ok(());
}
fn split_birth(birth: &str) -> (&str, Option<&str>, Option<&str>) {
let mut parts = birth.splitn(3, BOOT_SEPARATOR);
let identity: &str = parts.next().unwrap_or(birth);
return (identity, parts.next(), parts.next());
}
pub fn holder_state_in(
holder: Option<&(i64, String)>,
view: &ReaderView,
ask: &dyn Fn(i64) -> Result<Option<String>, ProcessIdentityError>,
) -> HolderState {
let Some((pid, birth)) = holder else { return HolderState::Gone };
let (identity, boot, scope) = split_birth(birth.as_str());
if let Some(recorded) = boot
&& !view.boot.is_empty()
&& recorded != view.boot
{
return HolderState::Gone;
}
if !view.procfs_agrees || (boot.is_some() && view.boot.is_empty()) {
return HolderState::Unknown;
}
if let Some(recorded) = scope
&& view.scope != Some(recorded)
{
return HolderState::Unknown;
}
return match ask(*pid) {
Ok(None) => HolderState::Gone,
Ok(Some(now)) if now == identity => HolderState::Running,
Ok(Some(_)) => HolderState::Gone,
Err(_) => HolderState::Unknown,
};
}
pub fn holder_state(holder: Option<&(i64, String)>) -> HolderState {
return holder_state_in(holder, &ReaderView::current(), &process_birth_identity);
}
pub fn holder_is_gone(holder: Option<&(i64, String)>) -> bool {
return holder_state(holder) == HolderState::Gone;
}
pub fn holder_is_running(holder: Option<&(i64, String)>) -> bool {
return holder_state(holder) == HolderState::Running;
}
pub fn mark_names_a_gone_holder(lock: &Path) -> bool {
return marker_of(lock).is_some_and(|marked: Marked| { return marked.holder.is_some() && holder_is_gone(marked.holder.as_ref()); });
}