use crate::claim_time::{current_boot_id, current_machine_id, is_machine_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 machine: 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(), machine: Some(current_machine_id()).filter(|id| { return !id.is_empty(); }), 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_on(stat.as_slice(), own, current_boot_id(), current_pid_scope(), Some(current_machine_id()).filter(|id| { return !id.is_empty(); }));
}
pub fn identity_in_view(stat: &[u8], own: u32, boot: &str, scope: Option<&str>) -> Result<String, ProcessIdentityError> {
return identity_in_view_on(stat, own, boot, scope, None);
}
pub fn identity_in_view_on(stat: &[u8], own: u32, boot: &str, scope: Option<&str>, machine: 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;
let machine_part: Option<&str> = scope.and(machine).filter(|id| { return is_machine_id(id); });
for part in [Some(boot).filter(|text| { return !text.is_empty(); }), scope, machine_part] {
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>, Option<&str>) {
let mut parts = birth.splitn(4, BOOT_SEPARATOR);
let identity: &str = parts.next().unwrap_or(birth);
return (identity, parts.next(), 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::Unknown };
let (identity, boot, scope, machine) = split_birth(birth.as_str());
if let Some(recorded) = machine
&& (!is_machine_id(recorded) || view.machine != Some(recorded))
{
return HolderState::Unknown;
}
if machine.is_none()
&& view.machine.is_some()
&& boot.is_some_and(|recorded| { return !view.boot.is_empty() && recorded != view.boot; })
{
return HolderState::Unknown;
}
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_is_elsewhere_in(holder: Option<&(i64, String)>, view: &ReaderView) -> bool {
let Some((_, birth)) = holder else { return true };
let (_, boot, _, machine) = split_birth(birth.as_str());
return match machine {
Some(recorded) => !is_machine_id(recorded) || view.machine != Some(recorded),
None => view.machine.is_some() && boot.is_some_and(|recorded| { return !view.boot.is_empty() && recorded != view.boot; }),
};
}
pub fn holder_is_elsewhere(holder: Option<&(i64, String)>) -> bool {
return holder_is_elsewhere_in(holder, &ReaderView::current());
}
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()); });
}