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 plan.special_vars_pid_ns = input.target_pid_views.and_then(helper_pid_ns).or_else(|| {
67 input
68 .in_container
69 .then_some(input.self_pid_views.and_then(helper_pid_ns))
70 .flatten()
71 });
72
73 plan.proc_offsets_pid_ns = input
74 .target_pid_views
75 .and_then(|pid_views| {
76 should_use_target_proc_offsets_pid_ns(pid_views, plan.pid_filter.as_ref())
77 .then_some(helper_pid_ns(pid_views))
78 .flatten()
79 })
80 .or_else(|| {
81 input
82 .in_container
83 .then_some(input.self_pid_views.and_then(helper_pid_ns))
84 .flatten()
85 });
86 }
87
88 Ok(plan)
89}
90
91fn pid_ns_context_needed(pid_views: &PidViews, in_container: bool, helper_supported: bool) -> bool {
92 pid_views.host_pid != pid_views.proc_pid || (in_container && helper_supported)
97}
98
99fn helper_pid_ns(pid_views: &PidViews) -> Option<PidNamespaceId> {
100 pid_views.pid_ns.filter(|pid_ns| pid_ns.dev.is_some())
101}
102
103fn should_use_target_proc_offsets_pid_ns(
104 pid_views: &PidViews,
105 pid_filter: Option<&PidFilterSpec>,
106) -> bool {
107 match pid_filter {
108 Some(PidFilterSpec::NamespaceTgid { filter_pid, .. }) => {
109 pid_views.container_pid == Some(*filter_pid) || pid_views.proc_pid == *filter_pid
110 }
111 _ => false,
112 }
113}
114
115fn should_fail_fast_pid_mode(
116 pid_views: &PidViews,
117 in_container: bool,
118 helper_supported: bool,
119) -> bool {
120 in_container
121 && !helper_supported
122 && !pid_views.has_explicit_host_mapping()
123 && !pid_views.is_initial_pid_namespace()
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::{PidResolveSource, INITIAL_PID_NAMESPACE_INO};
130
131 fn make_pid_views(proc_pid: u32, host_pid: u32) -> PidViews {
132 PidViews {
133 proc_pid,
134 host_pid,
135 container_pid: None,
136 pid_ns: None,
137 nspid_chain: None,
138 source: PidResolveSource::DirectProcStatus,
139 }
140 }
141
142 #[test]
143 fn host_filter_is_selected_when_pids_match_without_helper() {
144 let pid_views = make_pid_views(321, 321);
145 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
146 target_pid_views: Some(&pid_views),
147 self_pid_views: None,
148 in_container: false,
149 helper_supported: false,
150 })
151 .unwrap();
152
153 assert_eq!(
154 plan.pid_filter,
155 Some(PidFilterSpec::HostTgid { filter_pid: 321 })
156 );
157 assert_eq!(plan.special_vars_pid_ns, None);
158 assert_eq!(plan.proc_offsets_pid_ns, None);
159 }
160
161 #[test]
162 fn namespace_filter_is_selected_when_helper_is_available() {
163 let pid_views = PidViews {
164 proc_pid: 321,
165 host_pid: 4321,
166 container_pid: Some(17),
167 pid_ns: Some(PidNamespaceId {
168 dev: Some(1),
169 inode: 2,
170 }),
171 nspid_chain: Some(vec![4321, 321, 17]),
172 source: PidResolveSource::DirectProcStatus,
173 };
174
175 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
176 target_pid_views: Some(&pid_views),
177 self_pid_views: None,
178 in_container: true,
179 helper_supported: true,
180 })
181 .unwrap();
182
183 assert_eq!(
184 plan.pid_filter,
185 Some(PidFilterSpec::NamespaceTgid {
186 filter_pid: 17,
187 pid_ns: PidNamespaceId {
188 dev: Some(1),
189 inode: 2,
190 },
191 })
192 );
193 assert_eq!(
194 plan.special_vars_pid_ns,
195 Some(PidNamespaceId {
196 dev: Some(1),
197 inode: 2,
198 })
199 );
200 assert_eq!(
201 plan.proc_offsets_pid_ns,
202 Some(PidNamespaceId {
203 dev: Some(1),
204 inode: 2,
205 })
206 );
207 }
208
209 #[test]
210 fn target_mode_uses_self_namespace_for_special_vars_and_proc_offsets() {
211 let self_pid_views = PidViews {
212 proc_pid: 123,
213 host_pid: 456,
214 container_pid: Some(123),
215 pid_ns: Some(PidNamespaceId {
216 dev: Some(7),
217 inode: 8,
218 }),
219 nspid_chain: Some(vec![456, 123]),
220 source: PidResolveSource::DirectProcStatus,
221 };
222
223 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
224 target_pid_views: None,
225 self_pid_views: Some(&self_pid_views),
226 in_container: true,
227 helper_supported: true,
228 })
229 .unwrap();
230
231 assert_eq!(plan.pid_filter, None);
232 assert_eq!(plan.special_vars_pid_ns, self_pid_views.pid_ns);
233 assert_eq!(plan.proc_offsets_pid_ns, self_pid_views.pid_ns);
234 }
235
236 #[test]
237 fn fail_fast_in_container_without_helper_or_explicit_host_mapping() {
238 let pid_views = make_pid_views(321, 321);
239 assert_eq!(
240 build_runtime_pid_plan(RuntimePidPlanInput {
241 target_pid_views: Some(&pid_views),
242 self_pid_views: None,
243 in_container: true,
244 helper_supported: false,
245 }),
246 Err(PidModeFailFast { proc_pid: 321 })
247 );
248 }
249
250 #[test]
251 fn initial_pid_namespace_disables_fail_fast_without_helper() {
252 let pid_views = PidViews {
253 proc_pid: 321,
254 host_pid: 321,
255 container_pid: None,
256 pid_ns: Some(PidNamespaceId {
257 dev: Some(1),
258 inode: INITIAL_PID_NAMESPACE_INO,
259 }),
260 nspid_chain: None,
261 source: PidResolveSource::DirectProcStatus,
262 };
263
264 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
265 target_pid_views: Some(&pid_views),
266 self_pid_views: None,
267 in_container: true,
268 helper_supported: false,
269 })
270 .unwrap();
271
272 assert_eq!(
273 plan.pid_filter,
274 Some(PidFilterSpec::HostTgid { filter_pid: 321 })
275 );
276 }
277
278 #[test]
279 fn explicit_host_mapping_disables_fail_fast_without_helper() {
280 let pid_views = PidViews {
281 proc_pid: 321,
282 host_pid: 12345,
283 container_pid: Some(321),
284 pid_ns: None,
285 nspid_chain: Some(vec![12345, 321]),
286 source: PidResolveSource::DirectProcStatus,
287 };
288
289 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
290 target_pid_views: Some(&pid_views),
291 self_pid_views: None,
292 in_container: true,
293 helper_supported: false,
294 })
295 .unwrap();
296
297 assert_eq!(
298 plan.pid_filter,
299 Some(PidFilterSpec::HostTgid { filter_pid: 12345 })
300 );
301 }
302
303 #[test]
304 fn proc_offsets_stays_on_current_proc_view_for_host_filter() {
305 let pid_views = PidViews {
306 proc_pid: 321,
307 host_pid: 4321,
308 container_pid: Some(17),
309 pid_ns: Some(PidNamespaceId {
310 dev: Some(1),
311 inode: 2,
312 }),
313 nspid_chain: Some(vec![4321, 321, 17]),
314 source: PidResolveSource::DirectProcStatus,
315 };
316
317 let plan = build_runtime_pid_plan(RuntimePidPlanInput {
318 target_pid_views: Some(&pid_views),
319 self_pid_views: None,
320 in_container: true,
321 helper_supported: false,
322 })
323 .unwrap();
324
325 assert_eq!(plan.proc_offsets_pid_ns, None);
326 }
327}