1use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(tag = "platform", rename_all = "snake_case")]
10pub enum ProcessStart {
11 Linux { ticks: u64 },
12 Darwin { seconds: u64, microseconds: u32 },
13}
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub struct ProcessIdentity {
17 pub boot_id: String,
18 pub start: ProcessStart,
19}
20
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub enum ProcessObservation {
23 Present(ProcessIdentity),
24 Absent,
25 Unknown,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum OwnerPresence {
30 Live,
31 Absent,
32 Unknown,
33}
34
35impl ProcessIdentity {
36 pub fn is_valid(&self) -> bool {
37 uuid::Uuid::parse_str(&self.boot_id).is_ok_and(|uuid| uuid.to_string() == self.boot_id)
38 && match self.start {
39 ProcessStart::Linux { .. } => true,
40 ProcessStart::Darwin {
41 seconds,
42 microseconds,
43 } => seconds > 0 && microseconds < 1_000_000,
44 }
45 }
46
47 pub fn to_record(&self) -> Option<String> {
50 self.is_valid().then(|| match self.start {
51 ProcessStart::Linux { ticks } => format!("{}:{ticks}", self.boot_id),
52 ProcessStart::Darwin {
53 seconds,
54 microseconds,
55 } => format!("{}:darwin:{seconds}:{microseconds}", self.boot_id),
56 })
57 }
58
59 pub fn from_record(record: &str) -> Option<Self> {
60 let (boot, marker) = record.split_once(':')?;
61 let start = if let Some(time) = marker.strip_prefix("darwin:") {
62 let (seconds, microseconds) = time.split_once(':')?;
63 ProcessStart::Darwin {
64 seconds: decimal(seconds)?,
65 microseconds: u32::try_from(decimal(microseconds)?).ok()?,
66 }
67 } else {
68 ProcessStart::Linux {
69 ticks: decimal(marker)?,
70 }
71 };
72 let identity = Self {
73 boot_id: boot.into(),
74 start,
75 };
76 identity.is_valid().then_some(identity)
77 }
78
79 pub fn presence(&self, observation: &ProcessObservation) -> OwnerPresence {
80 if !self.is_valid() {
81 return OwnerPresence::Unknown;
82 }
83 match observation {
84 ProcessObservation::Absent => OwnerPresence::Absent,
85 ProcessObservation::Unknown => OwnerPresence::Unknown,
86 ProcessObservation::Present(current) => {
87 if !current.is_valid()
88 || std::mem::discriminant(&self.start) != std::mem::discriminant(¤t.start)
89 {
90 return OwnerPresence::Unknown;
91 }
92 if self == current {
93 OwnerPresence::Live
94 } else {
95 OwnerPresence::Absent
96 }
97 }
98 }
99 }
100}
101
102fn decimal(value: &str) -> Option<u64> {
103 (!value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()))
104 .then(|| value.parse().ok())
105 .flatten()
106}
107
108pub fn owner_presence(pid: u32, expected: Option<&ProcessIdentity>) -> OwnerPresence {
111 if pid <= 1 || i32::try_from(pid).is_err() {
112 return OwnerPresence::Unknown;
113 }
114 observed_owner_presence(expected, &observe_process(pid))
115}
116
117fn observed_owner_presence(
118 expected: Option<&ProcessIdentity>,
119 observed: &ProcessObservation,
120) -> OwnerPresence {
121 if *observed == ProcessObservation::Absent {
122 return OwnerPresence::Absent;
123 }
124 expected.map_or(OwnerPresence::Unknown, |identity| {
125 identity.presence(observed)
126 })
127}
128
129pub fn current_process_identity() -> Option<ProcessIdentity> {
132 let observation = observe_process(std::process::id());
133 match observation {
134 ProcessObservation::Present(identity) => Some(identity),
135 _ => None,
136 }
137}
138
139#[cfg(target_os = "macos")]
143pub fn remote_process_witness(pid: u32) -> Option<String> {
144 if pid <= 1 || i32::try_from(pid).is_err() {
145 return None;
146 }
147 let native_pid = i32::try_from(pid).ok()?;
148 let info = libproc::proc_pid::pidinfo::<libproc::bsd_info::BSDInfo>(native_pid, 0).ok()?;
149 let boot = boot_identity()?;
150 let first = darwin_info(
151 pid,
152 info.pbi_pid,
153 info.pbi_status,
154 info.pbi_start_tvsec,
155 info.pbi_start_tvusec,
156 &boot,
157 );
158 let ProcessObservation::Present(identity) = first else {
159 return None;
160 };
161 let confirmed = libproc::proc_pid::pidinfo::<libproc::bsd_info::BSDInfo>(native_pid, 0).ok()?;
162 if confirmed.pbi_pgid != info.pbi_pgid
163 || darwin_info(
164 pid,
165 confirmed.pbi_pid,
166 confirmed.pbi_status,
167 confirmed.pbi_start_tvsec,
168 confirmed.pbi_start_tvusec,
169 &boot,
170 ) != ProcessObservation::Present(identity)
171 {
172 return None;
173 }
174 let start = info
175 .pbi_start_tvsec
176 .checked_mul(1_000_000)?
177 .checked_add(info.pbi_start_tvusec)?;
178 Some(format!("{boot} {start} {}", info.pbi_pgid))
179}
180
181#[cfg(not(target_os = "macos"))]
182pub fn remote_process_witness(_pid: u32) -> Option<String> {
183 None
184}
185
186#[cfg(target_os = "linux")]
187fn boot_identity() -> Option<String> {
188 let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id").ok()?;
189 let boot = boot.trim();
190 uuid::Uuid::parse_str(boot).ok()?;
191 Some(boot.into())
192}
193
194#[cfg(target_os = "macos")]
195fn boot_identity() -> Option<String> {
196 static BOOT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
197 if let Some(boot) = BOOT.get() {
198 return Some(boot.clone());
199 }
200 let output = std::process::Command::new("/usr/sbin/sysctl")
201 .args(["-n", "kern.bootsessionuuid"])
202 .output()
203 .ok()?;
204 if !output.status.success() {
205 return None;
206 }
207 let text = std::str::from_utf8(&output.stdout).ok()?.trim();
208 let boot = uuid::Uuid::parse_str(text).ok()?.to_string();
209 let _ = BOOT.set(boot.clone());
211 Some(boot)
212}
213
214#[cfg(target_os = "linux")]
215pub fn observe_process(pid: u32) -> ProcessObservation {
216 if pid <= 1 || i32::try_from(pid).is_err() {
217 return ProcessObservation::Unknown;
218 }
219 let namespace_matches = match (
224 std::fs::read_to_string("/proc/self/stat"),
225 std::fs::read_to_string("/proc/self/status"),
226 ) {
227 (Ok(stat), Ok(status)) => linux_namespace_matches(std::process::id(), &stat, &status),
228 _ => false,
229 };
230 if !namespace_matches {
231 return ProcessObservation::Unknown;
232 }
233 let Some(boot) = boot_identity() else {
234 return ProcessObservation::Unknown;
235 };
236 observe_linux_process(
237 &std::path::PathBuf::from(format!("/proc/{pid}")),
238 pid,
239 &boot,
240 )
241}
242
243#[cfg(target_os = "linux")]
244fn observe_linux_process(process: &std::path::Path, pid: u32, boot: &str) -> ProcessObservation {
245 match std::fs::read_to_string(process.join("stat")) {
246 Ok(stat) => {
247 let observed = linux_stat(&stat, pid, boot);
248 if observed != ProcessObservation::Unknown {
249 return observed;
250 }
251 let Some(leader) = parse_linux_stat(&stat, pid) else {
252 return ProcessObservation::Unknown;
253 };
254 observe_linux_threads(process, pid, boot, leader)
255 }
256 Err(error) if error.kind() == std::io::ErrorKind::NotFound => kernel_absence(pid),
259 Err(_) => ProcessObservation::Unknown,
260 }
261}
262
263#[cfg(any(target_os = "linux", test))]
264fn linux_namespace_matches(pid: u32, stat: &str, status: &str) -> bool {
265 if parse_linux_stat(stat, pid).is_none() {
266 return false;
267 }
268 let mut records = status
272 .lines()
273 .filter_map(|line| line.strip_prefix("NSpid:"));
274 let Some(record) = records.next() else {
275 return false;
276 };
277 if records.next().is_some() {
278 return false;
279 }
280 let mut ids = record.split_whitespace();
281 ids.next().and_then(decimal) == Some(u64::from(pid)) && ids.next().is_none()
282}
283
284#[cfg(any(target_os = "linux", target_os = "macos"))]
287fn kernel_absence(pid: u32) -> ProcessObservation {
288 let Some(pid) = i32::try_from(pid)
289 .ok()
290 .filter(|pid| *pid > 1)
291 .and_then(rustix::process::Pid::from_raw)
292 else {
293 return ProcessObservation::Unknown;
294 };
295 match rustix::process::test_kill_process(pid) {
296 Err(rustix::io::Errno::SRCH) => ProcessObservation::Absent,
297 _ => ProcessObservation::Unknown,
298 }
299}
300
301#[cfg(any(target_os = "linux", test))]
302#[derive(Clone, Copy)]
303struct LinuxTaskStat {
304 executing: bool,
305 threads: u32,
306 ticks: u64,
307}
308
309#[cfg(any(target_os = "linux", test))]
310fn parse_linux_stat(stat: &str, pid: u32) -> Option<LinuxTaskStat> {
311 let (prefix, rest) = stat.rsplit_once(") ")?;
312 if prefix.split_once(" (").and_then(|(pid, _)| decimal(pid)) != Some(u64::from(pid)) {
313 return None;
314 }
315 let mut fields = rest.split_whitespace();
316 let executing = match fields.next()? {
317 "Z" | "X" | "x" => false,
318 "R" | "S" | "D" | "T" | "t" | "K" | "W" | "P" | "I" => true,
319 _ => return None,
320 };
321 let threads = u32::try_from(fields.nth(16).and_then(decimal)?).ok()?;
324 let ticks = fields.nth(1).and_then(decimal)?;
325 (threads > 0).then_some(LinuxTaskStat {
326 executing,
327 threads,
328 ticks,
329 })
330}
331
332#[cfg(any(target_os = "linux", test))]
333fn linux_stat(stat: &str, pid: u32, boot: &str) -> ProcessObservation {
334 let Some(task) = parse_linux_stat(stat, pid) else {
335 return ProcessObservation::Unknown;
336 };
337 if task.executing {
338 ProcessObservation::Present(ProcessIdentity {
339 boot_id: boot.into(),
340 start: ProcessStart::Linux { ticks: task.ticks },
341 })
342 } else if task.threads == 1 {
343 ProcessObservation::Absent
344 } else {
345 ProcessObservation::Unknown
348 }
349}
350
351#[cfg(target_os = "linux")]
355fn observe_linux_threads(
356 process: &std::path::Path,
357 pid: u32,
358 boot: &str,
359 leader: LinuxTaskStat,
360) -> ProcessObservation {
361 const MAX_THREAD_OBSERVATIONS: usize = 4096;
362 let Ok(entries) = std::fs::read_dir(process.join("task")) else {
363 return ProcessObservation::Unknown;
364 };
365 for (index, entry) in entries.enumerate() {
366 if index >= MAX_THREAD_OBSERVATIONS {
367 return ProcessObservation::Unknown;
368 }
369 let Ok(entry) = entry else {
370 return ProcessObservation::Unknown;
371 };
372 let Some(tid) = entry
373 .file_name()
374 .to_str()
375 .and_then(decimal)
376 .and_then(|tid| u32::try_from(tid).ok())
377 .filter(|tid| *tid > 0 && i32::try_from(*tid).is_ok())
378 else {
379 return ProcessObservation::Unknown;
380 };
381 let Ok(stat) = std::fs::read_to_string(entry.path().join("stat")) else {
382 return ProcessObservation::Unknown;
383 };
384 let Some(task) = parse_linux_stat(&stat, tid) else {
385 return ProcessObservation::Unknown;
386 };
387 if task.executing {
388 let confirmed = std::fs::read_to_string(process.join("stat"))
391 .ok()
392 .and_then(|stat| parse_linux_stat(&stat, pid));
393 return if confirmed.is_some_and(|current| current.ticks == leader.ticks) {
394 ProcessObservation::Present(ProcessIdentity {
395 boot_id: boot.into(),
396 start: ProcessStart::Linux {
397 ticks: leader.ticks,
398 },
399 })
400 } else {
401 ProcessObservation::Unknown
402 };
403 }
404 }
405 ProcessObservation::Unknown
406}
407
408#[cfg(target_os = "macos")]
409pub fn observe_process(pid: u32) -> ProcessObservation {
410 if pid <= 1 || i32::try_from(pid).is_err() {
411 return ProcessObservation::Unknown;
412 }
413 let Some(boot) = boot_identity() else {
414 return ProcessObservation::Unknown;
415 };
416 match libproc::proc_pid::pidinfo::<libproc::bsd_info::BSDInfo>(pid as i32, 0) {
417 Ok(info) => darwin_info(
418 pid,
419 info.pbi_pid,
420 info.pbi_status,
421 info.pbi_start_tvsec,
422 info.pbi_start_tvusec,
423 &boot,
424 ),
425 Err(_) => {
426 kernel_absence(pid)
429 }
430 }
431}
432
433#[cfg(any(target_os = "macos", test))]
434fn darwin_info(
435 requested: u32,
436 actual: u32,
437 state: u32,
438 seconds: u64,
439 microseconds: u64,
440 boot: &str,
441) -> ProcessObservation {
442 if actual != requested || seconds == 0 || microseconds >= 1_000_000 {
443 return ProcessObservation::Unknown;
444 }
445 match state {
448 5 => ProcessObservation::Absent,
449 1..=4 => ProcessObservation::Present(ProcessIdentity {
450 boot_id: boot.into(),
451 start: ProcessStart::Darwin {
452 seconds,
453 microseconds: microseconds as u32,
454 },
455 }),
456 _ => ProcessObservation::Unknown,
457 }
458}
459
460#[cfg(not(any(target_os = "linux", target_os = "macos")))]
461pub fn observe_process(_pid: u32) -> ProcessObservation {
462 ProcessObservation::Unknown
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 const BOOT: &str = "3f1c2a9e-5b7d-4e2a-9c1f-0a1b2c3d4e5f";
470
471 #[test]
472 fn native_precision_survives_records_and_rejects_second_resolution_history() {
473 let linux = format!("{BOOT}:123456789");
474 let darwin = format!("{BOOT}:darwin:1791280000:123456");
475 for record in [&linux, &darwin] {
476 let identity = ProcessIdentity::from_record(record).unwrap();
477 assert_eq!(identity.to_record().as_ref(), Some(record));
478 assert_eq!(
479 serde_json::from_str::<ProcessIdentity>(&serde_json::to_string(&identity).unwrap())
480 .unwrap(),
481 identity
482 );
483 }
484 for record in [
485 format!("{BOOT}:Mon Sep 28 10:48:17 2026"),
486 format!("{BOOT}:darwin:1791280000:1000000"),
487 format!("{BOOT}:darwin:0:123"),
488 format!("{BOOT}:darwin:1791280000:123:4"),
489 format!("{BOOT}:+123"),
490 "unverified-boot:123".into(),
491 format!("{}:123", BOOT.to_uppercase()),
492 ] {
493 assert!(ProcessIdentity::from_record(&record).is_none(), "{record}");
494 }
495 }
496
497 #[test]
498 fn matching_distinguishes_microseconds_reboots_and_unknown_observation() {
499 let owner =
500 ProcessIdentity::from_record(&format!("{BOOT}:darwin:1791280000:123456")).unwrap();
501 assert_eq!(
502 owner.presence(&ProcessObservation::Present(owner.clone())),
503 OwnerPresence::Live
504 );
505 let mut reused = owner.clone();
506 reused.start = ProcessStart::Darwin {
507 seconds: 1791280000,
508 microseconds: 123457,
509 };
510 assert_eq!(
511 owner.presence(&ProcessObservation::Present(reused)),
512 OwnerPresence::Absent
513 );
514 let mut rebooted = owner.clone();
515 rebooted.boot_id = "00000000-0000-0000-0000-000000000001".into();
516 assert_eq!(
517 owner.presence(&ProcessObservation::Present(rebooted)),
518 OwnerPresence::Absent
519 );
520 assert_eq!(
521 owner.presence(&ProcessObservation::Unknown),
522 OwnerPresence::Unknown
523 );
524 assert_eq!(
525 owner.presence(&ProcessObservation::Absent),
526 OwnerPresence::Absent
527 );
528 let linux = ProcessIdentity::from_record(&format!("{BOOT}:123")).unwrap();
529 assert_eq!(
530 owner.presence(&ProcessObservation::Present(linux)),
531 OwnerPresence::Unknown
532 );
533 }
534
535 #[test]
536 fn absent_pid_is_independent_proof_but_unknown_or_legacy_birth_cannot_adopt_a_live_pid() {
537 let current =
538 ProcessIdentity::from_record(&format!("{BOOT}:darwin:1791280000:123456")).unwrap();
539 let legacy = ProcessIdentity::from_record(&format!("{BOOT}:Mon Sep 28 10:48:17 2026"));
540 assert!(legacy.is_none());
541 for expected in [None, legacy.as_ref(), Some(¤t)] {
542 assert_eq!(
543 observed_owner_presence(expected, &ProcessObservation::Absent),
544 OwnerPresence::Absent
545 );
546 assert_eq!(
547 observed_owner_presence(expected, &ProcessObservation::Unknown),
548 OwnerPresence::Unknown
549 );
550 }
551 assert_eq!(
552 observed_owner_presence(None, &ProcessObservation::Present(current)),
553 OwnerPresence::Unknown
554 );
555 }
556
557 #[test]
558 fn proc_stat_handles_command_parentheses_and_refuses_wrong_pid_namespace() {
559 let stat = |state| linux_record(123, state, "1", 42);
560 let expected = ProcessIdentity::from_record(&format!("{BOOT}:42")).unwrap();
561 assert_eq!(
562 expected.presence(&linux_stat(&stat("S"), 123, BOOT)),
563 OwnerPresence::Live
564 );
565 for state in ["Z", "X", "x"] {
566 assert_eq!(
567 linux_stat(&stat(state), 123, BOOT),
568 ProcessObservation::Absent
569 );
570 }
571 assert_eq!(
572 linux_stat(&stat("S"), 456, BOOT),
573 ProcessObservation::Unknown
574 );
575 assert_eq!(
576 linux_stat(&stat("?"), 123, BOOT),
577 ProcessObservation::Unknown
578 );
579 assert_eq!(
580 linux_stat("truncated", 123, BOOT),
581 ProcessObservation::Unknown
582 );
583 }
584
585 fn linux_record(pid: u32, state: &str, threads: &str, ticks: u64) -> String {
586 format!(
587 "{pid} (rch (wrapper)) {state} {} {threads} 0 {ticks}",
588 vec!["0"; 16].join(" ")
589 )
590 }
591
592 #[test]
593 fn namespace_validation_rejects_coincident_and_ambiguous_ids() {
594 let stat = linux_record(123, "S", "1", 42);
595 assert!(linux_namespace_matches(
596 123,
597 &stat,
598 "Name:\trch\nNSpid:\t123\n"
599 ));
600 for status in [
601 "NSpid:\t123\t123\n",
602 "NSpid:\t123\t7\n",
603 "NSpid:\t999\t123\n",
604 "NSpid:\t999\n",
605 "NSpid:\t123\nNSpid:\t123\n",
606 "NSpid:\n",
607 "NSpid:\t+123\n",
608 "NSpid:\t123 invalid\n",
609 "Name:\trch\n",
610 "",
611 ] {
612 assert!(!linux_namespace_matches(123, &stat, status), "{status:?}");
613 }
614 assert!(!linux_namespace_matches(456, &stat, "NSpid:\t456\n"));
615 assert!(!linux_namespace_matches(123, "truncated", "NSpid:\t123\n"));
616 }
617
618 #[cfg(target_os = "linux")]
619 #[test]
620 fn a_missing_proc_entry_cannot_release_a_live_owner() {
621 let hidden = tempfile::tempdir().unwrap();
622 let pid = std::process::id();
623 let expected = current_process_identity().expect("native observer identity");
624 let observed = observe_linux_process(hidden.path(), pid, &expected.boot_id);
627 assert_eq!(observed, ProcessObservation::Unknown);
628 assert_eq!(expected.presence(&observed), OwnerPresence::Unknown);
629 assert_eq!(
630 observed_owner_presence(None, &observed),
631 OwnerPresence::Unknown
632 );
633 }
634
635 #[cfg(any(target_os = "linux", target_os = "macos"))]
636 #[test]
637 fn signal_free_probe_never_supplies_a_live_birth_identity() {
638 assert_eq!(
639 kernel_absence(std::process::id()),
640 ProcessObservation::Unknown
641 );
642 for invalid in [0, 1, u32::MAX] {
643 assert_eq!(kernel_absence(invalid), ProcessObservation::Unknown);
644 }
645 let mut child = std::process::Command::new("/bin/sleep")
646 .arg("20")
647 .spawn()
648 .unwrap();
649 let observed = kernel_absence(child.id());
650 child.kill().unwrap();
651 child.wait().unwrap();
652 assert_eq!(observed, ProcessObservation::Unknown);
653 assert_eq!(kernel_absence(child.id()), ProcessObservation::Absent);
654 }
655
656 #[test]
657 fn a_terminal_leader_with_other_threads_is_not_an_absent_owner() {
658 for state in ["Z", "X", "x"] {
659 assert_eq!(
660 linux_stat(&linux_record(123, state, "2", 42), 123, BOOT),
661 ProcessObservation::Unknown
662 );
663 }
664 for threads in ["0", "-1", "invalid", "4294967296"] {
665 for state in ["Z", "S"] {
666 assert_eq!(
667 linux_stat(&linux_record(123, state, threads, 42), 123, BOOT),
668 ProcessObservation::Unknown
669 );
670 }
671 }
672 }
673
674 #[cfg(target_os = "linux")]
675 fn thread_fixture() -> (tempfile::TempDir, LinuxTaskStat) {
676 let directory = tempfile::tempdir().unwrap();
677 let stat = linux_record(123, "Z", "2", 42);
678 std::fs::write(directory.path().join("stat"), &stat).unwrap();
679 let leader = parse_linux_stat(&stat, 123).unwrap();
680 (directory, leader)
681 }
682
683 #[cfg(target_os = "linux")]
684 fn write_task(process: &std::path::Path, tid: u32, state: &str) {
685 let directory = process.join("task").join(tid.to_string());
686 std::fs::create_dir_all(&directory).unwrap();
687 std::fs::write(directory.join("stat"), linux_record(tid, state, "2", 900)).unwrap();
688 }
689
690 #[cfg(target_os = "linux")]
691 #[test]
692 fn surviving_thread_retains_the_group_leaders_birth_identity() {
693 let (directory, leader) = thread_fixture();
694 write_task(directory.path(), 456, "S");
695 let observed = observe_linux_threads(directory.path(), 123, BOOT, leader);
696 let expected = ProcessIdentity::from_record(&format!("{BOOT}:42")).unwrap();
697 assert_eq!(observed, ProcessObservation::Present(expected.clone()));
698 assert_eq!(expected.presence(&observed), OwnerPresence::Live);
699 assert_eq!(
700 observed_owner_presence(None, &observed),
701 OwnerPresence::Unknown
702 );
703 }
704
705 #[cfg(target_os = "linux")]
706 #[test]
707 fn incomplete_and_dead_task_inventories_never_authorize_release() {
708 let (directory, leader) = thread_fixture();
709 let observe = || observe_linux_threads(directory.path(), 123, BOOT, leader);
710 assert_eq!(observe(), ProcessObservation::Unknown);
711 std::fs::create_dir(directory.path().join("task")).unwrap();
712 assert_eq!(observe(), ProcessObservation::Unknown);
713 write_task(directory.path(), 123, "Z");
714 assert_eq!(observe(), ProcessObservation::Unknown);
715 write_task(directory.path(), 456, "Z");
716 assert_eq!(observe(), ProcessObservation::Unknown);
717 std::fs::write(directory.path().join("task/456/stat"), "truncated").unwrap();
718 assert_eq!(observe(), ProcessObservation::Unknown);
719 }
720
721 #[cfg(target_os = "linux")]
722 #[test]
723 fn a_reused_leader_cannot_inherit_a_threads_liveness_evidence() {
724 let (directory, leader) = thread_fixture();
725 write_task(directory.path(), 456, "S");
726 std::fs::write(
727 directory.path().join("stat"),
728 linux_record(123, "S", "2", 43),
729 )
730 .unwrap();
731 assert_eq!(
732 observe_linux_threads(directory.path(), 123, BOOT, leader),
733 ProcessObservation::Unknown
734 );
735 }
736
737 #[test]
738 fn darwin_kernel_records_validate_pid_status_and_fractional_time() {
739 assert!(matches!(
740 darwin_info(42, 42, 2, 1791280000, 123456, BOOT),
741 ProcessObservation::Present(_)
742 ));
743 assert_eq!(
744 darwin_info(42, 42, 5, 1791280000, 123456, BOOT),
745 ProcessObservation::Absent
746 );
747 for (pid, state, seconds, micros) in [
748 (43, 2, 1791280000, 123456),
749 (42, 99, 1791280000, 123456),
750 (42, 2, 0, 1),
751 (42, 2, 1791280000, 1000000),
752 ] {
753 assert_eq!(
754 darwin_info(42, pid, state, seconds, micros, BOOT),
755 ProcessObservation::Unknown
756 );
757 }
758 }
759
760 #[cfg(target_os = "macos")]
761 #[test]
762 fn darwin_native_birth_is_stable_and_a_reaped_child_is_absent() {
763 let own = current_process_identity().expect("native own process identity");
764 assert!(matches!(own.start, ProcessStart::Darwin { .. }));
765 assert_eq!(current_process_identity(), Some(own.clone()));
766 let mut child = std::process::Command::new("/bin/sleep")
767 .arg("30")
768 .spawn()
769 .unwrap();
770 let observed = observe_process(child.id());
771 let witness = remote_process_witness(child.id()).expect("native shell group witness");
772 let fields: Vec<_> = witness.split_whitespace().collect();
773 assert_eq!(fields.len(), 3);
774 assert_eq!(fields[0], own.boot_id);
775 let ProcessObservation::Present(ref identity) = observed else {
776 panic!("live child identity unavailable: {observed:?}");
777 };
778 let ProcessStart::Darwin {
779 seconds,
780 microseconds,
781 } = &identity.start
782 else {
783 panic!("wrong platform identity");
784 };
785 assert_eq!(
786 fields[1].parse::<u64>().unwrap(),
787 *seconds * 1_000_000 + u64::from(*microseconds)
788 );
789 child.kill().unwrap();
790 child.wait().unwrap();
791 assert!(
792 matches!(observed, ProcessObservation::Present(_)),
793 "{observed:?}"
794 );
795 assert_eq!(observe_process(child.id()), ProcessObservation::Absent);
796 assert!(remote_process_witness(child.id()).is_none());
797 }
798}