1use std::collections::{BTreeMap, BTreeSet};
18
19use kernel::{AxError, RunId};
20
21use super::{Backlog, BacklogId, Body, Claim, Member};
22
23#[derive(Debug, Clone, Default, PartialEq, Eq)]
25pub struct RunProcesses {
26 pub pids: BTreeSet<u32>,
30 pub unfollowed: u32,
36}
37
38#[derive(Default)]
40pub(super) struct Jobs {
41 #[cfg(windows)]
42 runs: BTreeMap<RunId, RunJob>,
43}
44
45impl Backlog {
46 pub fn processes(&self) -> Result<BTreeMap<RunId, RunProcesses>, AxError> {
52 let table = self.hold()?;
53 let mut runs: BTreeMap<RunId, RunProcesses> = BTreeMap::new();
54 for member in table.members.values() {
55 if let Body::Command {
56 child,
57 claim: Claim::Window(owner) | Claim::Run(owner),
58 ..
59 } = &member.body
60 {
61 runs.entry(*owner).or_default().pids.insert(child.id());
62 }
63 }
64 table.jobs.follow(&mut runs);
65 Ok(runs)
66 }
67
68 pub(super) fn enrol(&self, id: BacklogId, member: Member) -> Result<(), AxError> {
70 let mut table = self.hold()?;
71 table.jobs.enter(&member);
72 table.members.insert(id, member);
73 Ok(())
74 }
75}
76
77impl Jobs {
78 #[cfg(windows)]
80 pub(super) fn forget(&mut self, owner: RunId) {
81 self.runs.remove(&owner);
82 }
83
84 #[cfg(not(windows))]
85 pub(super) fn forget(&mut self, _owner: RunId) {}
86
87 #[cfg(windows)]
88 fn enter(&mut self, member: &Member) {
89 let Body::Command {
90 child,
91 claim: Claim::Window(owner) | Claim::Run(owner),
92 ..
93 } = &member.body
94 else {
95 return;
96 };
97 let run = self.runs.entry(*owner).or_default();
98 if run.join(child).is_none() {
99 run.unjoined = run.unjoined.saturating_add(1);
100 }
101 }
102
103 #[cfg(not(windows))]
104 fn enter(&mut self, _member: &Member) {}
105
106 #[cfg(windows)]
107 fn follow(&self, readings: &mut BTreeMap<RunId, RunProcesses>) {
108 for (owner, run) in &self.runs {
109 let reading = readings.entry(*owner).or_default();
110 let listed = run.job.as_ref().map(win32job::Job::query_process_id_list);
111 if let Some(Ok(pids)) = listed {
112 reading
114 .pids
115 .extend(pids.into_iter().filter_map(|pid| u32::try_from(pid).ok()));
116 reading.unfollowed = reading.unfollowed.saturating_add(run.unjoined);
117 } else {
118 reading.unfollowed = u32::try_from(reading.pids.len()).unwrap_or(u32::MAX);
119 }
120 }
121 }
122
123 #[cfg(not(windows))]
124 fn follow(&self, readings: &mut BTreeMap<RunId, RunProcesses>) {
125 for reading in readings.values_mut() {
126 reading.unfollowed = u32::try_from(reading.pids.len()).unwrap_or(u32::MAX);
127 }
128 }
129}
130
131#[cfg(windows)]
133#[derive(Default)]
134struct RunJob {
135 job: Option<win32job::Job>,
136 unjoined: u32,
137}
138
139#[cfg(windows)]
140impl RunJob {
141 fn join(&mut self, child: &std::process::Child) -> Option<()> {
144 use std::os::windows::io::AsRawHandle;
145 let handle = isize::try_from(child.as_raw_handle().addr()).ok()?;
146 let job = match self.job.take() {
147 Some(job) => job,
148 None => win32job::Job::create().ok()?,
149 };
150 let joined = job.assign_process(handle).ok();
151 self.job = Some(job);
152 joined
153 }
154}