Skip to main content

agent_os_kernel/
process_table.rs

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}