Skip to main content

starnix_features/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use strum_macros::EnumString;
6use thiserror::Error;
7
8use std::fmt::Display;
9use std::str::FromStr;
10
11/// Features are a way to enable or disable specific starnix behaviors.
12///
13/// These are specified in the component manifest of the starnix container,
14/// or explicitly provided to the kernel constructor in tests.
15#[derive(Debug, Clone, Copy, PartialEq, EnumString, strum_macros::Display)]
16#[strum(serialize_all = "snake_case")]
17pub enum Feature {
18    AndroidSerialno,
19    AndroidBootreason,
20    AspectRatio,
21    Container,
22    CustomArtifacts,
23    Ashmem,
24    BootNotifier,
25    BootNotifierCpuBoost,
26    Framebuffer,
27    Gralloc,
28    Kgsl,
29    Magma,
30    MagmaSupportedVendors,
31    Nanohub,
32    Fastrpc,
33    NetworkManager,
34    Gfxstream,
35    Bpf,
36    EnableSuid,
37    IoUring,
38    ErrorOnFailedReboot,
39    Perfetto,
40    PingGroupRange,
41    RootfsRw,
42    Selinux,
43    SelinuxTestSuite,
44    TestData,
45    Thermal,
46    Cooling,
47    DataCollectionConsentSync,
48    HvdcpOpti,
49    Wifi,
50    AdditionalMounts,
51    WakeupTest,
52    MmcblkStub,
53    AndroidUsb,
54    // TODO(https://fxbug.dev/485370648) remove when unnecessary
55    FakeIon,
56}
57
58/// Error returned when a feature is not recognized.
59#[derive(Debug, Error)]
60#[error("unsupported feature: {0}")]
61pub struct UnsupportedFeatureError(String);
62
63impl Feature {
64    /// Parses the name of a feature from a string.
65    pub fn try_parse(s: &str) -> Result<Feature, UnsupportedFeatureError> {
66        Feature::from_str(s).map_err(|_| UnsupportedFeatureError(s.to_string()))
67    }
68
69    /// Parses a feature and args from a string.
70    pub fn try_parse_feature_and_args(
71        s: &str,
72    ) -> Result<(Feature, Option<String>), UnsupportedFeatureError> {
73        let (raw_flag, raw_args) =
74            s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
75        Self::try_parse(raw_flag).map(|feature| (feature, raw_args))
76    }
77}
78
79/// A feature together with any arguments that go along with it, if specified.
80#[derive(Debug, Clone, PartialEq)]
81pub struct FeatureAndArgs {
82    /// The feature.
83    pub feature: Feature,
84    /// If specified, the (unparsed) arguments for the feature.
85    pub raw_args: Option<String>,
86}
87
88impl FeatureAndArgs {
89    /// Parses a feature and args from a string that separates them with `:`, e.g. "bpf:v2".
90    ///
91    /// If there is no `:` then the whole string is interpreted as the feature name.
92    pub fn try_parse(s: &str) -> Result<FeatureAndArgs, UnsupportedFeatureError> {
93        let (raw_flag, raw_args) =
94            s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
95        let feature = Feature::try_parse(raw_flag)?;
96        Ok(FeatureAndArgs { feature, raw_args })
97    }
98}
99
100impl Display for FeatureAndArgs {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
102        let FeatureAndArgs { feature, raw_args } = self;
103        match raw_args {
104            None => feature.fmt(f),
105            Some(raw_args) => format_args!("{feature}:{raw_args}").fmt(f),
106        }
107    }
108}
109
110#[cfg(test)]
111mod test {
112    use super::*;
113
114    #[test]
115    fn feature_serde() {
116        for (feature, expected_str) in [
117            (Feature::AndroidSerialno, "android_serialno"),
118            (Feature::AndroidBootreason, "android_bootreason"),
119            (Feature::AspectRatio, "aspect_ratio"),
120            (Feature::Container, "container"),
121            (Feature::CustomArtifacts, "custom_artifacts"),
122            (Feature::Ashmem, "ashmem"),
123            (Feature::BootNotifier, "boot_notifier"),
124            (Feature::BootNotifierCpuBoost, "boot_notifier_cpu_boost"),
125            (Feature::Framebuffer, "framebuffer"),
126            (Feature::Gralloc, "gralloc"),
127            (Feature::Kgsl, "kgsl"),
128            (Feature::Magma, "magma"),
129            (Feature::MagmaSupportedVendors, "magma_supported_vendors"),
130            (Feature::Nanohub, "nanohub"),
131            (Feature::NetworkManager, "network_manager"),
132            (Feature::Gfxstream, "gfxstream"),
133            (Feature::Bpf, "bpf"),
134            (Feature::EnableSuid, "enable_suid"),
135            (Feature::IoUring, "io_uring"),
136            (Feature::ErrorOnFailedReboot, "error_on_failed_reboot"),
137            (Feature::Perfetto, "perfetto"),
138            (Feature::PingGroupRange, "ping_group_range"),
139            (Feature::RootfsRw, "rootfs_rw"),
140            (Feature::Selinux, "selinux"),
141            (Feature::SelinuxTestSuite, "selinux_test_suite"),
142            (Feature::TestData, "test_data"),
143            (Feature::Thermal, "thermal"),
144            (Feature::Cooling, "cooling"),
145            (Feature::DataCollectionConsentSync, "data_collection_consent_sync"),
146            (Feature::HvdcpOpti, "hvdcp_opti"),
147            (Feature::Wifi, "wifi"),
148            (Feature::AdditionalMounts, "additional_mounts"),
149            (Feature::WakeupTest, "wakeup_test"),
150            (Feature::MmcblkStub, "mmcblk_stub"),
151            (Feature::AndroidUsb, "android_usb"),
152            // TODO(https://fxbug.dev/485370648) remove when unnecessary
153            (Feature::FakeIon, "fake_ion"),
154        ] {
155            let string = feature.to_string();
156            assert_eq!(string.as_str(), expected_str);
157            assert_eq!(Feature::try_parse(&string).expect("should parse"), feature);
158        }
159    }
160
161    #[test]
162    fn deserialize_feature_and_args() {
163        let FeatureAndArgs { feature, raw_args } =
164            FeatureAndArgs::try_parse("bpf:v2").expect("should parse successfully");
165        assert_eq!(feature, Feature::Bpf);
166        assert_eq!(raw_args.as_ref().expect("should be populated"), "v2");
167    }
168}