1use crate::ContainerStartInfo;
6use anyhow::{Error, anyhow};
7use starnix_container_structured_config::Config as ContainerStructuredConfig;
8use starnix_core::fs::tmpfs::register_selinux_test_suite_ramfs;
9use starnix_core::mm::MlockPinFlavor;
10use starnix_core::task::{Kernel, KernelFeatures, OomWatermarksMb, SystemLimits};
11use starnix_features::{Feature, FeatureAndArgs};
12use starnix_modules::{Framebuffer, Modules, validate_module};
13use starnix_uapi::error;
14use starnix_uapi::errors::Errno;
15use std::sync::Arc;
16
17#[derive(Default, Debug)]
19pub struct Features {
20 pub kernel: KernelFeatures,
22
23 pub selinux: SELinuxFeature,
25
26 pub system_limits: SystemLimits,
28
29 pub enable_visual_debugging: bool,
32
33 pub initial_view_id_annotation: String,
35
36 pub container: bool,
38
39 pub test_data: bool,
41
42 pub custom_artifacts: bool,
44
45 pub android_serialno: bool,
47
48 pub rootfs_rw: bool,
50
51 pub enable_utc_time_adjustment: bool,
52
53 pub android_bootreason: bool,
55
56 pub additional_mounts: Option<Vec<String>>,
57
58 pub unified_tracing: bool,
60}
61
62#[derive(Default, Debug, PartialEq)]
63pub struct SELinuxFeature {
64 pub enabled: bool,
66
67 pub options: String,
69
70 pub exceptions: Vec<String>,
72}
73
74impl Features {
75 pub fn record_inspect(&self, parent_node: &fuchsia_inspect::Node) {
76 parent_node.record_child("features", |inspect_node| match self {
77 Features {
78 kernel:
79 KernelFeatures {
80 bpf_v2,
81 devtmpfs,
82 enable_suid,
83 io_uring,
84 error_on_failed_reboot,
85 default_uid,
86 default_seclabel,
87 selinux_test_suite,
88 default_ns_mount_options,
89 mlock_always_onfault,
90 mlock_pin_flavor,
91 crash_report_throttling,
92 wifi,
93 cached_zx_map_info_bytes,
94 dirent_cache_size,
95 fake_ion,
96 remotevol_in_process,
97 log_sysfs_lookup_misses,
98 oom_watermarks_mb,
99 stop_last_on_shutdown,
100 },
101 system_limits,
102 selinux,
103 enable_visual_debugging,
104 container,
105 test_data,
106 custom_artifacts,
107 android_serialno,
108 rootfs_rw,
109 enable_utc_time_adjustment,
110 android_bootreason,
111 additional_mounts,
112 unified_tracing,
113 initial_view_id_annotation,
114 } => {
115 inspect_node.record_bool("selinux", selinux.enabled);
116 inspect_node.record_bool("unified_tracing", *unified_tracing);
117 inspect_node.record_string(
118 "initial_view_id_annotation",
119 initial_view_id_annotation.as_str(),
120 );
121 inspect_node.record_bool("container", *container);
122 inspect_node.record_bool("test_data", *test_data);
123 inspect_node.record_bool("custom_artifacts", *custom_artifacts);
124 inspect_node.record_bool("android_serialno", *android_serialno);
125 inspect_node.record_bool("rootfs_rw", *rootfs_rw);
126 inspect_node.record_bool("android_bootreason", *android_bootreason);
127 inspect_node.record_string("ping_group_range", {
128 let range = system_limits.socket.icmp_ping_gids.lock();
129 std::format!("{},{}", range.start, range.end - 1)
130 });
131
132 inspect_node.record_child("kernel", |kernel_node| {
133 kernel_node.record_bool("bpf_v2", *bpf_v2);
134 kernel_node.record_bool("devtmpfs", *devtmpfs);
135 kernel_node.record_bool("enable_suid", *enable_suid);
136 kernel_node.record_bool("io_uring", *io_uring);
137 kernel_node.record_bool("error_on_failed_reboot", *error_on_failed_reboot);
138 kernel_node.record_bool("enable_visual_debugging", *enable_visual_debugging);
139 kernel_node.record_int("default_uid", (*default_uid).into());
140 kernel_node.record_string(
141 "default_seclabel",
142 default_seclabel.as_deref().unwrap_or_default(),
143 );
144 kernel_node.record_bool("selinux_test_suite", *selinux_test_suite);
145 kernel_node.record_bool("crash_report_throttling", *crash_report_throttling);
146 kernel_node
147 .record_uint("cached_zx_map_info_bytes", *cached_zx_map_info_bytes as u64);
148 kernel_node.record_bool("log_sysfs_lookup_misses", *log_sysfs_lookup_misses);
149 if let Some(OomWatermarksMb { out_of_memory_mb, critical_mb, warning_mb }) =
150 oom_watermarks_mb
151 {
152 kernel_node.record_uint("out_of_memory_mb", *out_of_memory_mb as u64);
153 kernel_node.record_uint("critical_mb", *critical_mb as u64);
154 kernel_node.record_uint("warning_mb", *warning_mb as u64);
155 }
156 inspect_node.record_string(
157 "default_ns_mount_options",
158 format!("{:?}", default_ns_mount_options),
159 );
160 inspect_node
161 .record_bool("enable_utc_time_adjustment", *enable_utc_time_adjustment);
162 inspect_node.record_bool("mlock_always_onfault", *mlock_always_onfault);
163 inspect_node
164 .record_string("mlock_pin_flavor", format!("{:?}", mlock_pin_flavor));
165 inspect_node.record_bool("wifi", *wifi);
166 inspect_node
167 .record_string("additional_mounts", format!("{:?}", additional_mounts));
168 inspect_node.record_uint("dirent_cache_size", *dirent_cache_size as u64);
169 inspect_node.record_bool("fake_ion", *fake_ion);
170 inspect_node.record_bool("remotevol_in_process", *remotevol_in_process);
171 kernel_node.record_string(
172 "stop_last_on_shutdown",
173 format!("{:?}", stop_last_on_shutdown),
174 );
175 });
176 }
177 });
178 }
179}
180
181pub fn parse_features(
183 start_info: &ContainerStartInfo,
184 kernel_extra_features: &[String],
185) -> Result<(Features, Modules), Error> {
186 let ContainerStructuredConfig {
187 crash_report_throttling,
188 enable_utc_time_adjustment,
189 extra_features,
190 cached_zx_map_info_bytes,
191 mlock_always_onfault,
192 mlock_pin_flavor,
193 selinux_exceptions,
194 ui_visual_debugging_level,
195 additional_mounts,
196 dirent_cache_size,
197 initial_view_id_annotation,
198 } = &start_info.config;
199
200 let mut features = Features::default();
201 let mut modules = Modules::default();
202
203 let mut legacy_boot_notifier_cpu_boost: Option<String> = None;
204 let mut legacy_aspect_ratio: Option<String> = None;
205
206 for entry in start_info
207 .program
208 .features
209 .iter()
210 .chain(kernel_extra_features.iter())
211 .chain(extra_features.iter())
212 {
213 let FeatureAndArgs { feature, raw_args } = FeatureAndArgs::try_parse(entry)?;
214 match (feature, raw_args) {
215 (Feature::AndroidSerialno, _) => features.android_serialno = true,
216 (Feature::AndroidBootreason, _) => features.android_bootreason = true,
217 (Feature::AspectRatio, Some(args)) => {
218 legacy_aspect_ratio = Some(args);
219 }
220 (Feature::AspectRatio, None) => {
221 return Err(anyhow!(
222 "Aspect ratio feature must contain the aspect ratio in the format: aspect_ratio:w:h"
223 ));
224 }
225 (Feature::Container, _) => features.container = true,
226 (Feature::CustomArtifacts, _) => features.custom_artifacts = true,
227 (Feature::Devtmpfs, _) => features.kernel.devtmpfs = true,
228 (
229 Feature::AndroidUsb
230 | Feature::Ashmem
231 | Feature::BootNotifier
232 | Feature::DataCollectionConsentSync
233 | Feature::Fastrpc
234 | Feature::Framebuffer
235 | Feature::Gfxstream
236 | Feature::GoogleOdpm
237 | Feature::Gralloc
238 | Feature::HvdcpOpti
239 | Feature::Kgsl
240 | Feature::Magma
241 | Feature::MmcblkStub
242 | Feature::Nanohub
243 | Feature::NetworkManager
244 | Feature::Thermal
245 | Feature::Usbfs
246 | Feature::WakeupTest,
247 _,
248 ) => modules.enable(&feature.to_string()),
249 (Feature::BootNotifierCpuBoost, Some(arg)) => {
250 legacy_boot_notifier_cpu_boost = Some(arg);
251 }
252 (Feature::BootNotifierCpuBoost, None) => {
253 return Err(anyhow!(
254 "boot_notifier_cpu_boost feature must have an argument (e.g. \"boot_notifier_cpu_boost:60\")"
255 ));
256 }
257 (Feature::MagmaSupportedVendors, Some(arg)) => {
258 modules.add_with_args("magma", vec![format!("supported_vendors={arg}")]);
259 }
260 (Feature::MagmaSupportedVendors, None) => {
261 return Err(anyhow!(
262 "Feature format must be: magma_supported_vendors:0x1234[,0xabcd]"
263 ));
264 }
265 (Feature::UnifiedTracing, _) => features.unified_tracing = true,
266 (Feature::Bpf, Some(version)) => features.kernel.bpf_v2 = version == "v2",
267 (Feature::Bpf, None) => {
268 return Err(anyhow!("bpf feature must have an argument (e.g. \"bpf:v2\")"));
269 }
270 (Feature::EnableSuid, _) => features.kernel.enable_suid = true,
271 (Feature::IoUring, _) => features.kernel.io_uring = true,
272 (Feature::ErrorOnFailedReboot, _) => features.kernel.error_on_failed_reboot = true,
273 (Feature::Perfetto, Some(socket_path)) => {
274 modules.add_with_args("perfetto", vec![format!("socket_path={socket_path}")]);
275 }
276 (Feature::Perfetto, None) => {
277 return Err(anyhow!("Perfetto feature must contain a socket path"));
278 }
279 (Feature::PingGroupRange, Some(arg)) => {
280 let mut args = arg.split(',');
281 let (min, max) = (|| {
282 let min = args.next()?.trim_ascii().parse::<u32>().ok()?;
283 let max = args.next()?.trim_ascii().parse::<u32>().ok()?.checked_add(1)?;
284 if args.next().is_some() {
285 return None;
286 }
287 Some((min, max))
288 })()
289 .ok_or_else(|| anyhow!("Feature format must be: ping_group_range:0,100"))?;
290 *features.system_limits.socket.icmp_ping_gids.lock() = min..max;
291 }
292 (Feature::PingGroupRange, None) => {
293 return Err(anyhow!("Feature format must be: ping_group_range:0,100"));
294 }
295 (Feature::RootfsRw, _) => features.rootfs_rw = true,
296 (Feature::Selinux, arg) => {
297 features.selinux = SELinuxFeature {
298 enabled: true,
299 options: arg.unwrap_or_default(),
300 exceptions: selinux_exceptions.clone(),
301 };
302 }
303 (Feature::SelinuxTestSuite, _) => features.kernel.selinux_test_suite = true,
304 (Feature::TestData, _) => features.test_data = true,
305 (Feature::Cooling, Some(arg)) => {
306 modules.add_with_args("thermal", vec![format!("cooling={arg}")]);
307 }
308 (Feature::Cooling, None) => {
309 return Err(anyhow!("cooling feature must have an argument"));
310 }
311 (Feature::Wifi, _) => features.kernel.wifi = true,
312 (Feature::AdditionalMounts, _) => {
313 features.additional_mounts = Some(additional_mounts.clone())
314 }
315 (Feature::FakeIon, _) => features.kernel.fake_ion = true,
316 (Feature::RemotevolInProcess, _) => features.kernel.remotevol_in_process = true,
317 (Feature::LogSysfsLookupMisses, _) => features.kernel.log_sysfs_lookup_misses = true,
318 };
319 }
320
321 let legacy_boot_notifier = modules.is_enabled("boot_notifier");
322 for spec in &start_info.program.modules {
323 modules.add_spec(spec)?;
324 }
325
326 if let Some(secs) = legacy_boot_notifier_cpu_boost {
327 if !legacy_boot_notifier && !modules.is_enabled("boot_notifier") {
328 return Err(anyhow!("boot_notifier_cpu_boost feature requires boot_notifier"));
329 }
330 let arg = format!("cpu_boost_secs={secs}");
331 validate_module("boot_notifier", Some(&[arg.as_str()]))?;
332 if modules.args("boot_notifier") == Some(&[]) {
333 modules.add_with_args("boot_notifier", vec![arg]);
334 }
335 }
336
337 if let Some(ratio) = legacy_aspect_ratio {
338 let arg = format!("aspect_ratio={ratio}");
339 validate_module("framebuffer", Some(&[arg.as_str()]))?;
340 if modules.args("framebuffer") == Some(&[]) {
341 modules.add_with_args("framebuffer", vec![arg]);
342 }
343 }
344
345 modules.validate()?;
346
347 if *ui_visual_debugging_level > 0 {
348 features.enable_visual_debugging = true;
349 }
350 features.enable_utc_time_adjustment = *enable_utc_time_adjustment;
351
352 features.kernel.default_uid = start_info.program.default_uid.0;
353 features.kernel.default_seclabel = start_info.program.default_seclabel.clone();
354 if start_info.program.mounts.iter().chain(additional_mounts.iter()).any(|mount| {
357 let mut parts = mount.splitn(3, ':');
358 let _mount_point = parts.next();
359 parts.next() == Some("devtmpfs")
360 }) {
361 features.kernel.devtmpfs = true;
362 }
363 features.kernel.stop_last_on_shutdown =
364 start_info.program.stop_last_on_shutdown.iter().map(|path| path.as_str().into()).collect();
365 features.kernel.default_ns_mount_options =
366 if let Some(mount_options) = &start_info.program.default_ns_mount_options {
367 let options = mount_options
368 .iter()
369 .map(|item| {
370 let mut splitter = item.splitn(2, ":");
371 let key = splitter.next().expect("Failed to parse mount options");
372 let value = splitter.next().expect("Failed to parse mount options");
373 (key.to_string(), value.to_string())
374 })
375 .collect();
376 Some(options)
377 } else {
378 None
379 };
380
381 features.kernel.mlock_always_onfault = *mlock_always_onfault;
382 features.kernel.mlock_pin_flavor = MlockPinFlavor::parse(mlock_pin_flavor.as_str())?;
383 features.kernel.crash_report_throttling = *crash_report_throttling;
384 features.kernel.cached_zx_map_info_bytes = *cached_zx_map_info_bytes;
385 features.kernel.dirent_cache_size = *dirent_cache_size;
386 features.initial_view_id_annotation = initial_view_id_annotation.clone();
387 modules.set_framebuffer_config(
388 features.enable_visual_debugging,
389 features.initial_view_id_annotation.clone(),
390 );
391
392 Ok((features, modules))
393}
394
395pub fn run_container_features(kernel: &Arc<Kernel>, features: &Features) -> Result<(), Error> {
397 if features.unified_tracing {
398 kernel.trace_event_manager.init_trace_observer(kernel);
399 }
400 if features.kernel.selinux_test_suite {
401 register_selinux_test_suite_ramfs(kernel);
402 }
403 Ok(())
404}
405
406pub fn run_component_features(
408 kernel: &Kernel,
409 entries: &Vec<String>,
410 mut incoming_dir: Option<fidl_fuchsia_io::DirectoryProxy>,
411) -> Result<(), Errno> {
412 for entry in entries {
413 match entry.as_str() {
414 "framebuffer" => {
415 Framebuffer::get(kernel)?.start_server(kernel, incoming_dir.take());
416 }
417 feature => {
418 return error!(ENOSYS, format!("Unsupported feature: {}", feature));
419 }
420 }
421 }
422 Ok(())
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 fn test_start_info(features: Vec<String>, modules: Vec<String>) -> ContainerStartInfo {
430 ContainerStartInfo::new_for_test(
431 features,
432 modules,
433 ContainerStructuredConfig {
434 additional_mounts: Vec::new(),
435 cached_zx_map_info_bytes: 0,
436 crash_report_throttling: false,
437 dirent_cache_size: 0,
438 enable_utc_time_adjustment: false,
439 extra_features: Vec::new(),
440 initial_view_id_annotation: String::new(),
441 mlock_always_onfault: false,
442 mlock_pin_flavor: "noop".to_string(),
443 selinux_exceptions: Vec::new(),
444 ui_visual_debugging_level: 0,
445 },
446 )
447 }
448
449 #[fuchsia::test]
450 fn parse_program_modules() {
451 let start_info = test_start_info(
452 vec!["container".to_string()],
453 vec![
454 "ashmem".to_string(),
455 "boot_notifier cpu_boost_secs=60".to_string(),
456 "framebuffer aspect_ratio=9:16".to_string(),
457 "magma supported_vendors=0x13B5,0x5143,0x8086".to_string(),
458 "perfetto socket_path=/dev/socket/traced_consumer".to_string(),
459 "thermal cooling=cpu,fcc=8".to_string(),
460 ],
461 );
462
463 let (features, modules) =
464 parse_features(&start_info, &[]).expect("parse_features should succeed");
465 assert!(features.container);
466 assert!(modules.is_enabled("ashmem"));
467 assert_eq!(modules.args("ashmem"), Some(&[][..]));
468 assert_eq!(modules.args("boot_notifier"), Some(&["cpu_boost_secs=60".to_string()][..]));
469 assert_eq!(modules.args("framebuffer"), Some(&["aspect_ratio=9:16".to_string()][..]));
470 assert_eq!(
471 modules.args("magma"),
472 Some(&["supported_vendors=0x13B5,0x5143,0x8086".to_string()][..])
473 );
474 assert_eq!(
475 modules.args("perfetto"),
476 Some(&["socket_path=/dev/socket/traced_consumer".to_string()][..])
477 );
478 assert_eq!(modules.args("thermal"), Some(&["cooling=cpu,fcc=8".to_string()][..]));
479 }
480
481 #[fuchsia::test]
482 fn parse_legacy_module_features() {
483 let start_info = test_start_info(
484 vec![
485 "ashmem".to_string(),
486 "boot_notifier".to_string(),
487 "boot_notifier_cpu_boost:45".to_string(),
488 "framebuffer".to_string(),
489 "aspect_ratio:16:9".to_string(),
490 "magma_supported_vendors:0x13B5,0x8086".to_string(),
491 "mmcblk_stub".to_string(),
492 "perfetto:/dev/socket/traced_consumer".to_string(),
493 "thermal".to_string(),
494 "cooling:cpu,fcc=8".to_string(),
495 ],
496 Vec::new(),
497 );
498
499 let (_features, modules) =
500 parse_features(&start_info, &[]).expect("legacy features should translate");
501 assert!(modules.is_enabled("ashmem"));
502 assert!(modules.is_enabled("mmcblk_stub"));
503 assert_eq!(modules.args("boot_notifier"), Some(&["cpu_boost_secs=45".to_string()][..]));
504 assert_eq!(modules.args("framebuffer"), Some(&["aspect_ratio=16:9".to_string()][..]));
505 assert_eq!(
506 modules.args("magma"),
507 Some(&["supported_vendors=0x13B5,0x8086".to_string()][..])
508 );
509 assert_eq!(
510 modules.args("perfetto"),
511 Some(&["socket_path=/dev/socket/traced_consumer".to_string()][..])
512 );
513 assert_eq!(modules.args("thermal"), Some(&["cooling=cpu,fcc=8".to_string()][..]));
514 }
515
516 #[fuchsia::test]
517 fn reject_boot_notifier_cpu_boost_without_boot_notifier() {
518 let start_info =
519 test_start_info(vec!["boot_notifier_cpu_boost:60".to_string()], Vec::new());
520 assert!(parse_features(&start_info, &[]).is_err());
521 }
522
523 #[fuchsia::test]
524 fn reject_mutually_exclusive_odpm_and_hvdcp_opti() {
525 let start_info =
526 test_start_info(Vec::new(), vec!["google_odpm".to_string(), "hvdcp_opti".to_string()]);
527 assert!(parse_features(&start_info, &[]).is_err());
528
529 let mixed_info =
530 test_start_info(vec!["google_odpm".to_string()], vec!["hvdcp_opti".to_string()]);
531 assert!(parse_features(&mixed_info, &[]).is_err());
532 }
533
534 #[fuchsia::test]
535 fn hvdcp_opti_supersedes_default_battery_manager_and_rejects_explicit_conflict() {
536 let default_info = test_start_info(Vec::new(), Vec::new());
537 let (_features, default_modules) =
538 parse_features(&default_info, &[]).expect("default modules should parse");
539 assert!(default_modules.is_enabled("battery_manager"));
540 assert!(default_modules.is_enabled("goog_chip_info"));
541
542 let hvdcp_info = test_start_info(Vec::new(), vec!["hvdcp_opti".to_string()]);
543 let (_features, hvdcp_modules) =
544 parse_features(&hvdcp_info, &[]).expect("hvdcp_opti should supersede default battery");
545 assert!(hvdcp_modules.is_enabled("hvdcp_opti"));
546 assert!(!hvdcp_modules.is_enabled("battery_manager"));
547 assert_eq!(hvdcp_modules.args("battery_manager"), None);
548
549 let explicit_conflict = test_start_info(
550 Vec::new(),
551 vec!["battery_manager".to_string(), "hvdcp_opti".to_string()],
552 );
553 assert!(parse_features(&explicit_conflict, &[]).is_err());
554 }
555
556 #[fuchsia::test]
557 fn bare_module_does_not_overwrite_module_args() {
558 let start_info = test_start_info(
559 vec!["boot_notifier".to_string()],
560 vec![
561 "boot_notifier cpu_boost_secs=60".to_string(),
562 "boot_notifier".to_string(),
563 "framebuffer aspect_ratio=9:16".to_string(),
564 "framebuffer".to_string(),
565 "magma supported_vendors=0x13B5".to_string(),
566 "magma".to_string(),
567 "thermal cooling=cpu,fcc=8".to_string(),
568 "thermal".to_string(),
569 ],
570 );
571
572 let (_features, modules) =
573 parse_features(&start_info, &[]).expect("parse_features should succeed");
574 assert_eq!(modules.args("boot_notifier"), Some(&["cpu_boost_secs=60".to_string()][..]));
575 assert_eq!(modules.args("framebuffer"), Some(&["aspect_ratio=9:16".to_string()][..]));
576 assert_eq!(modules.args("magma"), Some(&["supported_vendors=0x13B5".to_string()][..]));
577 assert_eq!(modules.args("thermal"), Some(&["cooling=cpu,fcc=8".to_string()][..]));
578 }
579
580 #[fuchsia::test]
581 fn reject_empty_module_specification() {
582 let start_info = test_start_info(Vec::new(), vec![" ".to_string()]);
583 assert!(parse_features(&start_info, &[]).is_err());
584 }
585
586 #[fuchsia::test]
587 fn disable_default_and_shard_modules() {
588 let start_info = test_start_info(
589 Vec::new(),
590 vec![
591 "-zram".to_string(),
592 "-gfxstream".to_string(),
593 "gfxstream".to_string(),
594 "ashmem".to_string(),
595 "-ashmem".to_string(),
596 ],
597 );
598
599 let (_features, modules) =
600 parse_features(&start_info, &[]).expect("parse_features should succeed");
601 assert!(modules.is_enabled("rtc"));
602 assert!(modules.is_enabled("loop"));
603 assert!(modules.is_enabled("power_residency"));
604 assert!(!modules.is_enabled("zram"));
605 assert_eq!(modules.args("zram"), None);
606 assert!(!modules.is_enabled("gfxstream"));
607 assert!(!modules.is_enabled("ashmem"));
608 }
609
610 #[fuchsia::test]
611 fn reject_invalid_disable_module_specifications() {
612 for invalid_spec in ["-", "--foo", "-zram --extra-arg", "-unknown_module"] {
613 let start_info = test_start_info(Vec::new(), vec![invalid_spec.to_string()]);
614 assert!(
615 parse_features(&start_info, &[]).is_err(),
616 "expected '{invalid_spec}' to be rejected"
617 );
618 }
619 }
620
621 #[fuchsia::test]
622 fn program_modules_override_legacy_feature_arguments() {
623 let start_info = test_start_info(
624 vec![
625 "boot_notifier".to_string(),
626 "boot_notifier_cpu_boost:45".to_string(),
627 "framebuffer".to_string(),
628 "aspect_ratio:16:9".to_string(),
629 "magma_supported_vendors:0x13B5".to_string(),
630 "perfetto:/dev/socket/old".to_string(),
631 "cooling:cpu".to_string(),
632 ],
633 vec![
634 "boot_notifier cpu_boost_secs=120".to_string(),
635 "framebuffer aspect_ratio=9:16".to_string(),
636 "magma supported_vendors=0x8086".to_string(),
637 "perfetto socket_path=/dev/socket/new".to_string(),
638 "thermal cooling=fcc=8".to_string(),
639 ],
640 );
641
642 let (_features, modules) =
643 parse_features(&start_info, &[]).expect("parse_features should succeed");
644 assert_eq!(modules.args("boot_notifier"), Some(&["cpu_boost_secs=120".to_string()][..]));
645 assert_eq!(modules.args("framebuffer"), Some(&["aspect_ratio=9:16".to_string()][..]));
646 assert_eq!(modules.args("magma"), Some(&["supported_vendors=0x8086".to_string()][..]));
647 assert_eq!(
648 modules.args("perfetto"),
649 Some(&["socket_path=/dev/socket/new".to_string()][..])
650 );
651 assert_eq!(modules.args("thermal"), Some(&["cooling=fcc=8".to_string()][..]));
652
653 let disabled_boot_notifier = test_start_info(
654 vec!["boot_notifier".to_string(), "boot_notifier_cpu_boost:45".to_string()],
655 vec!["-boot_notifier".to_string()],
656 );
657 let (_features, modules) = parse_features(&disabled_boot_notifier, &[])
658 .expect("disabling legacy boot_notifier via program.modules should succeed");
659 assert!(!modules.is_enabled("boot_notifier"));
660 }
661
662 #[fuchsia::test]
663 fn reject_unknown_modules_and_invalid_module_arguments() {
664 for invalid_spec in [
665 "unknown_module",
666 "ashmem unexpected",
667 "ashmem --unexpected",
668 "boot_notifier cpu_boost_secs=-1",
669 "boot_notifier --cpu-boost-secs=60",
670 "boot_notifier cpu-boost-secs=60",
671 "boot_notifier unknown_option=1",
672 "boot_notifier =60",
673 "framebuffer aspect_ratio=0:9",
674 "framebuffer aspect_ratio=9:0",
675 "framebuffer aspect_ratio=invalid",
676 "magma supported_vendors=not_hex",
677 "perfetto socket_path=",
678 "thermal cooling=",
679 ] {
680 let start_info = test_start_info(Vec::new(), vec![invalid_spec.to_string()]);
681 assert!(
682 parse_features(&start_info, &[]).is_err(),
683 "expected '{invalid_spec}' to be rejected"
684 );
685 }
686 }
687
688 #[fuchsia::test]
689 fn parse_devtmpfs_feature_and_mount_fallback() {
690 let default_info = test_start_info(Vec::new(), Vec::new());
691 let (features, _modules) =
692 parse_features(&default_info, &[]).expect("parse_features should succeed");
693 assert!(!features.kernel.devtmpfs);
694
695 let feature_info = test_start_info(vec!["devtmpfs".to_string()], Vec::new());
696 let (features, _modules) =
697 parse_features(&feature_info, &[]).expect("parse_features should succeed");
698 assert!(features.kernel.devtmpfs);
699
700 let mut program_mount_info = test_start_info(Vec::new(), Vec::new());
701 program_mount_info.program.mounts.push("/dev:devtmpfs::nosuid,relatime".to_string());
702 let (features, _modules) =
703 parse_features(&program_mount_info, &[]).expect("parse_features should succeed");
704 assert!(features.kernel.devtmpfs);
705
706 let mut additional_mount_info = test_start_info(Vec::new(), Vec::new());
707 additional_mount_info
708 .config
709 .additional_mounts
710 .push("/dev:devtmpfs::nosuid,relatime".to_string());
711 let (features, _modules) =
712 parse_features(&additional_mount_info, &[]).expect("parse_features should succeed");
713 assert!(features.kernel.devtmpfs);
714 }
715}