1use super::{PidNamespaceId, PidViews};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum PidFilterSpec {
5 HostTgid { filter_pid: u32 },
7 NamespaceTgid {
9 filter_pid: u32,
10 pid_ns: PidNamespaceId,
11 },
12}
13
14#[derive(Debug, Clone, Copy)]
15pub struct RuntimePidPlanInput<'a> {
16 pub target_pid_views: Option<&'a PidViews>,
17 pub self_pid_views: Option<&'a PidViews>,
18 pub in_container: bool,
19 pub helper_supported: bool,
20}
21
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct RuntimePidPlan {
24 pub pid_filter: Option<PidFilterSpec>,
25 pub special_vars_pid_ns: Option<PidNamespaceId>,
26 pub proc_offsets_pid_ns: Option<PidNamespaceId>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct PidModeFailFast {
31 pub proc_pid: u32,
32}
33
34pub fn build_runtime_pid_plan(
35 input: RuntimePidPlanInput<'_>,
36) -> Result<RuntimePidPlan, PidModeFailFast> {
37 let mut plan = RuntimePidPlan::default();
38
39 if let Some(pid_views) = input.target_pid_views {
40 if should_fail_fast_pid_mode(pid_views, input.in_container, input.helper_supported) {
41 return Err(PidModeFailFast {
42 proc_pid: pid_views.proc_pid,
43 });
44 }
45
46 let ns_context_needed =
47 pid_ns_context_needed(pid_views, input.in_container, input.helper_supported);
48
49 plan.pid_filter = if ns_context_needed && input.helper_supported {
50 helper_pid_ns(pid_views)
51 .map(|pid_ns| PidFilterSpec::NamespaceTgid {
52 filter_pid: pid_views.container_pid.unwrap_or(pid_views.proc_pid),
53 pid_ns,
54 })
55 .or(Some(PidFilterSpec::HostTgid {
56 filter_pid: pid_views.host_pid,
57 }))
58 } else {
59 Some(PidFilterSpec::HostTgid {
60 filter_pid: pid_views.host_pid,
61 })
62 };
63 }
64
65 if input.helper_supported {
66 let self_pid_ns_for_target_mode = input
67 .target_pid_views
68 .is_none()
69 .then_some(input.self_pid_views.and_then(helper_pid_ns))
70 .flatten();
71 let self_pid_ns_for_container_fallback = input
72 .in_container
73 .then_some(input.self_pid_views.and_then(helper_pid_ns))
74 .flatten();
75
76 plan.special_vars_pid_ns = input
77 .target_pid_views
78 .and_then(helper_pid_ns)
79 .or_else(|| self_pid_ns_for_target_mode.or(self_pid_ns_for_container_fallback));
80
81 plan.proc_offsets_pid_ns = input
82 .target_pid_views
83 .and_then(|pid_views| {
84 should_use_target_proc_offsets_pid_ns(pid_views, plan.pid_filter.as_ref())
85 .then_some(helper_pid_ns(pid_views))
86 .flatten()
87 })
88 .or_else(|| self_pid_ns_for_target_mode.or(self_pid_ns_for_container_fallback));
89 }
90
91 Ok(plan)
92}
93
94fn pid_ns_context_needed(pid_views: &PidViews, in_container: bool, helper_supported: bool) -> bool {
95 pid_views.host_pid != pid_views.proc_pid || (in_container && helper_supported)
100}
101
102fn helper_pid_ns(pid_views: &PidViews) -> Option<PidNamespaceId> {
103 pid_views.pid_ns.filter(|pid_ns| pid_ns.dev.is_some())
104}
105
106fn should_use_target_proc_offsets_pid_ns(
107 pid_views: &PidViews,
108 pid_filter: Option<&PidFilterSpec>,
109) -> bool {
110 match pid_filter {
111 Some(PidFilterSpec::NamespaceTgid { filter_pid, .. }) => {
112 pid_views.container_pid == Some(*filter_pid) || pid_views.proc_pid == *filter_pid
113 }
114 _ => false,
115 }
116}
117
118fn should_fail_fast_pid_mode(
119 pid_views: &PidViews,
120 in_container: bool,
121 helper_supported: bool,
122) -> bool {
123 in_container
124 && !helper_supported
125 && !pid_views.has_explicit_host_mapping()
126 && !pid_views.is_initial_pid_namespace()
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::{PidResolveSource, INITIAL_PID_NAMESPACE_INO};
133
134 fn make_pid_views(proc_pid: u32, host_pid: u32) -> PidViews {
135 PidViews {
136 proc_pid,
137 host_pid,
138 container_pid: None,
139 pid_ns: None,
140 nspid_chain: None,
141 source: PidResolveSource::DirectProcStatus,
142 }
143 }
144
145 #[test]
146 fn host_filter_is_selected_when_pids_match_without_helper() {
147 let pid_views = make_pid_views(321, 321);
148 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
149 target_pid_views: Some(&pid_views),
150 self_pid_views: None,
151 in_container: false,
152 helper_supported: false,
153 })
154 .unwrap();
155
156 assert_eq!(
157 plan.pid_filter,
158 Some(PidFilterSpec::HostTgid { filter_pid: 321 })
159 );
160 assert_eq!(plan.special_vars_pid_ns, None);
161 assert_eq!(plan.proc_offsets_pid_ns, None);
162 }
163
164 #[test]
165 fn namespace_filter_is_selected_when_helper_is_available() {
166 let pid_views = PidViews {
167 proc_pid: 321,
168 host_pid: 4321,
169 container_pid: Some(17),
170 pid_ns: Some(PidNamespaceId {
171 dev: Some(1),
172 inode: 2,
173 }),
174 nspid_chain: Some(vec![4321, 321, 17]),
175 source: PidResolveSource::DirectProcStatus,
176 };
177
178 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
179 target_pid_views: Some(&pid_views),
180 self_pid_views: None,
181 in_container: true,
182 helper_supported: true,
183 })
184 .unwrap();
185
186 assert_eq!(
187 plan.pid_filter,
188 Some(PidFilterSpec::NamespaceTgid {
189 filter_pid: 17,
190 pid_ns: PidNamespaceId {
191 dev: Some(1),
192 inode: 2,
193 },
194 })
195 );
196 assert_eq!(
197 plan.special_vars_pid_ns,
198 Some(PidNamespaceId {
199 dev: Some(1),
200 inode: 2,
201 })
202 );
203 assert_eq!(
204 plan.proc_offsets_pid_ns,
205 Some(PidNamespaceId {
206 dev: Some(1),
207 inode: 2,
208 })
209 );
210 }
211
212 #[test]
213 fn target_mode_uses_self_namespace_for_special_vars_and_proc_offsets() {
214 let self_pid_views = PidViews {
215 proc_pid: 123,
216 host_pid: 456,
217 container_pid: Some(123),
218 pid_ns: Some(PidNamespaceId {
219 dev: Some(7),
220 inode: 8,
221 }),
222 nspid_chain: Some(vec![456, 123]),
223 source: PidResolveSource::DirectProcStatus,
224 };
225
226 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
227 target_pid_views: None,
228 self_pid_views: Some(&self_pid_views),
229 in_container: true,
230 helper_supported: true,
231 })
232 .unwrap();
233
234 assert_eq!(plan.pid_filter, None);
235 assert_eq!(plan.special_vars_pid_ns, self_pid_views.pid_ns);
236 assert_eq!(plan.proc_offsets_pid_ns, self_pid_views.pid_ns);
237 }
238
239 #[test]
240 fn target_mode_uses_self_namespace_when_container_detection_misses() {
241 let self_pid_views = PidViews {
242 proc_pid: 123,
243 host_pid: 456,
244 container_pid: Some(123),
245 pid_ns: Some(PidNamespaceId {
246 dev: Some(7),
247 inode: 8,
248 }),
249 nspid_chain: Some(vec![456, 123]),
250 source: PidResolveSource::DirectProcStatus,
251 };
252
253 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
254 target_pid_views: None,
255 self_pid_views: Some(&self_pid_views),
256 in_container: false,
257 helper_supported: true,
258 })
259 .unwrap();
260
261 assert_eq!(plan.pid_filter, None);
262 assert_eq!(plan.special_vars_pid_ns, self_pid_views.pid_ns);
263 assert_eq!(plan.proc_offsets_pid_ns, self_pid_views.pid_ns);
264 }
265
266 #[test]
267 fn fail_fast_in_container_without_helper_or_explicit_host_mapping() {
268 let pid_views = make_pid_views(321, 321);
269 assert_eq!(
270 build_runtime_pid_plan(RuntimePidPlanInput {
271 target_pid_views: Some(&pid_views),
272 self_pid_views: None,
273 in_container: true,
274 helper_supported: false,
275 }),
276 Err(PidModeFailFast { proc_pid: 321 })
277 );
278 }
279
280 #[test]
281 fn initial_pid_namespace_disables_fail_fast_without_helper() {
282 let pid_views = PidViews {
283 proc_pid: 321,
284 host_pid: 321,
285 container_pid: None,
286 pid_ns: Some(PidNamespaceId {
287 dev: Some(1),
288 inode: INITIAL_PID_NAMESPACE_INO,
289 }),
290 nspid_chain: None,
291 source: PidResolveSource::DirectProcStatus,
292 };
293
294 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
295 target_pid_views: Some(&pid_views),
296 self_pid_views: None,
297 in_container: true,
298 helper_supported: false,
299 })
300 .unwrap();
301
302 assert_eq!(
303 plan.pid_filter,
304 Some(PidFilterSpec::HostTgid { filter_pid: 321 })
305 );
306 }
307
308 #[test]
309 fn explicit_host_mapping_disables_fail_fast_without_helper() {
310 let pid_views = PidViews {
311 proc_pid: 321,
312 host_pid: 12345,
313 container_pid: Some(321),
314 pid_ns: None,
315 nspid_chain: Some(vec![12345, 321]),
316 source: PidResolveSource::DirectProcStatus,
317 };
318
319 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
320 target_pid_views: Some(&pid_views),
321 self_pid_views: None,
322 in_container: true,
323 helper_supported: false,
324 })
325 .unwrap();
326
327 assert_eq!(
328 plan.pid_filter,
329 Some(PidFilterSpec::HostTgid { filter_pid: 12345 })
330 );
331 }
332
333 #[test]
334 fn proc_offsets_stays_on_current_proc_view_for_host_filter() {
335 let pid_views = PidViews {
336 proc_pid: 321,
337 host_pid: 4321,
338 container_pid: Some(17),
339 pid_ns: Some(PidNamespaceId {
340 dev: Some(1),
341 inode: 2,
342 }),
343 nspid_chain: Some(vec![4321, 321, 17]),
344 source: PidResolveSource::DirectProcStatus,
345 };
346
347 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
348 target_pid_views: Some(&pid_views),
349 self_pid_views: None,
350 in_container: true,
351 helper_supported: false,
352 })
353 .unwrap();
354
355 assert_eq!(plan.proc_offsets_pid_ns, None);
356 }
357}