Skip to main content

hermit_entry/
config.rs

1//! The image `hermit.toml` config file format.
2//!
3//! All file paths are relative to the image root.
4
5use alloc::borrow::Cow;
6use alloc::vec::Vec;
7use core::fmt;
8
9/// The possible errors which the parser might emit.
10type ParserError = toml::de::Error;
11
12/// The configuration toplevel structure.
13#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
14#[serde(tag = "version")]
15pub enum Config<'a> {
16    /// The first (and current) version of the config format.
17    #[serde(rename = "1")]
18    V1 {
19        /// Input parameter for the kernel and application
20        #[serde(borrow)]
21        input: Input<'a>,
22
23        /// Minimal requirements for an image to be able to run as expected
24        #[serde(default)]
25        requirements: Requirements,
26
27        /// Kernel ELF file path
28        #[serde(borrow)]
29        kernel: Cow<'a, str>,
30    },
31}
32
33impl Config<'_> {
34    /// The default configuration file name, relative to the image root.
35    pub const DEFAULT_PATH: &'static str = "hermit.toml";
36}
37
38/// Input parameter for the kernel and application
39#[derive(Clone, Debug, PartialEq, serde::Deserialize)]
40pub struct Input<'a> {
41    /// Arguments to be passed to the kernel
42    #[serde(borrow)]
43    pub kernel_args: Vec<Cow<'a, str>>,
44
45    /// Arguments to be passed to the application
46    #[serde(borrow)]
47    pub app_args: Vec<Cow<'a, str>>,
48
49    /// Environment variables
50    #[serde(borrow, default)]
51    pub env_vars: Vec<Cow<'a, str>>,
52}
53
54/// Minimal requirements for an image to be able to run as expected
55#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize)]
56pub struct Requirements {
57    /// Minimum RAM
58    pub memory: Option<byte_unit::Byte>,
59
60    /// Minimum amount of CPUs
61    #[serde(default)]
62    pub cpus: u32,
63}
64
65/// An error from [`parse_tar`].
66#[derive(Clone, Debug)]
67pub struct ParseTarError(ParseTarErrorInner);
68
69impl From<ParseTarErrorInner> for ParseTarError {
70    #[inline]
71    fn from(x: ParseTarErrorInner) -> Self {
72        Self(x)
73    }
74}
75
76#[derive(Clone, Debug)]
77#[non_exhaustive]
78enum ParseTarErrorInner {
79    /// The Hermit image tar is corrupt.
80    TarCorrupt,
81
82    /// [`DEFAULT_CONFIG_NAME`]
83    /// either couldn't be found in the image or isn't a regular file.
84    ConfigResolve,
85
86    /// The Hermit image configuration file failed to parse due to being non-UTF8.
87    ConfigUtf8Error(core::str::Utf8Error),
88
89    /// The Hermit image configuration file failed to parse due to being invalid TOML.
90    ConfigTomlParseError(ParserError),
91
92    /// The Kernel specified in the image configuration file
93    /// either couldn't be found in the image or isn't a regular file.
94    KernelResolve,
95}
96
97impl fmt::Display for ParseTarErrorInner {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            Self::TarCorrupt => f.write_str("tar file in Hermit image is corrupt"),
101            Self::ConfigResolve => {
102                write!(f, "couldn't find Hermit image configuration file")
103            }
104            Self::ConfigUtf8Error(e) => write!(f, "Hermit image configuration is invalid: {e}"),
105            Self::ConfigTomlParseError(e) => {
106                write!(f, "Hermit image configuration is invalid: {e}")
107            }
108            Self::KernelResolve => write!(f, "couldn't find Hermit kernel in image"),
109        }
110    }
111}
112
113impl fmt::Display for ParseTarError {
114    #[inline]
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        fmt::Display::fmt(&self.0, f)
117    }
118}
119
120impl core::error::Error for ParseTarErrorInner {}
121impl core::error::Error for ParseTarError {}
122
123/// Parsed data from an image
124pub struct ConfigHandle<'a> {
125    /// The image configuration
126    pub config: Config<'a>,
127
128    /// The raw kernel ELF slice
129    pub raw_kernel: &'a [u8],
130}
131
132/// A convenience function to handle looking up the config
133/// in a tar file (decompressed) and retrieve the kernel slice.
134pub fn parse_tar(image: &[u8]) -> Result<ConfigHandle<'_>, ParseTarError> {
135    use ParseTarErrorInner as Error;
136
137    let taref = tar_no_std::TarArchiveRef::new(image).map_err(|_| Error::TarCorrupt)?;
138
139    fn lookup_in_image<'a>(
140        taref: &tar_no_std::TarArchiveRef<'a>,
141        f: &str,
142    ) -> Result<Option<&'a [u8]>, Error> {
143        let f = {
144            let f = f.as_bytes();
145            let mut tmp = [0u8; 256];
146            if f.len() >= 256 {
147                return Ok(None);
148            }
149            tmp.copy_from_slice(f);
150            tar_no_std::TarFormatString::new(tmp)
151        };
152
153        let mut ret = None;
154        for i in taref.entries() {
155            // multiple entries with the same name might exist,
156            // latest entry wins / overwrites existing ones
157            if i.filename() == f {
158                ret = Some(i.data());
159            }
160        }
161
162        Ok(ret)
163    }
164
165    let config_slice =
166        lookup_in_image(&taref, Config::DEFAULT_PATH)?.ok_or(Error::ConfigResolve)?;
167    let config_slice = core::str::from_utf8(config_slice).map_err(Error::ConfigUtf8Error)?;
168    let config: Config<'_> = toml::from_str(config_slice).map_err(Error::ConfigTomlParseError)?;
169
170    let kernel_name: &str = match &config {
171        Config::V1 { kernel, .. } => kernel,
172    };
173
174    let raw_kernel = lookup_in_image(&taref, kernel_name)?.ok_or(Error::KernelResolve)?;
175
176    Ok(ConfigHandle { config, raw_kernel })
177}
178
179#[cfg(test)]
180mod tests {
181    #[test]
182    fn test_parsing() {
183        let dat = r#"
184version = "1"
185kernel = "/kernel.elf"
186
187[input]
188kernel_args = []
189app_args = []
190
191[requirements]
192"#;
193        let parsed: super::Config = toml::from_str(dat).unwrap();
194        use alloc::vec;
195        assert_eq!(
196            parsed,
197            super::Config::V1 {
198                kernel: "/kernel.elf".into(),
199                input: super::Input {
200                    kernel_args: vec![],
201                    app_args: vec![],
202                    env_vars: vec![],
203                },
204                requirements: super::Requirements {
205                    memory: None,
206                    cpus: 0,
207                },
208            }
209        );
210    }
211}