1use bstr::ByteSlice;
6use regex_lite::{Error, Regex};
7use starnix_task_command::TaskCommand;
8
9#[derive(Debug)]
11pub struct RoleOverrides {
12 process_filter: Vec<Regex>,
13 thread_filter: Vec<Regex>,
14 cgroup_filter: Vec<Option<Regex>>,
15 is_leader_filter: Vec<Option<bool>>,
16 min_nice_filter: Vec<Option<i8>>,
17 max_nice_filter: Vec<Option<i8>>,
18 role_names: Vec<String>,
19}
20
21impl RoleOverrides {
22 pub fn new() -> RoleOverridesBuilder {
24 RoleOverridesBuilder {
25 process_patterns: vec![],
26 thread_patterns: vec![],
27 cgroup_patterns: vec![],
28 is_leader_filters: vec![],
29 min_nice_filters: vec![],
30 max_nice_filters: vec![],
31 role_names: vec![],
32 }
33 }
34
35 pub fn get_role_name<'a>(
37 &self,
38 process_name: &TaskCommand,
39 thread_name: &TaskCommand,
40 cgroup_path: &str,
41 ) -> Option<&str> {
42 self.get_role_name_with_filters(process_name, thread_name, cgroup_path, false, 0)
43 }
44
45 pub fn get_role_name_with_filters<'a>(
48 &self,
49 process_name: &TaskCommand,
50 thread_name: &TaskCommand,
51 cgroup_path: &str,
52 is_leader: bool,
53 nice: i8,
54 ) -> Option<&str> {
55 debug_assert_eq!(self.process_filter.len(), self.role_names.len());
56 debug_assert_eq!(self.thread_filter.len(), self.role_names.len());
57 debug_assert_eq!(self.cgroup_filter.len(), self.role_names.len());
58 debug_assert_eq!(self.is_leader_filter.len(), self.role_names.len());
59 debug_assert_eq!(self.min_nice_filter.len(), self.role_names.len());
60 debug_assert_eq!(self.max_nice_filter.len(), self.role_names.len());
61
62 let process_name = process_name.as_bytes().to_str().ok()?;
65 let thread_name = thread_name.as_bytes().to_str().ok()?;
66 for index in 0..self.process_filter.len() {
67 if self.is_leader_filter[index].is_none_or(|expected| expected == is_leader)
68 && self.min_nice_filter[index].is_none_or(|min| nice >= min)
69 && self.max_nice_filter[index].is_none_or(|max| nice <= max)
70 && self.process_filter[index].is_match(process_name)
71 && self.thread_filter[index].is_match(thread_name)
72 && self.cgroup_filter[index].as_ref().is_none_or(|r| r.is_match(cgroup_path))
73 {
74 return Some(self.role_names[index].as_str());
75 }
76 }
77 None
78 }
79}
80
81pub struct RoleOverridesBuilder {
83 process_patterns: Vec<String>,
84 thread_patterns: Vec<String>,
85 cgroup_patterns: Vec<Option<String>>,
86 is_leader_filters: Vec<Option<bool>>,
87 min_nice_filters: Vec<Option<i8>>,
88 max_nice_filters: Vec<Option<i8>>,
89 role_names: Vec<String>,
90}
91
92impl RoleOverridesBuilder {
93 pub fn add(
95 &mut self,
96 process: impl Into<String>,
97 thread: impl Into<String>,
98 cgroup: Option<String>,
99 role_name: impl Into<String>,
100 ) {
101 self.add_with_filters(process, thread, cgroup, None, None, None, role_name);
102 }
103
104 pub fn add_with_filters(
106 &mut self,
107 process: impl Into<String>,
108 thread: impl Into<String>,
109 cgroup: Option<String>,
110 is_leader: Option<bool>,
111 min_nice: Option<i8>,
112 max_nice: Option<i8>,
113 role_name: impl Into<String>,
114 ) {
115 self.process_patterns.push(process.into());
116 self.thread_patterns.push(thread.into());
117 self.cgroup_patterns.push(cgroup);
118 self.is_leader_filters.push(is_leader);
119 self.min_nice_filters.push(min_nice);
120 self.max_nice_filters.push(max_nice);
121 self.role_names.push(role_name.into());
122 }
123
124 pub fn build(self) -> Result<RoleOverrides, Error> {
126 let cgroup_filter = self
127 .cgroup_patterns
128 .into_iter()
129 .map(|opt| opt.map(|p| Regex::new(p.as_str())).transpose())
130 .collect::<Result<Vec<Option<Regex>>, Error>>()?;
131
132 Ok(RoleOverrides {
133 process_filter: self
134 .process_patterns
135 .into_iter()
136 .map(|pattern| Regex::new(pattern.as_str()))
137 .collect::<Result<Vec<Regex>, Error>>()?,
138 thread_filter: self
139 .thread_patterns
140 .into_iter()
141 .map(|pattern| Regex::new(pattern.as_str()))
142 .collect::<Result<Vec<Regex>, Error>>()?,
143 cgroup_filter,
144 is_leader_filter: self.is_leader_filters,
145 min_nice_filter: self.min_nice_filters,
146 max_nice_filter: self.max_nice_filters,
147 role_names: self.role_names,
148 })
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 fn str_role_name<'a>(
157 mappings: &'a RoleOverrides,
158 process_name: &str,
159 thread_name: &str,
160 cpuset_path: &str,
161 ) -> Option<&'a str> {
162 mappings.get_role_name(
163 &TaskCommand::new(process_name.as_bytes()),
164 &TaskCommand::new(thread_name.as_bytes()),
165 cpuset_path,
166 )
167 }
168
169 #[fuchsia::test]
170 fn single_pattern() {
171 let mut builder = RoleOverrides::new();
172 builder.add("process_prefix_.+", "thread_prefix_.+", None, "replacement_role");
173 let mappings = builder.build().unwrap();
174
175 assert_eq!(
176 str_role_name(&mappings, "process_prefix_foo", "thread_prefix_bar", "/"),
177 Some("replacement_role")
178 );
179 assert_eq!(str_role_name(&mappings, "process_prefix_foo", "non_matching", "/"), None);
180 assert_eq!(str_role_name(&mappings, "non_matching", "process_prefix_bar", "/"), None);
181 assert_eq!(str_role_name(&mappings, "non_matching", "non_matching", "/"), None);
182 }
183
184 #[fuchsia::test]
185 fn multiple_patterns() {
186 let mut builder = RoleOverrides::new();
187 builder.add("pre_one.+", "pre_one.+", None, "replace_one");
188 builder.add("pre_two.+", "pre_two.+", None, "replace_two");
189 builder.add("pre_three.+", "pre_three.+", None, "replace_three");
190 builder.add("pre_four.+", "pre_four.+", None, "replace_four");
191 let mappings = builder.build().unwrap();
192
193 assert_eq!(
194 str_role_name(&mappings, "pre_one_foo", "pre_one_bar", "/"),
195 Some("replace_one")
196 );
197 assert_eq!(str_role_name(&mappings, "pre_one_foo", "non_matching", "/"), None);
198 assert_eq!(str_role_name(&mappings, "non_matching", "pre_one_bar", "/"), None);
199 assert_eq!(str_role_name(&mappings, "non_matching", "non_matching", "/"), None);
200
201 assert_eq!(
202 str_role_name(&mappings, "pre_two_foo", "pre_two_bar", "/"),
203 Some("replace_two")
204 );
205 assert_eq!(str_role_name(&mappings, "pre_two_foo", "non_matching", "/"), None);
206 assert_eq!(str_role_name(&mappings, "non_matching", "pre_two_bar", "/"), None);
207
208 assert_eq!(
209 str_role_name(&mappings, "pre_three_foo", "pre_three_bar", "/"),
210 Some("replace_three")
211 );
212 assert_eq!(str_role_name(&mappings, "pre_three_foo", "non_matching", "/"), None);
213 assert_eq!(str_role_name(&mappings, "non_matching", "pre_three_bar", "/"), None);
214
215 assert_eq!(
216 str_role_name(&mappings, "pre_four_foo", "pre_four_bar", "/"),
217 Some("replace_four")
218 );
219 assert_eq!(str_role_name(&mappings, "pre_four_foo", "non_matching", "/"), None);
220 assert_eq!(str_role_name(&mappings, "non_matching", "pre_four_bar", "/"), None);
221 }
222
223 #[fuchsia::test]
224 fn cgroup_patterns() {
225 let mut builder = RoleOverrides::new();
226 builder.add("proc", "thread", Some("/background".to_string()), "bg_role");
227 builder.add("proc", "thread", Some("/foreground".to_string()), "fg_role");
228 builder.add("proc", "thread", None, "default_role");
229 let mappings = builder.build().unwrap();
230
231 assert_eq!(str_role_name(&mappings, "proc", "thread", "/background"), Some("bg_role"));
232 assert_eq!(str_role_name(&mappings, "proc", "thread", "/foreground"), Some("fg_role"));
233 assert_eq!(str_role_name(&mappings, "proc", "thread", "/other"), Some("default_role"));
234 assert_eq!(str_role_name(&mappings, "proc", "thread", "/"), Some("default_role"));
235 }
236
237 #[fuchsia::test]
238 fn leader_and_nice_filters() {
239 let mut builder = RoleOverrides::new();
240 builder.add_with_filters(
241 ".+",
242 ".+",
243 Some("^/active$".to_string()),
244 Some(true),
245 None,
246 None,
247 "leader_role",
248 );
249 builder.add_with_filters(
250 ".+",
251 ".+",
252 Some("^/active$".to_string()),
253 None,
254 Some(1),
255 None,
256 "low_prio_worker_role",
257 );
258 builder.add_with_filters(
259 ".+",
260 ".+",
261 Some("^/active$".to_string()),
262 None,
263 None,
264 Some(-1),
265 "boosted_worker_role",
266 );
267 builder.add(".+", ".+", Some("^/active$".to_string()), "default_worker_role");
268 let mappings = builder.build().unwrap();
269
270 let proc_cmd = TaskCommand::new(b"app");
271 let worker_cmd = TaskCommand::new(b"worker");
272
273 assert_eq!(
274 mappings.get_role_name_with_filters(&proc_cmd, &proc_cmd, "/active", true, 0),
275 Some("leader_role")
276 );
277 assert_eq!(
278 mappings.get_role_name_with_filters(&proc_cmd, &worker_cmd, "/active", false, 0),
279 Some("default_worker_role")
280 );
281 assert_eq!(
282 mappings.get_role_name_with_filters(&proc_cmd, &worker_cmd, "/active", false, 10),
283 Some("low_prio_worker_role")
284 );
285 assert_eq!(
286 mappings.get_role_name_with_filters(&proc_cmd, &worker_cmd, "/active", false, -2),
287 Some("boosted_worker_role")
288 );
289 }
290}