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