1use crate::user::ProcessIdentity;
2use std::collections::{BTreeMap, BTreeSet, VecDeque};
3use std::error::Error;
4use std::fmt;
5use std::ops::{BitOr, BitOrAssign};
6use std::sync::atomic::{AtomicUsize, Ordering};
7use std::sync::{Arc, Condvar, Mutex, MutexGuard, WaitTimeoutResult, Weak};
8use std::thread;
9use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
10
11const ZOMBIE_TTL: Duration = Duration::from_secs(60);
12const INIT_PID: u32 = 1;
13pub const DEFAULT_PROCESS_UMASK: u32 = 0o022;
14pub const SIGHUP: i32 = 1;
15pub const SIGCHLD: i32 = 17;
16pub const SIGCONT: i32 = 18;
17pub const SIGSTOP: i32 = 19;
18pub const SIGTSTP: i32 = 20;
19pub const SIGTERM: i32 = 15;
20pub const SIGKILL: i32 = 9;
21pub const SIGPIPE: i32 = 13;
22pub const SIGWINCH: i32 = 28;
23const MAX_SIGNAL: i32 = 64;
24
25pub type ProcessResult<T> = Result<T, ProcessTableError>;
26pub type ProcessExitCallback = Arc<dyn Fn(i32) + Send + Sync + 'static>;
27
28pub trait DriverProcess: Send + Sync {
29 fn kill(&self, signal: i32);
30 fn wait(&self, timeout: Duration) -> Option<i32>;
31 fn set_on_exit(&self, callback: ProcessExitCallback);
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct ProcessTableError {
36 code: &'static str,
37 message: String,
38}
39
40impl ProcessTableError {
41 pub fn code(&self) -> &'static str {
42 self.code
43 }
44
45 fn invalid_signal(signal: i32) -> Self {
46 Self {
47 code: "EINVAL",
48 message: format!("invalid signal {signal}"),
49 }
50 }
51
52 fn no_such_process(pid: u32) -> Self {
53 Self {
54 code: "ESRCH",
55 message: format!("no such process {pid}"),
56 }
57 }
58
59 fn no_such_process_group(pgid: u32) -> Self {
60 Self {
61 code: "ESRCH",
62 message: format!("no such process group {pgid}"),
63 }
64 }
65
66 fn no_matching_child(waiter_pid: u32, pid: i32) -> Self {
67 Self {
68 code: "ECHILD",
69 message: format!("process {waiter_pid} has no matching child for waitpid({pid})"),
70 }
71 }
72
73 fn permission_denied(message: impl Into<String>) -> Self {
74 Self {
75 code: "EPERM",
76 message: message.into(),
77 }
78 }
79}
80
81impl fmt::Display for ProcessTableError {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(f, "{}: {}", self.code, self.message)
84 }
85}
86
87impl Error for ProcessTableError {}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum ProcessStatus {
91 Running,
92 Stopped,
93 Exited,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
97pub struct SignalSet {
98 bits: u64,
99}
100
101impl SignalSet {
102 pub const fn empty() -> Self {
103 Self { bits: 0 }
104 }
105
106 pub const fn is_empty(self) -> bool {
107 self.bits == 0
108 }
109
110 pub fn from_signal(signal: i32) -> ProcessResult<Self> {
111 Ok(Self {
112 bits: signal_bit(signal)?,
113 })
114 }
115
116 pub fn from_signals(signals: impl IntoIterator<Item = i32>) -> ProcessResult<Self> {
117 let mut set = Self::empty();
118 for signal in signals {
119 set.insert(signal)?;
120 }
121 Ok(set)
122 }
123
124 pub fn contains(self, signal: i32) -> bool {
125 signal_bit(signal)
126 .map(|bit| self.bits & bit != 0)
127 .unwrap_or(false)
128 }
129
130 pub fn insert(&mut self, signal: i32) -> ProcessResult<()> {
131 self.bits |= signal_bit(signal)?;
132 Ok(())
133 }
134
135 pub fn remove(&mut self, signal: i32) -> ProcessResult<()> {
136 self.bits &= !signal_bit(signal)?;
137 Ok(())
138 }
139
140 pub fn union(self, other: Self) -> Self {
141 Self {
142 bits: self.bits | other.bits,
143 }
144 }
145
146 pub fn difference(self, other: Self) -> Self {
147 Self {
148 bits: self.bits & !other.bits,
149 }
150 }
151
152 pub fn signals(self) -> Vec<i32> {
153 let mut signals = Vec::new();
154 for signal in 1..=MAX_SIGNAL {
155 if self.contains(signal) {
156 signals.push(signal);
157 }
158 }
159 signals
160 }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum SigmaskHow {
165 Block,
166 Unblock,
167 SetMask,
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct WaitPidFlags {
172 bits: u32,
173}
174
175impl WaitPidFlags {
176 pub const WNOHANG: Self = Self { bits: 1 << 0 };
177 pub const WUNTRACED: Self = Self { bits: 1 << 1 };
178 pub const WCONTINUED: Self = Self { bits: 1 << 2 };
179
180 pub const fn empty() -> Self {
181 Self { bits: 0 }
182 }
183
184 pub const fn contains(self, other: Self) -> bool {
185 (self.bits & other.bits) == other.bits
186 }
187}
188
189impl Default for WaitPidFlags {
190 fn default() -> Self {
191 Self::empty()
192 }
193}
194
195impl BitOr for WaitPidFlags {
196 type Output = Self;
197
198 fn bitor(self, rhs: Self) -> Self::Output {
199 Self {
200 bits: self.bits | rhs.bits,
201 }
202 }
203}
204
205impl BitOrAssign for WaitPidFlags {
206 fn bitor_assign(&mut self, rhs: Self) {
207 self.bits |= rhs.bits;
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub enum ProcessWaitEvent {
213 Exited,
214 Stopped,
215 Continued,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct ProcessWaitResult {
220 pub pid: u32,
221 pub status: i32,
222 pub event: ProcessWaitEvent,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct ProcessFileDescriptors {
227 pub stdin: u32,
228 pub stdout: u32,
229 pub stderr: u32,
230}
231
232impl Default for ProcessFileDescriptors {
233 fn default() -> Self {
234 Self {
235 stdin: 0,
236 stdout: 1,
237 stderr: 2,
238 }
239 }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub struct ProcessContext {
244 pub pid: u32,
245 pub ppid: u32,
246 pub env: BTreeMap<String, String>,
247 pub cwd: String,
248 pub umask: u32,
249 pub fds: ProcessFileDescriptors,
250 pub identity: ProcessIdentity,
251 pub blocked_signals: SignalSet,
252 pub pending_signals: SignalSet,
253}
254
255impl Default for ProcessContext {
256 fn default() -> Self {
257 Self {
258 pid: 0,
259 ppid: 0,
260 env: BTreeMap::new(),
261 cwd: String::from("/"),
262 umask: DEFAULT_PROCESS_UMASK,
263 fds: ProcessFileDescriptors::default(),
264 identity: ProcessIdentity::default(),
265 blocked_signals: SignalSet::empty(),
266 pending_signals: SignalSet::empty(),
267 }
268 }
269}
270
271#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct ProcessEntry {
273 pub pid: u32,
274 pub ppid: u32,
275 pub pgid: u32,
276 pub sid: u32,
277 pub driver: String,
278 pub command: String,
279 pub args: Vec<String>,
280 pub status: ProcessStatus,
281 pub exit_code: Option<i32>,
282 pub exit_time_ms: Option<u64>,
283 pub env: BTreeMap<String, String>,
284 pub cwd: String,
285 pub umask: u32,
286 pub identity: ProcessIdentity,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub struct ProcessInfo {
291 pub pid: u32,
292 pub ppid: u32,
293 pub pgid: u32,
294 pub sid: u32,
295 pub driver: String,
296 pub command: String,
297 pub status: ProcessStatus,
298 pub exit_code: Option<i32>,
299 pub identity: ProcessIdentity,
300}
301
302#[derive(Clone)]
303pub struct ProcessTable {
304 inner: Arc<ProcessTableInner>,
305}
306
307struct ProcessTableInner {
308 state: Mutex<ProcessTableState>,
309 waiters: Condvar,
310 reaper: Arc<ZombieReaper>,
311}
312
313struct ProcessRecord {
314 entry: ProcessEntry,
315 driver_process: Arc<dyn DriverProcess>,
316 pending_wait_events: VecDeque<PendingWaitEvent>,
317 blocked_signals: SignalSet,
318 pending_signals: SignalSet,
319}
320
321struct ScheduledSignalDelivery {
322 pid: u32,
323 signal: i32,
324 status: ProcessStatus,
325 driver_process: Arc<dyn DriverProcess>,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329struct PendingWaitEvent {
330 status: i32,
331 event: ProcessWaitEvent,
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq)]
335enum WaitSelector {
336 AnyChild,
337 ChildPid(u32),
338 ProcessGroup(u32),
339}
340
341struct ZombieReaper {
342 state: Mutex<ZombieReaperState>,
343 wake: Condvar,
344 thread_spawns: AtomicUsize,
345}
346
347#[derive(Default)]
348struct ZombieReaperState {
349 deadlines: BTreeMap<u32, Instant>,
350 shutdown: bool,
351}
352
353struct ProcessTableState {
354 entries: BTreeMap<u32, ProcessRecord>,
355 next_pid: u32,
356 zombie_ttl: Duration,
357 on_process_exit: Option<Arc<dyn Fn(u32) + Send + Sync + 'static>>,
358 terminating_all: bool,
359}
360
361impl Default for ProcessTableState {
362 fn default() -> Self {
363 Self {
364 entries: BTreeMap::new(),
365 next_pid: 1,
366 zombie_ttl: ZOMBIE_TTL,
367 on_process_exit: None,
368 terminating_all: false,
369 }
370 }
371}
372
373impl Default for ProcessTable {
374 fn default() -> Self {
375 let reaper = Arc::new(ZombieReaper::default());
376 Self {
377 inner: {
378 let inner = Arc::new(ProcessTableInner {
379 state: Mutex::new(ProcessTableState::default()),
380 waiters: Condvar::new(),
381 reaper,
382 });
383 start_zombie_reaper(Arc::downgrade(&inner), Arc::clone(&inner.reaper));
384 inner
385 },
386 }
387 }
388}
389
390impl ProcessTable {
391 pub fn new() -> Self {
392 Self::default()
393 }
394
395 pub fn with_zombie_ttl(zombie_ttl: Duration) -> Self {
396 let table = Self::new();
397 table.inner.lock_state().zombie_ttl = zombie_ttl;
398 table
399 }
400
401 pub fn allocate_pid(&self) -> u32 {
402 let mut state = self.inner.lock_state();
403 let pid = state.next_pid;
404 state.next_pid += 1;
405 pid
406 }
407
408 pub fn set_on_process_exit(&self, callback: Option<Arc<dyn Fn(u32) + Send + Sync + 'static>>) {
409 self.inner.lock_state().on_process_exit = callback;
410 }
411
412 pub fn register(
413 &self,
414 pid: u32,
415 driver: impl Into<String>,
416 command: impl Into<String>,
417 args: Vec<String>,
418 ctx: ProcessContext,
419 driver_process: Arc<dyn DriverProcess>,
420 ) -> ProcessEntry {
421 let (pgid, sid) = {
422 let state = self.inner.lock_state();
423 match state.entries.get(&ctx.ppid) {
424 Some(parent) => (parent.entry.pgid, parent.entry.sid),
425 None => (pid, pid),
426 }
427 };
428
429 let entry = ProcessEntry {
430 pid,
431 ppid: ctx.ppid,
432 pgid,
433 sid,
434 driver: driver.into(),
435 command: command.into(),
436 args,
437 status: ProcessStatus::Running,
438 exit_code: None,
439 exit_time_ms: None,
440 env: ctx.env,
441 cwd: ctx.cwd,
442 umask: ctx.umask & 0o777,
443 identity: ctx.identity,
444 };
445
446 let weak = Arc::downgrade(&self.inner);
447 driver_process.set_on_exit(Arc::new(move |code| {
448 if let Some(inner) = weak.upgrade() {
449 mark_exited_inner(&inner, pid, code);
450 }
451 }));
452
453 self.inner.lock_state().entries.insert(
454 pid,
455 ProcessRecord {
456 entry: entry.clone(),
457 driver_process,
458 pending_wait_events: VecDeque::new(),
459 blocked_signals: ctx.blocked_signals,
460 pending_signals: ctx.pending_signals,
461 },
462 );
463
464 entry
465 }
466
467 pub fn get(&self, pid: u32) -> Option<ProcessEntry> {
468 self.inner
469 .lock_state()
470 .entries
471 .get(&pid)
472 .map(|record| record.entry.clone())
473 }
474
475 pub fn zombie_timer_count(&self) -> usize {
476 self.inner.reaper.scheduled_count()
477 }
478
479 pub fn zombie_reaper_thread_spawn_count(&self) -> usize {
480 self.inner.reaper.thread_spawn_count()
481 }
482
483 pub fn running_count(&self) -> usize {
484 self.inner
485 .lock_state()
486 .entries
487 .values()
488 .filter(|record| record.entry.status == ProcessStatus::Running)
489 .count()
490 }
491
492 pub fn mark_exited(&self, pid: u32, exit_code: i32) {
493 mark_exited_inner(&self.inner, pid, exit_code);
494 }
495
496 pub fn mark_stopped(&self, pid: u32, signal: i32) {
497 mark_wait_event_inner(
498 &self.inner,
499 pid,
500 ProcessStatus::Stopped,
501 PendingWaitEvent {
502 status: signal,
503 event: ProcessWaitEvent::Stopped,
504 },
505 );
506 }
507
508 pub fn mark_continued(&self, pid: u32) {
509 mark_wait_event_inner(
510 &self.inner,
511 pid,
512 ProcessStatus::Running,
513 PendingWaitEvent {
514 status: SIGCONT,
515 event: ProcessWaitEvent::Continued,
516 },
517 );
518 }
519
520 pub fn waitpid(&self, pid: u32) -> ProcessResult<(u32, i32)> {
521 let mut state = self.inner.lock_state();
522 loop {
523 let Some(record) = state.entries.get(&pid) else {
524 return Err(ProcessTableError::no_such_process(pid));
525 };
526
527 if record.entry.status == ProcessStatus::Exited {
528 let status = record.entry.exit_code.unwrap_or_default();
529 state.entries.remove(&pid);
530 drop(state);
531 self.inner.reaper.cancel(pid);
532 self.inner.waiters.notify_all();
533 return Ok((pid, status));
534 }
535
536 state = self.inner.wait_for_state(state);
537 }
538 }
539
540 pub fn waitpid_for(
541 &self,
542 waiter_pid: u32,
543 pid: i32,
544 flags: WaitPidFlags,
545 ) -> ProcessResult<Option<ProcessWaitResult>> {
546 let mut state = self.inner.lock_state();
547 loop {
548 let selector = resolve_wait_selector(&state, waiter_pid, pid)?;
549 let matching_children = matching_child_pids(&state, waiter_pid, selector);
550 if matching_children.is_empty() {
551 return Err(ProcessTableError::no_matching_child(waiter_pid, pid));
552 }
553
554 if let Some(result) = take_waitable_event(&mut state, &matching_children, flags) {
555 let should_reap = result.event == ProcessWaitEvent::Exited;
556 drop(state);
557 if should_reap {
558 self.inner.reaper.cancel(result.pid);
559 self.inner.waiters.notify_all();
560 }
561 return Ok(Some(result));
562 }
563
564 if flags.contains(WaitPidFlags::WNOHANG) {
565 return Ok(None);
566 }
567
568 state = self.inner.wait_for_state(state);
569 }
570 }
571
572 pub fn kill(&self, pid: i32, signal: i32) -> ProcessResult<()> {
573 if !(0..=MAX_SIGNAL).contains(&signal) {
574 return Err(ProcessTableError::invalid_signal(signal));
575 }
576
577 let deliveries = {
578 let mut state = self.inner.lock_state();
579 if pid < 0 {
580 let pgid = pid.unsigned_abs();
581 let grouped = state
582 .entries
583 .values()
584 .filter(|record| record.entry.pgid == pgid)
585 .map(|record| record.entry.pid)
586 .collect::<Vec<_>>();
587 if grouped.is_empty() {
588 return Err(ProcessTableError::no_such_process_group(pgid));
589 }
590 collect_signal_deliveries(&mut state, &grouped, signal)?
591 } else {
592 let pid = pid as u32;
593 let Some(record) = state.entries.get(&pid) else {
594 return Err(ProcessTableError::no_such_process(pid));
595 };
596 if record.entry.status == ProcessStatus::Exited || signal == 0 {
597 return Ok(());
598 }
599 collect_signal_deliveries(&mut state, &[pid], signal)?
600 }
601 };
602
603 if signal == 0 {
604 return Ok(());
605 }
606
607 deliver_signals(&self.inner, deliveries);
608 Ok(())
609 }
610
611 pub fn setpgid(&self, pid: u32, pgid: u32) -> ProcessResult<()> {
612 let mut state = self.inner.lock_state();
613 let (current_sid, target_pgid) = {
614 let Some(record) = state.entries.get(&pid) else {
615 return Err(ProcessTableError::no_such_process(pid));
616 };
617 (record.entry.sid, if pgid == 0 { pid } else { pgid })
618 };
619
620 if target_pgid != pid {
621 let mut group_exists = false;
622 for record in state.entries.values() {
623 if record.entry.pgid != target_pgid || record.entry.status == ProcessStatus::Exited
624 {
625 continue;
626 }
627 if record.entry.sid != current_sid {
628 return Err(ProcessTableError::permission_denied(
629 "cannot join process group in different session",
630 ));
631 }
632 group_exists = true;
633 break;
634 }
635 if !group_exists {
636 return Err(ProcessTableError::permission_denied(format!(
637 "no such process group {target_pgid}"
638 )));
639 }
640 }
641
642 if let Some(record) = state.entries.get_mut(&pid) {
643 record.entry.pgid = target_pgid;
644 }
645 Ok(())
646 }
647
648 pub fn getpgid(&self, pid: u32) -> ProcessResult<u32> {
649 self.get(pid)
650 .map(|entry| entry.pgid)
651 .ok_or_else(|| ProcessTableError::no_such_process(pid))
652 }
653
654 pub fn setsid(&self, pid: u32) -> ProcessResult<u32> {
655 let mut state = self.inner.lock_state();
656 let Some(record) = state.entries.get_mut(&pid) else {
657 return Err(ProcessTableError::no_such_process(pid));
658 };
659
660 if record.entry.pgid == pid {
661 return Err(ProcessTableError::permission_denied(format!(
662 "process {pid} is already a process group leader"
663 )));
664 }
665
666 record.entry.sid = pid;
667 record.entry.pgid = pid;
668 Ok(pid)
669 }
670
671 pub fn getsid(&self, pid: u32) -> ProcessResult<u32> {
672 self.get(pid)
673 .map(|entry| entry.sid)
674 .ok_or_else(|| ProcessTableError::no_such_process(pid))
675 }
676
677 pub fn getppid(&self, pid: u32) -> ProcessResult<u32> {
678 self.get(pid)
679 .map(|entry| entry.ppid)
680 .ok_or_else(|| ProcessTableError::no_such_process(pid))
681 }
682
683 pub fn get_umask(&self, pid: u32) -> ProcessResult<u32> {
684 self.get(pid)
685 .map(|entry| entry.umask)
686 .ok_or_else(|| ProcessTableError::no_such_process(pid))
687 }
688
689 pub fn set_umask(&self, pid: u32, umask: u32) -> ProcessResult<u32> {
690 let mut state = self.inner.lock_state();
691 let record = state
692 .entries
693 .get_mut(&pid)
694 .ok_or_else(|| ProcessTableError::no_such_process(pid))?;
695 let previous = record.entry.umask;
696 record.entry.umask = umask & 0o777;
697 Ok(previous)
698 }
699
700 pub fn has_process_group(&self, pgid: u32) -> bool {
701 self.inner
702 .lock_state()
703 .entries
704 .values()
705 .any(|record| record.entry.pgid == pgid && record.entry.status != ProcessStatus::Exited)
706 }
707
708 pub fn list_processes(&self) -> BTreeMap<u32, ProcessInfo> {
709 self.inner
710 .lock_state()
711 .entries
712 .values()
713 .map(|record| (record.entry.pid, to_process_info(&record.entry)))
714 .collect()
715 }
716
717 pub fn terminate_all(&self) {
718 let running = {
719 let mut state = self.inner.lock_state();
720 state.terminating_all = true;
721 self.inner.reaper.clear();
722 state
723 .entries
724 .values()
725 .filter(|record| record.entry.status == ProcessStatus::Running)
726 .map(|record| (record.entry.pid, Arc::clone(&record.driver_process)))
727 .collect::<Vec<_>>()
728 };
729
730 for (_, driver) in &running {
731 driver.kill(SIGTERM);
732 }
733 for (pid, driver) in &running {
734 if let Some(exit_code) = driver.wait(Duration::from_secs(1)) {
735 self.mark_exited(*pid, exit_code);
736 }
737 }
738
739 let survivors = {
740 let state = self.inner.lock_state();
741 running
742 .iter()
743 .filter(|(pid, _)| {
744 state
745 .entries
746 .get(pid)
747 .map(|record| record.entry.status == ProcessStatus::Running)
748 .unwrap_or(false)
749 })
750 .cloned()
751 .collect::<Vec<_>>()
752 };
753
754 for (_, driver) in &survivors {
755 driver.kill(SIGKILL);
756 }
757 for (pid, driver) in &survivors {
758 if let Some(exit_code) = driver.wait(Duration::from_millis(500)) {
759 self.mark_exited(*pid, exit_code);
760 }
761 }
762
763 self.inner.lock_state().terminating_all = false;
764 }
765
766 pub fn sigprocmask(
767 &self,
768 pid: u32,
769 how: SigmaskHow,
770 set: SignalSet,
771 ) -> ProcessResult<SignalSet> {
772 let (previous, deliveries) = {
773 let mut state = self.inner.lock_state();
774 let record = state
775 .entries
776 .get_mut(&pid)
777 .ok_or_else(|| ProcessTableError::no_such_process(pid))?;
778 let previous = record.blocked_signals;
779 record.blocked_signals = match how {
780 SigmaskHow::Block => previous.union(set),
781 SigmaskHow::Unblock => previous.difference(set),
782 SigmaskHow::SetMask => set,
783 };
784
785 let unblocked_pending = record.pending_signals.difference(record.blocked_signals);
786 let deliveries = collect_pending_signal_deliveries(record, unblocked_pending)?;
787 (previous, deliveries)
788 };
789
790 deliver_signals(&self.inner, deliveries);
791 Ok(previous)
792 }
793
794 pub fn sigpending(&self, pid: u32) -> ProcessResult<SignalSet> {
795 self.inner
796 .lock_state()
797 .entries
798 .get(&pid)
799 .map(|record| record.pending_signals)
800 .ok_or_else(|| ProcessTableError::no_such_process(pid))
801 }
802}
803
804fn to_process_info(entry: &ProcessEntry) -> ProcessInfo {
805 ProcessInfo {
806 pid: entry.pid,
807 ppid: entry.ppid,
808 pgid: entry.pgid,
809 sid: entry.sid,
810 driver: entry.driver.clone(),
811 command: entry.command.clone(),
812 status: entry.status,
813 exit_code: entry.exit_code,
814 identity: entry.identity.clone(),
815 }
816}
817
818fn mark_exited_inner(inner: &Arc<ProcessTableInner>, pid: u32, exit_code: i32) {
819 let (callback, zombie_ttl, should_schedule, deliveries) = {
820 let mut state = inner.lock_state();
821 let (ppid, pgid) = {
822 let Some(record) = state.entries.get_mut(&pid) else {
823 return;
824 };
825
826 if record.entry.status == ProcessStatus::Exited {
827 return;
828 }
829
830 record.entry.status = ProcessStatus::Exited;
831 record.entry.exit_code = Some(exit_code);
832 record.entry.exit_time_ms = Some(now_ms());
833 let ppid = record.entry.ppid;
834 let pgid = record.entry.pgid;
835 (ppid, pgid)
836 };
837 let mut affected_pgids = BTreeSet::from([pgid]);
838 reparent_children_to_init(&mut state, pid, &mut affected_pgids);
839
840 let orphaned_group_targets = collect_orphaned_group_signal_targets(&state, &affected_pgids);
841
842 let should_schedule = !state.terminating_all;
843 let mut deliveries = Vec::new();
844 if should_schedule {
845 if let Some(parent) = state
846 .entries
847 .get_mut(&ppid)
848 .filter(|parent| parent.entry.status == ProcessStatus::Running)
849 {
850 if let Some(delivery) =
851 queue_or_schedule_signal(parent, SIGCHLD).expect("SIGCHLD should be valid")
852 {
853 deliveries.push(delivery);
854 }
855 }
856 }
857
858 for target_pid in orphaned_group_targets {
859 if let Some(record) = state.entries.get_mut(&target_pid) {
860 if let Some(delivery) =
861 queue_or_schedule_signal(record, SIGHUP).expect("SIGHUP should be valid")
862 {
863 deliveries.push(delivery);
864 }
865 if let Some(delivery) =
866 queue_or_schedule_signal(record, SIGCONT).expect("SIGCONT should be valid")
867 {
868 deliveries.push(delivery);
869 }
870 }
871 }
872
873 (
874 state.on_process_exit.clone(),
875 state.zombie_ttl,
876 should_schedule,
877 deliveries,
878 )
879 };
880
881 if should_schedule {
882 inner.reaper.schedule(pid, zombie_ttl);
883 } else {
884 inner.reaper.cancel(pid);
885 }
886
887 deliver_signals(inner, deliveries);
888
889 if let Some(on_process_exit) = callback {
890 on_process_exit(pid);
891 }
892
893 inner.waiters.notify_all();
894}
895
896fn reparent_children_to_init(
897 state: &mut ProcessTableState,
898 exiting_pid: u32,
899 affected_pgids: &mut BTreeSet<u32>,
900) {
901 let new_parent = reparent_target_pid(state, exiting_pid);
902 for record in state.entries.values_mut() {
903 if record.entry.ppid != exiting_pid {
904 continue;
905 }
906 record.entry.ppid = new_parent;
907 affected_pgids.insert(record.entry.pgid);
908 }
909}
910
911fn reparent_target_pid(state: &ProcessTableState, exiting_pid: u32) -> u32 {
912 if exiting_pid != INIT_PID
913 && state
914 .entries
915 .get(&INIT_PID)
916 .map(|record| record.entry.status != ProcessStatus::Exited)
917 .unwrap_or(false)
918 {
919 INIT_PID
920 } else {
921 0
922 }
923}
924
925fn collect_orphaned_group_signal_targets(
926 state: &ProcessTableState,
927 candidate_pgids: &BTreeSet<u32>,
928) -> Vec<u32> {
929 let mut targets = Vec::new();
930 for &pgid in candidate_pgids {
931 if !process_group_is_orphaned(state, pgid) || !process_group_has_stopped_member(state, pgid)
932 {
933 continue;
934 }
935
936 for record in state.entries.values() {
937 if record.entry.pgid == pgid && record.entry.status != ProcessStatus::Exited {
938 targets.push(record.entry.pid);
939 }
940 }
941 }
942 targets
943}
944
945fn process_group_is_orphaned(state: &ProcessTableState, pgid: u32) -> bool {
946 let mut has_member = false;
947 for record in state.entries.values() {
948 if record.entry.pgid != pgid || record.entry.status == ProcessStatus::Exited {
949 continue;
950 }
951 has_member = true;
952 if has_parent_outside_group_in_same_session(state, &record.entry) {
953 return false;
954 }
955 }
956
957 has_member
958}
959
960fn has_parent_outside_group_in_same_session(
961 state: &ProcessTableState,
962 entry: &ProcessEntry,
963) -> bool {
964 match entry.ppid {
965 0 | INIT_PID => false,
966 ppid => state
967 .entries
968 .get(&ppid)
969 .map(|parent| {
970 parent.entry.status != ProcessStatus::Exited
971 && parent.entry.sid == entry.sid
972 && parent.entry.pgid != entry.pgid
973 })
974 .unwrap_or(false),
975 }
976}
977
978fn process_group_has_stopped_member(state: &ProcessTableState, pgid: u32) -> bool {
979 state
980 .entries
981 .values()
982 .any(|record| record.entry.pgid == pgid && record.entry.status == ProcessStatus::Stopped)
983}
984
985fn mark_wait_event_inner(
986 inner: &Arc<ProcessTableInner>,
987 pid: u32,
988 next_status: ProcessStatus,
989 event: PendingWaitEvent,
990) {
991 let deliveries = {
992 let mut state = inner.lock_state();
993 let ppid = {
994 let Some(record) = state.entries.get_mut(&pid) else {
995 return;
996 };
997
998 if record.entry.status == ProcessStatus::Exited || record.entry.status == next_status {
999 return;
1000 }
1001
1002 record.entry.status = next_status;
1003 record.pending_wait_events.push_back(event);
1004 record.entry.ppid
1005 };
1006
1007 state
1008 .entries
1009 .get_mut(&ppid)
1010 .filter(|parent| parent.entry.status == ProcessStatus::Running)
1011 .and_then(|parent| {
1012 queue_or_schedule_signal(parent, SIGCHLD)
1013 .expect("SIGCHLD should be valid")
1014 .into_iter()
1015 .next()
1016 })
1017 .into_iter()
1018 .collect::<Vec<_>>()
1019 };
1020
1021 deliver_signals(inner, deliveries);
1022
1023 inner.waiters.notify_all();
1024}
1025
1026fn signal_bit(signal: i32) -> ProcessResult<u64> {
1027 if !(1..=MAX_SIGNAL).contains(&signal) {
1028 return Err(ProcessTableError::invalid_signal(signal));
1029 }
1030 Ok(1u64 << (signal - 1))
1031}
1032
1033fn signal_can_be_blocked(signal: i32) -> bool {
1034 !matches!(signal, SIGKILL | SIGSTOP | SIGCONT)
1035}
1036
1037fn queue_or_schedule_signal(
1038 record: &mut ProcessRecord,
1039 signal: i32,
1040) -> ProcessResult<Option<ScheduledSignalDelivery>> {
1041 if signal_can_be_blocked(signal) && record.blocked_signals.contains(signal) {
1042 record.pending_signals.insert(signal)?;
1043 return Ok(None);
1044 }
1045
1046 Ok(Some(ScheduledSignalDelivery {
1047 pid: record.entry.pid,
1048 signal,
1049 status: record.entry.status,
1050 driver_process: Arc::clone(&record.driver_process),
1051 }))
1052}
1053
1054fn collect_signal_deliveries(
1055 state: &mut ProcessTableState,
1056 target_pids: &[u32],
1057 signal: i32,
1058) -> ProcessResult<Vec<ScheduledSignalDelivery>> {
1059 let mut deliveries = Vec::new();
1060 for pid in target_pids {
1061 let Some(record) = state.entries.get_mut(pid) else {
1062 continue;
1063 };
1064 if let Some(delivery) = queue_or_schedule_signal(record, signal)? {
1065 deliveries.push(delivery);
1066 }
1067 }
1068 Ok(deliveries)
1069}
1070
1071fn collect_pending_signal_deliveries(
1072 record: &mut ProcessRecord,
1073 signals: SignalSet,
1074) -> ProcessResult<Vec<ScheduledSignalDelivery>> {
1075 let mut deliveries = Vec::new();
1076 for signal in signals.signals() {
1077 record.pending_signals.remove(signal)?;
1078 deliveries.push(ScheduledSignalDelivery {
1079 pid: record.entry.pid,
1080 signal,
1081 status: record.entry.status,
1082 driver_process: Arc::clone(&record.driver_process),
1083 });
1084 }
1085 Ok(deliveries)
1086}
1087
1088fn deliver_signals(inner: &Arc<ProcessTableInner>, deliveries: Vec<ScheduledSignalDelivery>) {
1089 let mut stopped = Vec::new();
1090 let mut continued = Vec::new();
1091
1092 for delivery in &deliveries {
1093 match delivery.signal {
1094 SIGSTOP | SIGTSTP if delivery.status == ProcessStatus::Running => {
1095 stopped.push((delivery.pid, delivery.signal))
1096 }
1097 SIGCONT if delivery.status == ProcessStatus::Stopped => continued.push(delivery.pid),
1098 _ => {}
1099 }
1100 delivery.driver_process.kill(delivery.signal);
1101 }
1102
1103 for (pid, signal) in stopped {
1104 mark_wait_event_inner(
1105 inner,
1106 pid,
1107 ProcessStatus::Stopped,
1108 PendingWaitEvent {
1109 status: signal,
1110 event: ProcessWaitEvent::Stopped,
1111 },
1112 );
1113 }
1114 for pid in continued {
1115 mark_wait_event_inner(
1116 inner,
1117 pid,
1118 ProcessStatus::Running,
1119 PendingWaitEvent {
1120 status: SIGCONT,
1121 event: ProcessWaitEvent::Continued,
1122 },
1123 );
1124 }
1125}
1126
1127fn resolve_wait_selector(
1128 state: &ProcessTableState,
1129 waiter_pid: u32,
1130 pid: i32,
1131) -> ProcessResult<WaitSelector> {
1132 let waiter = state
1133 .entries
1134 .get(&waiter_pid)
1135 .ok_or_else(|| ProcessTableError::no_such_process(waiter_pid))?;
1136
1137 Ok(match pid {
1138 -1 => WaitSelector::AnyChild,
1139 0 => WaitSelector::ProcessGroup(waiter.entry.pgid),
1140 p if p < -1 => WaitSelector::ProcessGroup(p.unsigned_abs()),
1141 p => WaitSelector::ChildPid(p as u32),
1142 })
1143}
1144
1145fn matching_child_pids(
1146 state: &ProcessTableState,
1147 waiter_pid: u32,
1148 selector: WaitSelector,
1149) -> Vec<u32> {
1150 state
1151 .entries
1152 .values()
1153 .filter(|record| record.entry.ppid == waiter_pid)
1154 .filter(|record| match selector {
1155 WaitSelector::AnyChild => true,
1156 WaitSelector::ChildPid(pid) => record.entry.pid == pid,
1157 WaitSelector::ProcessGroup(pgid) => record.entry.pgid == pgid,
1158 })
1159 .map(|record| record.entry.pid)
1160 .collect()
1161}
1162
1163fn take_waitable_event(
1164 state: &mut ProcessTableState,
1165 matching_children: &[u32],
1166 flags: WaitPidFlags,
1167) -> Option<ProcessWaitResult> {
1168 for child_pid in matching_children {
1169 let mut non_exit_result = None;
1170 let mut should_reap = false;
1171 {
1172 let record = state.entries.get_mut(child_pid)?;
1173 if let Some(index) = record
1174 .pending_wait_events
1175 .iter()
1176 .position(|event| is_waitable_event(event.event, flags))
1177 {
1178 let event = record
1179 .pending_wait_events
1180 .remove(index)
1181 .expect("pending wait event should exist");
1182 non_exit_result = Some(ProcessWaitResult {
1183 pid: *child_pid,
1184 status: event.status,
1185 event: event.event,
1186 });
1187 } else if record.entry.status == ProcessStatus::Exited {
1188 should_reap = true;
1189 }
1190 }
1191
1192 if let Some(result) = non_exit_result {
1193 return Some(result);
1194 }
1195
1196 if should_reap {
1197 let record = state
1198 .entries
1199 .remove(child_pid)
1200 .expect("exited child should still exist");
1201 return Some(ProcessWaitResult {
1202 pid: *child_pid,
1203 status: record.entry.exit_code.unwrap_or_default(),
1204 event: ProcessWaitEvent::Exited,
1205 });
1206 }
1207 }
1208
1209 None
1210}
1211
1212fn is_waitable_event(event: ProcessWaitEvent, flags: WaitPidFlags) -> bool {
1213 match event {
1214 ProcessWaitEvent::Exited => true,
1215 ProcessWaitEvent::Stopped => flags.contains(WaitPidFlags::WUNTRACED),
1216 ProcessWaitEvent::Continued => flags.contains(WaitPidFlags::WCONTINUED),
1217 }
1218}
1219
1220fn start_zombie_reaper(inner: Weak<ProcessTableInner>, reaper: Arc<ZombieReaper>) {
1221 reaper.thread_spawns.fetch_add(1, Ordering::SeqCst);
1222 thread::spawn(move || loop {
1223 let Some(pid) = reaper.take_next_due_pid() else {
1224 return;
1225 };
1226
1227 let Some(inner) = inner.upgrade() else {
1228 return;
1229 };
1230
1231 let mut state = inner.lock_state();
1232 let should_reap = state
1233 .entries
1234 .get(&pid)
1235 .map(|record| {
1236 record.entry.status == ProcessStatus::Exited
1237 && !has_living_parent(&state, record.entry.ppid)
1238 })
1239 .unwrap_or(false);
1240 if should_reap {
1241 state.entries.remove(&pid);
1242 } else if state
1243 .entries
1244 .get(&pid)
1245 .map(|record| record.entry.status == ProcessStatus::Exited)
1246 .unwrap_or(false)
1247 {
1248 reaper.schedule(pid, state.zombie_ttl);
1249 }
1250 drop(state);
1251 inner.waiters.notify_all();
1252 });
1253}
1254
1255fn has_living_parent(state: &ProcessTableState, ppid: u32) -> bool {
1256 ppid != 0
1257 && state
1258 .entries
1259 .get(&ppid)
1260 .map(|record| record.entry.status != ProcessStatus::Exited)
1261 .unwrap_or(false)
1262}
1263
1264impl ProcessTableInner {
1265 fn lock_state(&self) -> MutexGuard<'_, ProcessTableState> {
1266 lock_or_recover(&self.state)
1267 }
1268
1269 fn wait_for_state<'a>(
1270 &self,
1271 guard: MutexGuard<'a, ProcessTableState>,
1272 ) -> MutexGuard<'a, ProcessTableState> {
1273 wait_or_recover(&self.waiters, guard)
1274 }
1275}
1276
1277fn now_ms() -> u64 {
1278 SystemTime::now()
1279 .duration_since(UNIX_EPOCH)
1280 .unwrap_or_default()
1281 .as_millis() as u64
1282}
1283
1284impl Default for ZombieReaper {
1285 fn default() -> Self {
1286 Self {
1287 state: Mutex::new(ZombieReaperState::default()),
1288 wake: Condvar::new(),
1289 thread_spawns: AtomicUsize::new(0),
1290 }
1291 }
1292}
1293
1294impl ZombieReaper {
1295 fn schedule(&self, pid: u32, ttl: Duration) {
1296 let mut state = lock_or_recover(&self.state);
1297 state.deadlines.insert(pid, Instant::now() + ttl);
1298 drop(state);
1299 self.wake.notify_all();
1300 }
1301
1302 fn cancel(&self, pid: u32) {
1303 let mut state = lock_or_recover(&self.state);
1304 let removed = state.deadlines.remove(&pid).is_some();
1305 drop(state);
1306 if removed {
1307 self.wake.notify_all();
1308 }
1309 }
1310
1311 fn clear(&self) {
1312 let mut state = lock_or_recover(&self.state);
1313 let changed = !state.deadlines.is_empty();
1314 state.deadlines.clear();
1315 drop(state);
1316 if changed {
1317 self.wake.notify_all();
1318 }
1319 }
1320
1321 fn shutdown(&self) {
1322 let mut state = lock_or_recover(&self.state);
1323 state.shutdown = true;
1324 drop(state);
1325 self.wake.notify_all();
1326 }
1327
1328 fn scheduled_count(&self) -> usize {
1329 lock_or_recover(&self.state).deadlines.len()
1330 }
1331
1332 fn thread_spawn_count(&self) -> usize {
1333 self.thread_spawns.load(Ordering::SeqCst)
1334 }
1335
1336 fn take_next_due_pid(&self) -> Option<u32> {
1337 let mut state = lock_or_recover(&self.state);
1338 loop {
1339 if state.shutdown {
1340 return None;
1341 }
1342
1343 let Some((pid, deadline)) = state
1344 .deadlines
1345 .iter()
1346 .min_by_key(|(_, deadline)| **deadline)
1347 .map(|(&pid, &deadline)| (pid, deadline))
1348 else {
1349 state = wait_or_recover(&self.wake, state);
1350 continue;
1351 };
1352
1353 let now = Instant::now();
1354 if deadline <= now {
1355 state.deadlines.remove(&pid);
1356 return Some(pid);
1357 }
1358
1359 let timeout = deadline.saturating_duration_since(now);
1360 let (next_state, _) = wait_timeout_or_recover(&self.wake, state, timeout);
1361 state = next_state;
1362 }
1363 }
1364}
1365
1366impl Drop for ProcessTableInner {
1367 fn drop(&mut self) {
1368 self.reaper.shutdown();
1369 }
1370}
1371
1372fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
1373 match mutex.lock() {
1374 Ok(guard) => guard,
1375 Err(poisoned) => poisoned.into_inner(),
1376 }
1377}
1378
1379fn wait_or_recover<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
1380 match condvar.wait(guard) {
1381 Ok(guard) => guard,
1382 Err(poisoned) => poisoned.into_inner(),
1383 }
1384}
1385
1386fn wait_timeout_or_recover<'a, T>(
1387 condvar: &Condvar,
1388 guard: MutexGuard<'a, T>,
1389 timeout: Duration,
1390) -> (MutexGuard<'a, T>, WaitTimeoutResult) {
1391 match condvar.wait_timeout(guard, timeout) {
1392 Ok(result) => result,
1393 Err(poisoned) => poisoned.into_inner(),
1394 }
1395}