Next Generation WASM Microkernel Operating System
wasm
os
rust
microkernel
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8#![allow(unused)]
9
10use std::collections::BTreeSet;
11use std::fmt::{Display, Formatter};
12use std::hash::{DefaultHasher, Hasher};
13use std::path::{Path, PathBuf};
14
15use color_eyre::eyre::{Context, bail, eyre};
16use serde::Deserialize;
17
18#[derive(Clone, Debug, Deserialize)]
19#[serde(rename_all = "kebab-case", deny_unknown_fields)]
20struct RawConfiguration {
21 arch: Architecture,
22 name: String,
23 #[serde(default)]
24 version: u32,
25 #[serde(default)]
26 max_log_level: LogLevel,
27 kernel: Kernel,
28 loader: Loader,
29}
30
31#[derive(Clone, Copy, Debug, Deserialize)]
32pub enum Architecture {
33 #[serde(rename = "riscv64")]
34 Riscv64,
35}
36
37#[repr(u8)]
38#[derive(Clone, Copy, Default, Debug, Deserialize)]
39#[serde(rename_all = "kebab-case")]
40pub enum LogLevel {
41 Trace = 0,
42 Debug,
43 #[default]
44 Info,
45 Warn,
46 Error,
47}
48
49#[derive(Clone, Debug, Deserialize)]
50#[serde(rename_all = "kebab-case", deny_unknown_fields)]
51pub struct Kernel {
52 pub target: RawRustTarget,
53 pub stacksize_pages: Option<u32>,
54 pub max_log_level: Option<LogLevel>,
55 #[serde(default)]
56 pub features: Vec<String>,
57 #[serde(default)]
58 pub no_default_features: bool,
59}
60
61#[derive(Clone, Debug, Deserialize)]
62#[serde(rename_all = "kebab-case", deny_unknown_fields)]
63pub struct Loader {
64 pub target: RawRustTarget,
65 #[serde(default)]
66 pub features: Vec<String>,
67 #[serde(default)]
68 pub no_default_features: bool,
69}
70
71#[derive(Clone, Debug, Deserialize)]
72pub struct RawRustTarget(String);
73
74#[derive(Clone, Debug)]
75pub enum RustTarget {
76 Builtin(String),
77 Json(PathBuf),
78}
79
80impl RawRustTarget {
81 pub fn resolve(&self, configuration: &Configuration) -> RustTarget {
82 if self.0.ends_with(".json") {
83 RustTarget::Json(configuration.resolve_path(&self.0).unwrap())
84 } else {
85 RustTarget::Builtin(self.0.clone())
86 }
87 }
88}
89
90impl RustTarget {
91 pub fn name(&self) -> &str {
92 match self {
93 RustTarget::Builtin(name) => name.as_str(),
94 RustTarget::Json(path) => path.file_stem().unwrap().to_str().unwrap(),
95 }
96 }
97}
98
99impl Display for RustTarget {
100 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101 match self {
102 RustTarget::Builtin(s) => write!(f, "{s}"),
103 RustTarget::Json(p) => write!(f, "{}", p.display()),
104 }
105 }
106}
107
108#[derive(Clone, Debug)]
109pub struct Configuration {
110 pub arch: Architecture,
111 pub name: String,
112 pub version: u32,
113 pub max_log_level: LogLevel,
114 pub kernel: Kernel,
115 pub loader: Loader,
116 pub buildhash: u64,
117 pub file_path: PathBuf,
118}
119
120impl Configuration {
121 pub fn from_file(file_path: &Path) -> crate::Result<Self> {
122 let mut hasher = DefaultHasher::new();
123
124 let doc = read_and_flatten_toml(file_path, &mut hasher, &mut BTreeSet::new())?;
125 let configuration_contents = doc.to_string();
126
127 let toml: RawConfiguration = toml::from_str(&configuration_contents)?;
128
129 // if we had any other checks, perform them here
130
131 Ok(Self {
132 arch: toml.arch,
133 name: toml.name,
134 version: toml.version,
135 max_log_level: toml.max_log_level,
136 kernel: toml.kernel,
137 loader: toml.loader,
138 buildhash: hasher.finish(),
139 file_path: file_path.to_path_buf(),
140 })
141 }
142
143 pub fn resolve_path(&self, path: impl AsRef<Path>) -> crate::Result<PathBuf> {
144 self.file_path
145 .parent()
146 .unwrap()
147 .join(path)
148 .canonicalize()
149 .map_err(Into::into)
150 }
151}
152
153fn read_and_flatten_toml(
154 configuration: &Path,
155 hasher: &mut DefaultHasher,
156 seen: &mut BTreeSet<PathBuf>,
157) -> crate::Result<toml_edit::DocumentMut> {
158 use toml_patch::merge_toml_documents;
159
160 // Prevent diamond inheritance
161 if !seen.insert(configuration.to_owned()) {
162 bail!(
163 "{configuration:?} is inherited more than once; \
164 diamond dependencies are not allowed"
165 );
166 }
167 let configuration_contents = std::fs::read(configuration)
168 .with_context(|| format!("could not read {}", configuration.display()))?;
169
170 // Accumulate the contents into the buildhash here, so that we hash both
171 // the inheritance file and the target (recursively, if necessary)
172 hasher.write(&configuration_contents);
173
174 let configuration_contents =
175 std::str::from_utf8(&configuration_contents).context("failed to read manifest as UTF-8")?;
176
177 // Additive TOML file inheritance
178 let mut doc = configuration_contents
179 .parse::<toml_edit::DocumentMut>()
180 .context("failed to parse TOML file")?;
181 let Some(inherited_from) = doc.remove("inherit") else {
182 // No further inheritance, so return the current document
183 return Ok(doc);
184 };
185
186 use toml_edit::{Item, Value};
187 let mut original = match inherited_from {
188 // Single inheritance
189 Item::Value(Value::String(s)) => {
190 let file = configuration.parent().unwrap().join(s.value());
191 read_and_flatten_toml(&file, hasher, seen)
192 .with_context(|| format!("Could not load {file:?}"))?
193 }
194 // Multiple inheritance, applied sequentially
195 Item::Value(Value::Array(a)) => {
196 let mut doc: Option<toml_edit::DocumentMut> = None;
197 for a in a.iter() {
198 if let Value::String(s) = a {
199 let file = configuration.parent().unwrap().join(s.value());
200 let next: toml_edit::DocumentMut =
201 read_and_flatten_toml(&file, hasher, seen)
202 .with_context(|| format!("Could not load {file:?}"))?;
203 match doc.as_mut() {
204 Some(doc) => merge_toml_documents(doc, next)?,
205 None => doc = Some(next),
206 }
207 } else {
208 bail!("could not inherit from {a}; bad type");
209 }
210 }
211 doc.ok_or_else(|| eyre!("inherit array cannot be empty"))?
212 }
213 v => bail!("could not inherit from {v}; bad type"),
214 };
215
216 // Finally, apply any changes that are local in this file
217 merge_toml_documents(&mut original, doc)?;
218 Ok(original)
219}