use std::time::{SystemTime, UNIX_EPOCH};
pub const LAUNCHER_SESSION_ENV: &str = "ONEPIPELINE_LAUNCHER_SESSION";
pub const LAUNCHER_ENV: &str = "ONEPIPELINE_LAUNCHER";
pub const UNKNOWN_LAUNCHER: &str = "unknown";
pub fn now_millis() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
.unwrap_or(0)
}
pub fn now_rfc3339() -> String {
rfc3339_from_millis(now_millis())
}
pub fn rfc3339_from_millis(millis: u64) -> String {
let secs = millis / 1_000;
let ms = millis % 1_000;
let days = i64::try_from(secs / 86_400).unwrap_or(0);
let sod = secs % 86_400;
let (year, month, day) = civil_from_days(days);
let (hour, minute, second) = (sod / 3_600, (sod % 3_600) / 60, sod % 60);
format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{ms:03}Z")
}
fn civil_from_days(days: i64) -> (i64, u64, u64) {
let z = days + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if m <= 2 { y + 1 } else { y };
(year, m as u64, d as u64)
}
pub fn pid() -> u32 {
std::process::id()
}
pub fn hostname() -> String {
for key in ["HOSTNAME", "COMPUTERNAME"] {
if let Ok(value) = std::env::var(key) {
if !value.is_empty() {
return value;
}
}
}
std::fs::read_to_string("/etc/hostname")
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "localhost".to_string())
}
pub fn process_may_be_live(pid: u32) -> bool {
if pid == 0 {
return false;
}
platform_process_may_be_live(pid)
}
#[cfg(unix)]
fn platform_process_may_be_live(pid: u32) -> bool {
let Ok(raw) = i32::try_from(pid) else {
return true;
};
let rc = unsafe { libc::kill(raw, 0) };
if rc == 0 {
return true;
}
std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH)
}
#[cfg(windows)]
fn platform_process_may_be_live(pid: u32) -> bool {
use windows_sys::Win32::Foundation::{CloseHandle, ERROR_INVALID_PARAMETER, WAIT_OBJECT_0};
use windows_sys::Win32::System::Threading::{
OpenProcess, WaitForSingleObject, PROCESS_SYNCHRONIZE,
};
let handle = unsafe { OpenProcess(PROCESS_SYNCHRONIZE, 0, pid) };
if handle.is_null() {
return std::io::Error::last_os_error().raw_os_error()
!= Some(ERROR_INVALID_PARAMETER as i32);
}
let waited = unsafe { WaitForSingleObject(handle, 0) };
unsafe { CloseHandle(handle) };
waited != WAIT_OBJECT_0
}
pub fn disown_standard_handles() {
platform_disown_standard_handles();
}
#[cfg(unix)]
fn platform_disown_standard_handles() {}
#[cfg(windows)]
fn platform_disown_standard_handles() {
use windows_sys::Win32::Foundation::{
SetHandleInformation, HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::System::Console::{
GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
};
for which in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] {
let handle = unsafe { GetStdHandle(which) };
if handle.is_null() || handle == INVALID_HANDLE_VALUE {
continue;
}
unsafe { SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0) };
}
}
pub fn launching_session() -> String {
std::env::var(LAUNCHER_SESSION_ENV)
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| UNKNOWN_LAUNCHER.to_string())
}
pub fn launcher() -> String {
std::env::var(LAUNCHER_ENV)
.ok()
.filter(|s| !s.is_empty())
.unwrap_or_else(|| UNKNOWN_LAUNCHER.to_string())
}
pub fn session_digest(session: &str) -> String {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for byte in session.as_bytes() {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x100_0000_01b3);
}
format!("{:08x}", (hash >> 32) as u32)
}
#[cfg(test)]
pub(crate) fn reaped_pid() -> u32 {
let mut child = std::process::Command::new(
std::env::current_exe().expect("the test binary knows its own path"),
)
.args(["--list", "--format", "terse"])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.expect("the test binary starts");
let pid = child.id();
child.wait().expect("it exits");
pid
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_epoch_renders_as_rfc3339_millis() {
assert_eq!(rfc3339_from_millis(0), "1970-01-01T00:00:00.000Z");
}
#[test]
fn a_known_instant_renders_with_its_milliseconds() {
assert_eq!(
rfc3339_from_millis(1_786_195_785_678),
"2026-08-08T13:29:45.678Z"
);
}
#[test]
fn a_leap_day_is_not_skipped() {
assert_eq!(
rfc3339_from_millis(1_709_164_800_000),
"2024-02-29T00:00:00.000Z"
);
}
#[test]
fn now_is_rendered_in_the_envelope_shape() {
let now = now_rfc3339();
assert_eq!(now.len(), 24, "{now} is not RFC 3339 millisecond UTC");
assert!(now.ends_with('Z'), "{now} is not UTC");
}
#[test]
fn this_process_is_live_and_pid_zero_is_not() {
assert!(process_may_be_live(pid()));
assert!(!process_may_be_live(0));
}
#[test]
fn a_reaped_process_is_proved_gone() {
let dead = reaped_pid();
assert!(!process_may_be_live(dead), "pid {dead} was reaped");
}
#[test]
fn a_foreign_session_is_labelled_by_a_stable_digest() {
let first = session_digest("claude-code:3f9a1c2e");
assert_eq!(first, session_digest("claude-code:3f9a1c2e"));
assert_ne!(first, session_digest("claude-code:other"));
assert_eq!(first.len(), 8);
}
#[test]
fn the_host_always_names_itself() {
assert!(!hostname().is_empty());
}
}