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