use crate::json_record::{
ObjectWriter, decode_fatal, number_equals, parse_object, safe_integer_field, string_field,
};
use crate::process_identity::{
ProcessIdentityError, current_birth_identity, process_birth_identity,
};
use crate::pid_namespace::{current_pid_namespace, foreign_owner_standing};
pub const TRANSACTION_OWNER_SCHEMA_VERSION: i64 = 2;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TransactionOwner {
pub transaction_id: String,
pub owner_pid: i64,
pub owner_identity: String,
pub created_at: String,
pub owner_pid_namespace: Option<String>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OwnerLiveness {
Alive,
Dead,
}
#[derive(Debug)]
pub enum OwnerLivenessError {
Probe(std::io::Error),
Identity(ProcessIdentityError),
}
pub fn current_transaction_owner(
transaction_id: &str,
created_at: &str,
) -> Result<TransactionOwner, ProcessIdentityError> {
return Ok(TransactionOwner {
transaction_id: String::from(transaction_id),
owner_pid: i64::from(std::process::id()),
owner_identity: current_birth_identity()?,
created_at: String::from(created_at),
owner_pid_namespace: current_pid_namespace(),
});
}
pub fn encode_transaction_owner(owner: &TransactionOwner) -> String {
let mut writer: ObjectWriter = ObjectWriter::new();
writer
.integer("schemaVersion", TRANSACTION_OWNER_SCHEMA_VERSION)
.string("transactionId", owner.transaction_id.as_str())
.integer("ownerPid", owner.owner_pid)
.string("ownerIdentity", owner.owner_identity.as_str())
.string("createdAt", owner.created_at.as_str());
if let Some(namespace) = &owner.owner_pid_namespace {
writer.string("ownerPidNamespace", namespace.as_str());
}
return format!("{}\n", writer.finish());
}
pub fn parse_transaction_owner(bytes: &[u8]) -> Option<TransactionOwner> {
let map: serde_json::Map<String, serde_json::Value> = parse_object(decode_fatal(bytes)?)?;
if !number_equals(&map, "schemaVersion", TRANSACTION_OWNER_SCHEMA_VERSION) {
return None;
}
let transaction_id: &str = string_field(&map, "transactionId")?;
let owner_pid: i64 = safe_integer_field(&map, "ownerPid")?;
let owner_identity: &str = string_field(&map, "ownerIdentity")?;
let created_at: &str = string_field(&map, "createdAt")?;
if owner_pid < 1 || owner_identity.is_empty() {
return None;
}
return Some(TransactionOwner {
transaction_id: String::from(transaction_id),
owner_pid,
owner_identity: String::from(owner_identity),
created_at: String::from(created_at),
owner_pid_namespace: string_field(&map, "ownerPidNamespace")
.filter(|namespace| { return !namespace.is_empty(); })
.map(String::from),
});
}
fn process_exists(pid: i64) -> Result<bool, OwnerLivenessError> {
#[cfg(unix)]
{
return crate::process_identity::signal_probe(pid).map_err(OwnerLivenessError::Probe);
}
#[cfg(not(unix))]
{
let _ = pid;
return Ok(true);
}
}
pub fn classify_transaction_owner(
owner_pid: i64,
owner_identity: &str,
) -> Result<OwnerLiveness, OwnerLivenessError> {
if !process_exists(owner_pid)? {
return Ok(OwnerLiveness::Dead);
}
match process_birth_identity(owner_pid).map_err(OwnerLivenessError::Identity)? {
Some(current) if current == owner_identity => return Ok(OwnerLiveness::Alive),
Some(_) | None => return Ok(OwnerLiveness::Dead),
}
}
pub fn classify_transaction_owner_record(
owner: &TransactionOwner,
owner_path: &std::path::Path,
) -> Result<OwnerLiveness, OwnerLivenessError> {
if let Some(standing) = foreign_owner_standing(owner.owner_pid_namespace.as_deref(), owner_path) {
return Ok(if standing { OwnerLiveness::Alive } else { OwnerLiveness::Dead });
}
return classify_transaction_owner(owner.owner_pid, owner.owner_identity.as_str());
}
#[cfg(test)]
#[path = "transaction_owner_tests.rs"]
mod tests;