Skip to content

Commit 60353e9

Browse files
committed
feat: Merge hermit-image-reader into this crate
Signed-off-by: Ellen Εμιλία Άννα Zscheile <[email protected]>
1 parent 18a0306 commit 60353e9

File tree

7 files changed

+605
-2
lines changed

7 files changed

+605
-2
lines changed

Cargo.toml

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,60 @@ rustdoc-args = ["--cfg", "docsrs"]
2222
[dependencies]
2323
align-address = "0.3"
2424
const_parse = "1"
25-
goblin = { version = "0.10", default-features = false, features = ["elf64"], optional = true }
2625
log = { version = "0.4", optional = true }
2726
plain = { version = "0.2", optional = true }
2827
time = { version = "0.3", default-features = false }
2928
uhyve-interface = "0.1"
3029

30+
[dependencies.byte-unit]
31+
version = "5"
32+
default-features = false
33+
features = ["byte", "serde"]
34+
optional = true
35+
36+
[dependencies.compression]
37+
version = "0.1"
38+
default-features = false
39+
features = ["gzip"]
40+
optional = true
41+
42+
[dependencies.goblin]
43+
version = "0.10"
44+
default-features = false
45+
features = ["elf64"]
46+
optional = true
47+
48+
[dependencies.num-traits]
49+
version = "0.2"
50+
default-features = false
51+
52+
[dependencies.serde]
53+
version = "1"
54+
default-features = false
55+
features = ["alloc", "derive"]
56+
optional = true
57+
58+
[dependencies.toml]
59+
version = "0.9"
60+
default-features = false
61+
features = ["parse", "serde"]
62+
optional = true
63+
64+
[dependencies.yoke]
65+
version = "0.8"
66+
default-features = false
67+
features = ["derive"]
68+
optional = true
69+
70+
[dev-dependencies.proptest]
71+
version = "1.9"
72+
3173
[features]
3274
default = []
33-
loader = ["log", "goblin", "plain"]
75+
std = ["alloc", "compression/std"]
76+
alloc = []
77+
compression = ["alloc", "dep:compression"]
78+
loader = ["dep:log", "dep:goblin", "dep:plain"]
3479
kernel = []
80+
config = ["alloc", "dep:byte-unit", "dep:serde", "dep:toml"]
81+
thin-tree = ["alloc", "dep:yoke"]

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,20 @@ at your option.
2222
Unless you explicitly state otherwise, any contribution intentionally submitted
2323
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
2424
dual licensed as above, without any additional terms or conditions.
25+
26+
## Hermit images
27+
28+
This Rust crate also implements a basic reader for Hermit images.
29+
Overall, these are just `.tar.gz` (i.e. gzipped tar) files.
30+
31+
They contain at least 2 special entries:
32+
* The config file (in TOML format), at `hermit.toml` in the image root.
33+
The expected entries are described in the crate documentation in `hermit_entry::config::Config` (requires enabling the `config` feature).
34+
* A Hermit Kernel ELF file, whose path is specified in the config.
35+
36+
For performance reasons, it should be preferred to put the config and kernel
37+
as the first two entries of the image (tar files don't have any sorting or index,
38+
except that normally, the latest entry of the file takes precedence).
39+
40+
The image itself is mapped (from the Hermit kernel perspective) into a path
41+
(`mount_point`) specified in the config file.

src/config.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use alloc::string::String;
2+
use alloc::vec::Vec;
3+
4+
pub const DEFAULT_CONFIG_NAME: &str = "hermit.toml";
5+
pub type ParserError = toml::de::Error;
6+
7+
/// The image `hermit.toml` config file format.
8+
///
9+
/// All file paths are relative to the image root.
10+
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
11+
#[serde(tag = "version")]
12+
pub enum Config {
13+
#[serde(rename = "1")]
14+
V1 {
15+
input: Input,
16+
#[serde(default)]
17+
requirements: Requirements,
18+
19+
kernel: String,
20+
mount_point: String,
21+
},
22+
}
23+
24+
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
25+
pub struct Input {
26+
/// Arguments to be passed to the kernel
27+
pub kernel_args: Vec<String>,
28+
29+
/// Arguments to be passed to the application
30+
pub app_args: Vec<String>,
31+
32+
/// Environment variables
33+
pub env_vars: Vec<String>,
34+
}
35+
36+
#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
37+
pub struct Requirements {
38+
pub memory: Option<byte_unit::Byte>,
39+
40+
#[serde(default)]
41+
pub cpus: u32,
42+
}
43+
44+
#[inline]
45+
pub fn parse(data: &[u8]) -> Result<Config, ParserError> {
46+
toml::from_slice(data)
47+
}

src/filename.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use core::{cmp, fmt};
2+
3+
const SEP: u8 = b'/';
4+
5+
// taken from `tar-rs`
6+
pub fn truncate(slice: &[u8]) -> &[u8] {
7+
match slice.iter().position(|i| *i == 0) {
8+
Some(i) => &slice[..i],
9+
None => slice,
10+
}
11+
}
12+
13+
// FIXME: once `slice`.split_once is stable, we can get rid of this.
14+
fn split_once(this: &[u8], on: u8) -> Option<(&[u8], &[u8])> {
15+
let index = this.iter().position(|i| *i == on)?;
16+
Some((&this[..index], &this[index + 1..]))
17+
}
18+
19+
#[derive(Clone, Copy, Debug, Eq)]
20+
pub enum Filename<'a> {
21+
One(&'a [u8]),
22+
Two(&'a [u8], &'a [u8]),
23+
}
24+
25+
impl Filename<'_> {
26+
/// Truncate the filename to the first `n` components
27+
#[must_use = "input is not modified"]
28+
pub fn as_truncated(self, mut n: usize) -> Self {
29+
let handle_parts = |n: &mut usize, x: &mut &[u8]| {
30+
if *n > 0 {
31+
let mut offset = 0;
32+
for i in (*x).split(|i| *i == SEP) {
33+
*n -= 1;
34+
offset += i.len();
35+
if *n == 0 {
36+
// truncate
37+
*x = &x[..offset];
38+
break;
39+
}
40+
// separator
41+
offset += 1;
42+
}
43+
}
44+
};
45+
46+
match self {
47+
Self::One(_) if n == 0 => Self::One(&[]),
48+
Self::One(mut x) => {
49+
handle_parts(&mut n, &mut x);
50+
Self::One(x)
51+
}
52+
Self::Two(mut x, mut y) => {
53+
handle_parts(&mut n, &mut x);
54+
if n >= 1 {
55+
handle_parts(&mut n, &mut y);
56+
Self::Two(x, y)
57+
} else {
58+
Self::One(x)
59+
}
60+
}
61+
}
62+
}
63+
}
64+
65+
impl fmt::Display for Filename<'_> {
66+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67+
match self {
68+
Self::One(x) => write!(f, "{:?}", x),
69+
Self::Two(x, y) => write!(f, "{:?}/{:?}", x, y),
70+
}
71+
}
72+
}
73+
74+
impl cmp::PartialEq for Filename<'_> {
75+
fn eq(&self, oth: &Self) -> bool {
76+
let (mut this, mut oth) = (*self, *oth);
77+
loop {
78+
match (this.next(), oth.next()) {
79+
(None, None) => break true,
80+
(None, _) | (_, None) => break false,
81+
(Some(x), Some(y)) if x != y => break false,
82+
_ => {}
83+
}
84+
}
85+
}
86+
}
87+
88+
impl<'a> From<&'a [u8]> for Filename<'a> {
89+
#[inline]
90+
fn from(x: &'a [u8]) -> Self {
91+
// null bytes are the only illegal bytes in a filename
92+
assert!(!x.contains(&0x00));
93+
Self::One(x)
94+
}
95+
}
96+
97+
impl<'a> From<&'a str> for Filename<'a> {
98+
#[inline]
99+
fn from(x: &'a str) -> Self {
100+
// null bytes are the only illegal bytes in a str filename
101+
assert!(!x.contains('\0'));
102+
Self::One(x.as_bytes())
103+
}
104+
}
105+
106+
impl<'a> Iterator for Filename<'a> {
107+
type Item = &'a [u8];
108+
109+
fn next(&mut self) -> Option<&'a [u8]> {
110+
match self {
111+
Self::One([]) => None,
112+
Self::One(x) => Some(match split_once(x, SEP) {
113+
None => {
114+
let ret = *x;
115+
*x = &[];
116+
ret
117+
}
118+
Some((fi, rest)) => {
119+
*x = rest;
120+
fi
121+
}
122+
}),
123+
Self::Two(x, y) => Some(match split_once(x, SEP) {
124+
None => {
125+
let ret = *x;
126+
*self = Self::One(y);
127+
ret
128+
}
129+
Some((fi, rest)) => {
130+
*x = rest;
131+
fi
132+
}
133+
}),
134+
}
135+
}
136+
}
137+
138+
#[cfg(test)]
139+
mod tests {
140+
use super::*;
141+
142+
fn assert_iteq<'a, I: Iterator<Item = &'a [u8]>>(mut it: I, eqto: &[&str]) {
143+
for i in eqto {
144+
assert_eq!(it.next(), Some(i.as_bytes()), "divergence @ {}", i);
145+
}
146+
assert_eq!(it.next(), None);
147+
}
148+
149+
#[test]
150+
fn test_filename_iter_one() {
151+
let it = Filename::One(b"aleph/beta/omicron");
152+
assert_iteq(it, &["aleph", "beta", "omicron"]);
153+
}
154+
155+
#[test]
156+
fn test_filename_as_truncated_one() {
157+
let it = Filename::One(b"aleph/beta/omicron").as_truncated(2);
158+
assert_iteq(it, &["aleph", "beta"]);
159+
}
160+
161+
#[test]
162+
fn test_filename_iter_two() {
163+
let it = Filename::Two(b"aleph/beta", b"omicron/depth");
164+
assert_iteq(it, &["aleph", "beta", "omicron", "depth"]);
165+
}
166+
167+
#[test]
168+
fn test_filename_as_truncated_two() {
169+
let mfn = Filename::Two(b"aleph/beta", b"omicron/depth");
170+
assert_iteq(mfn.as_truncated(2), &["aleph", "beta"]);
171+
assert_iteq(mfn.as_truncated(3), &["aleph", "beta", "omicron"]);
172+
assert_iteq(mfn.as_truncated(4), &["aleph", "beta", "omicron", "depth"]);
173+
assert_iteq(mfn.as_truncated(5), &["aleph", "beta", "omicron", "depth"]);
174+
}
175+
176+
#[test]
177+
fn test_filename_non_ascii() {
178+
let mfn = Filename::Two("αleph/βetα".as_bytes(), "ο/δth".as_bytes());
179+
assert_iteq(mfn, &["αleph", "βetα", "ο", "δth"]);
180+
assert_iteq(mfn.as_truncated(2), &["αleph", "βetα"]);
181+
assert_iteq(mfn.as_truncated(3), &["αleph", "βetα", "ο"]);
182+
assert_iteq(mfn.as_truncated(4), &["αleph", "βetα", "ο", "δth"]);
183+
assert_iteq(mfn.as_truncated(5), &["αleph", "βetα", "ο", "δth"]);
184+
}
185+
}

src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,28 @@
88
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
99
#![warn(missing_docs)]
1010

11+
#[cfg(feature = "alloc")]
12+
extern crate alloc;
13+
1114
pub mod boot_info;
1215

16+
#[cfg(feature = "config")]
17+
pub mod config;
18+
1319
#[cfg(feature = "loader")]
1420
pub mod elf;
1521

22+
mod filename;
23+
pub use filename::Filename;
24+
1625
#[cfg(feature = "kernel")]
1726
mod note;
1827

28+
pub mod tar_parser;
29+
30+
#[cfg(feature = "thin-tree")]
31+
pub mod thin_tree;
32+
1933
use core::error::Error;
2034
use core::fmt;
2135
use core::str::FromStr;
@@ -178,6 +192,20 @@ impl fmt::Display for UhyveIfVersion {
178192
}
179193
}
180194

195+
#[cfg(feature = "compression")]
196+
/// We assume that all images are gzip-compressed
197+
pub fn decompress_image(
198+
data: &[u8],
199+
) -> Result<tar_parser::Bytes, compression::prelude::CompressionError> {
200+
use compression::prelude::{DecodeExt as _, GZipDecoder};
201+
202+
data.iter()
203+
.copied()
204+
.decode(&mut GZipDecoder::new())
205+
.collect::<Result<_, _>>()
206+
.map(tar_parser::Bytes)
207+
}
208+
181209
#[cfg(test)]
182210
mod tests {
183211
use super::*;

0 commit comments

Comments
 (0)