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    // Legacy module feature flags retained for backwards compatibility with `ExtraFeatures`,
19    // Product Assembly, and prebuilt container manifests. Prefer configuring modules via
20    // `program.modules` in container manifests.
21    AndroidUsb,
22    AspectRatio,
23    Ashmem,
24    BootNotifier,
25    BootNotifierCpuBoost,
26    Cooling,
27    DataCollectionConsentSync,
28    Fastrpc,
29    Framebuffer,
30    Gfxstream,
31    // Note: GoogleOdpm statically registers `iio:device0`.
32    // Conflicts with `HvdcpOpti`, which also statically registers `iio:device0`.
33    // Until dynamic IIO numbering is supported, they must not be used at the same time.
34    GoogleOdpm,
35    Gralloc,
36    // Note: HvdcpOpti statically registers IIO devices (`iio:device0`, `iio:device1`).
37    // Conflicts with `GoogleOdpm`, which also statically registers `iio:device0`.
38    // Until dynamic IIO numbering is supported, they must not be used at the same time.
39    HvdcpOpti,
40    Kgsl,
41    Magma,
42    MagmaSupportedVendors,
43    Nanohub,
44    NetworkManager,
45    Perfetto,
46    Thermal,
47    WakeupTest,
48
49    // Container and kernel behavior features.
50    AdditionalMounts,
51    AndroidBootreason,
52    AndroidSerialno,
53    Bpf,
54    Container,
55    CustomArtifacts,
56    EnableSuid,
57    ErrorOnFailedReboot,
58    // TODO(https://fxbug.dev/485370648) remove when unnecessary
59    FakeIon,
60    IoUring,
61    LogSysfsLookupMisses,
62    MmcblkStub,
63    PingGroupRange,
64    RemotevolInProcess,
65    RootfsRw,
66    Selinux,
67    SelinuxTestSuite,
68    TestData,
69    UnifiedTracing,
70    Wifi,
71}
72
73/// Error returned when a feature is not recognized.
74#[derive(Debug, Error)]
75#[error("unsupported feature: {0}")]
76pub struct UnsupportedFeatureError(String);
77
78impl Feature {
79    /// Parses the name of a feature from a string.
80    pub fn try_parse(s: &str) -> Result<Feature, UnsupportedFeatureError> {
81        Feature::from_str(s).map_err(|_| UnsupportedFeatureError(s.to_string()))
82    }
83
84    /// Parses a feature and args from a string.
85    pub fn try_parse_feature_and_args(
86        s: &str,
87    ) -> Result<(Feature, Option<String>), UnsupportedFeatureError> {
88        let (raw_flag, raw_args) =
89            s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
90        Self::try_parse(raw_flag).map(|feature| (feature, raw_args))
91    }
92}
93
94/// A feature together with any arguments that go along with it, if specified.
95#[derive(Debug, Clone, PartialEq)]
96pub struct FeatureAndArgs {
97    /// The feature.
98    pub feature: Feature,
99    /// If specified, the (unparsed) arguments for the feature.
100    pub raw_args: Option<String>,
101}
102
103impl FeatureAndArgs {
104    /// Parses a feature and args from a string that separates them with `:`, e.g. "bpf:v2".
105    ///
106    /// If there is no `:` then the whole string is interpreted as the feature name.
107    pub fn try_parse(s: &str) -> Result<FeatureAndArgs, UnsupportedFeatureError> {
108        let (raw_flag, raw_args) =
109            s.split_once(':').map(|(f, a)| (f, Some(a.to_string()))).unwrap_or((s, None));
110        let feature = Feature::try_parse(raw_flag)?;
111        Ok(FeatureAndArgs { feature, raw_args })
112    }
113}
114
115impl Display for FeatureAndArgs {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
117        let FeatureAndArgs { feature, raw_args } = self;
118        match raw_args {
119            None => feature.fmt(f),
120            Some(raw_args) => format_args!("{feature}:{raw_args}").fmt(f),
121        }
122    }
123}
124
125#[cfg(test)]
126mod test {
127    use super::*;
128
129    #[test]
130    fn feature_serde() {
131        for (feature, expected_str) in [
132            (Feature::AndroidSerialno, "android_serialno"),
133            (Feature::AndroidBootreason, "android_bootreason"),
134            (Feature::AspectRatio, "aspect_ratio"),
135            (Feature::Container, "container"),
136            (Feature::CustomArtifacts, "custom_artifacts"),
137            (Feature::Ashmem, "ashmem"),
138            (Feature::BootNotifier, "boot_notifier"),
139            (Feature::BootNotifierCpuBoost, "boot_notifier_cpu_boost"),
140            (Feature::Framebuffer, "framebuffer"),
141            (Feature::Gralloc, "gralloc"),
142            (Feature::Kgsl, "kgsl"),
143            (Feature::Magma, "magma"),
144            (Feature::MagmaSupportedVendors, "magma_supported_vendors"),
145            (Feature::Nanohub, "nanohub"),
146            (Feature::NetworkManager, "network_manager"),
147            (Feature::Gfxstream, "gfxstream"),
148            (Feature::Bpf, "bpf"),
149            (Feature::EnableSuid, "enable_suid"),
150            (Feature::IoUring, "io_uring"),
151            (Feature::ErrorOnFailedReboot, "error_on_failed_reboot"),
152            (Feature::Perfetto, "perfetto"),
153            (Feature::PingGroupRange, "ping_group_range"),
154            (Feature::RootfsRw, "rootfs_rw"),
155            (Feature::Selinux, "selinux"),
156            (Feature::SelinuxTestSuite, "selinux_test_suite"),
157            (Feature::TestData, "test_data"),
158            (Feature::Thermal, "thermal"),
159            (Feature::Cooling, "cooling"),
160            (Feature::DataCollectionConsentSync, "data_collection_consent_sync"),
161            (Feature::HvdcpOpti, "hvdcp_opti"),
162            (Feature::Wifi, "wifi"),
163            (Feature::AdditionalMounts, "additional_mounts"),
164            (Feature::WakeupTest, "wakeup_test"),
165            (Feature::MmcblkStub, "mmcblk_stub"),
166            (Feature::AndroidUsb, "android_usb"),
167            (Feature::GoogleOdpm, "google_odpm"),
168            // TODO(https://fxbug.dev/485370648) remove when unnecessary
169            (Feature::FakeIon, "fake_ion"),
170            (Feature::UnifiedTracing, "unified_tracing"),
171            (Feature::RemotevolInProcess, "remotevol_in_process"),
172            (Feature::LogSysfsLookupMisses, "log_sysfs_lookup_misses"),
173        ] {
174            let string = feature.to_string();
175            assert_eq!(string.as_str(), expected_str);
176            assert_eq!(Feature::try_parse(&string).expect("should parse"), feature);
177        }
178    }
179
180    #[test]
181    fn deserialize_feature_and_args() {
182        let FeatureAndArgs { feature, raw_args } =
183            FeatureAndArgs::try_parse("bpf:v2").expect("should parse successfully");
184        assert_eq!(feature, Feature::Bpf);
185        assert_eq!(raw_args.as_ref().expect("should be populated"), "v2");
186    }
187}