Skip to content

Commit 5e2c878

Browse files
committed
feat(vm): Implement support for Hermit images
This is the Hermit image implementation variant which uses virtual (in-memory) files on the hypervisor level (in contrast to the kernel level).
1 parent e122757 commit 5e2c878

File tree

9 files changed

+195
-19
lines changed

9 files changed

+195
-19
lines changed

Cargo.lock

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ clap = { version = "4.5", features = ["derive", "env"] }
4141
clean-path = "0.2.1"
4242
core_affinity = "0.8"
4343
env_logger = "0.11"
44+
flate2 = "1.1"
4445
gdbstub = "0.7"
4546
gdbstub_arch = "0.3"
4647
hermit-abi = "0.5"

src/bin/uhyve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ impl<'de> serde::de::Deserialize<'de> for Affinity {
400400
#[derive(Debug, Default, Deserialize, Merge, Parser)]
401401
#[cfg_attr(test, derive(PartialEq))]
402402
struct GuestArgs {
403-
/// The kernel to execute
403+
/// The kernel or Hermit image to execute
404404
#[serde(skip)]
405405
#[merge(skip)]
406406
kernel: PathBuf,

src/isolation/fd.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ pub enum FdData {
4747
/// A host file descriptor
4848
Raw(RawFd),
4949

50-
#[allow(dead_code)]
5150
/// An in-memory slice.
5251
///
5352
/// SAFETY: It is not allowed for `data` to point into guest memory.

src/isolation/filemap/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ impl UhyveFileMap {
111111
}
112112

113113
/// Adds the contents of a decompressed hermit image to the file map.
114-
#[allow(unused)]
115114
pub fn add_hermit_image(&mut self, tar_bytes: &[u8]) -> Result<(), HermitImageError> {
116115
if tar_bytes.is_empty() {
117116
return Ok(());

src/isolation/filemap/tree.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ pub enum File {
1616
OnHost(PathBuf),
1717

1818
/// An in-memory file
19-
#[allow(unused)]
2019
Virtual(Arc<[u8]>),
2120
}
2221

src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ pub enum HypervisorError {
4949
#[error("Invalid kernel path ({0})")]
5050
InvalidKernelPath(PathBuf),
5151

52+
#[error("Path for {0} contained null bytes")]
53+
PathContainedNullBytes(&'static str),
54+
55+
#[error(transparent)]
56+
HermitImageError(#[from] crate::isolation::filemap::HermitImageError),
57+
58+
#[error("Unable to find Hermit image config in archive")]
59+
HermitImageConfigNotFound,
60+
61+
#[error("Unable to parse Hermit image config: {0}")]
62+
HermitImageConfigParseError(#[from] toml::de::Error),
63+
64+
#[error("Insufficient guest memory size: got = {got}, wanted = {wanted}")]
65+
InsufficientGuestMemorySize {
66+
got: byte_unit::Byte,
67+
wanted: byte_unit::Byte,
68+
},
69+
70+
#[error("Insufficient guest CPU count: got = {got}, wanted = {wanted}")]
71+
InsufficientGuestCPUs { got: u32, wanted: u32 },
72+
5273
#[error("Kernel Loading Error: {0}")]
5374
LoadedKernelError(#[from] vm::LoadKernelError),
5475
}

src/params.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ impl FromStr for CpuCount {
150150
}
151151

152152
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
153-
pub struct GuestMemorySize(Byte);
153+
pub struct GuestMemorySize(pub(crate) Byte);
154154

155155
impl GuestMemorySize {
156156
const fn minimum() -> Byte {
@@ -257,13 +257,13 @@ impl Default for EnvVars {
257257
Self::Set(HashMap::new())
258258
}
259259
}
260-
impl<S: AsRef<str> + std::fmt::Debug + PartialEq<S> + From<&'static str>> TryFrom<&[S]>
260+
impl<S: AsRef<str> + core::fmt::Debug + PartialEq + PartialEq<&'static str>> TryFrom<&[S]>
261261
for EnvVars
262262
{
263263
type Error = &'static str;
264264

265265
fn try_from(v: &[S]) -> Result<Self, Self::Error> {
266-
if v.contains(&S::from("host")) {
266+
if v.iter().any(|i| *i == "host") {
267267
if v.len() != 1 {
268268
warn!(
269269
"Specifying -e host discards all other explicitly specified environment vars"

src/vm.rs

Lines changed: 127 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use std::{
2-
env, fmt, fs, io,
2+
env,
3+
ffi::CString,
4+
fmt, fs, io,
5+
mem::{drop, take},
36
num::NonZero,
47
os::unix::prelude::JoinHandleExt,
58
path::PathBuf,
@@ -10,8 +13,9 @@ use std::{
1013

1114
use core_affinity::CoreId;
1215
use hermit_entry::{
13-
HermitVersion, UhyveIfVersion,
16+
Format, HermitVersion, UhyveIfVersion,
1417
boot_info::{BootInfo, HardwareInfo, LoadInfo, PlatformInfo, RawBootInfo, SerialPortBase},
18+
config, detect_format,
1519
elf::{KernelObject, LoadedKernel, ParseKernelError},
1620
};
1721
use internal::VirtualizationBackendInternal;
@@ -24,7 +28,7 @@ use crate::{
2428
consts::*,
2529
fdt::Fdt,
2630
generate_address,
27-
isolation::filemap::UhyveFileMap,
31+
isolation::filemap::{UhyveFileMap, UhyveTreeFile},
2832
mem::MmapMemory,
2933
os::KickSignal,
3034
params::{EnvVars, Params},
@@ -137,11 +141,127 @@ pub struct UhyveVm<VirtBackend: VirtualizationBackend> {
137141
pub(crate) kernel_info: Arc<KernelInfo>,
138142
}
139143
impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
140-
pub fn new(kernel_path: PathBuf, params: Params) -> HypervisorResult<UhyveVm<VirtBackend>> {
144+
pub fn new(kernel_path: PathBuf, mut params: Params) -> HypervisorResult<UhyveVm<VirtBackend>> {
141145
let memory_size = params.memory_size.get();
142146

143-
let elf = fs::read(&kernel_path)
147+
let kernel_data = fs::read(&kernel_path)
144148
.map_err(|_e| HypervisorError::InvalidKernelPath(kernel_path.clone()))?;
149+
150+
// TODO: file_mapping not in kernel_info
151+
let mut file_mapping = UhyveFileMap::new(
152+
&params.file_mapping,
153+
params.tempdir.clone(),
154+
#[cfg(target_os = "linux")]
155+
params.io_mode,
156+
);
157+
158+
// `kernel_data` might be an Hermit image
159+
let elf = match detect_format(&kernel_data[..]) {
160+
None => return Err(HypervisorError::InvalidKernelPath(kernel_path.clone())),
161+
Some(Format::Elf) => kernel_data,
162+
Some(Format::Gzip) => {
163+
{
164+
use io::Read;
165+
166+
// decompress image
167+
let mut buf_decompressed = Vec::new();
168+
flate2::bufread::GzDecoder::new(&kernel_data[..])
169+
.read_to_end(&mut buf_decompressed)?;
170+
drop(kernel_data);
171+
172+
// insert Hermit image tree into file map
173+
file_mapping.add_hermit_image(&buf_decompressed[..])?;
174+
}
175+
176+
// TODO: make `hermit_entry::config::DEFAULT_CONFIG_NAME` public and use that here instead.
177+
let config_data = if let Some(UhyveTreeFile::Virtual(data)) =
178+
file_mapping.get_host_path(&CString::new("/hermit.toml").unwrap())
179+
{
180+
data
181+
} else {
182+
return Err(HypervisorError::HermitImageConfigNotFound);
183+
};
184+
185+
let config: config::Config<'_> = toml::from_slice(&config_data[..])?;
186+
187+
// handle Hermit image configuration
188+
match config {
189+
config::Config::V1 {
190+
mut input,
191+
requirements,
192+
kernel,
193+
} => {
194+
// .input
195+
if params.kernel_args.is_empty() {
196+
params.kernel_args.append(
197+
&mut take(&mut input.kernel_args)
198+
.into_iter()
199+
.map(|i| i.into_owned())
200+
.collect(),
201+
);
202+
if !input.app_args.is_empty() {
203+
params.kernel_args.push("--".to_string());
204+
params.kernel_args.append(
205+
&mut take(&mut input.app_args)
206+
.into_iter()
207+
.map(|i| i.into_owned())
208+
.collect(),
209+
)
210+
}
211+
}
212+
debug!("Passing kernel arguments: {:?}", &params.kernel_args);
213+
214+
// don't pass privileged env-var commands through
215+
input.env_vars.retain(|i| i.contains('='));
216+
217+
if let EnvVars::Set(env) = &mut params.env {
218+
if let Ok(EnvVars::Set(prev_env_vars)) =
219+
EnvVars::try_from(&input.env_vars[..])
220+
{
221+
// env vars from params take precedence
222+
let new_env_vars = take(env);
223+
*env = prev_env_vars.into_iter().chain(new_env_vars).collect();
224+
} else {
225+
warn!("Unable to parse env vars from Hermit image configuration");
226+
}
227+
} else if input.env_vars.is_empty() {
228+
info!("Ignoring Hermit image env vars due to `-e host`");
229+
}
230+
231+
// .requirements
232+
233+
// TODO: what about default memory size?
234+
if let Some(required_memory_size) = requirements.memory
235+
&& params.memory_size.0 < required_memory_size
236+
{
237+
return Err(HypervisorError::InsufficientGuestMemorySize {
238+
got: params.memory_size.0,
239+
wanted: required_memory_size,
240+
});
241+
}
242+
243+
if params.cpu_count.get() < requirements.cpus {
244+
return Err(HypervisorError::InsufficientGuestCPUs {
245+
got: params.cpu_count.get(),
246+
wanted: requirements.cpus,
247+
});
248+
}
249+
250+
// .kernel
251+
if let Some(UhyveTreeFile::Virtual(data)) =
252+
file_mapping.get_host_path(&CString::new(kernel.into_owned()).map_err(
253+
|_| HypervisorError::PathContainedNullBytes("hermit image kernel"),
254+
)?) {
255+
data.to_vec()
256+
} else {
257+
error!("Unable to find kernel in Hermit image");
258+
return Err(HypervisorError::InvalidKernelPath(kernel_path.clone()));
259+
}
260+
}
261+
}
262+
}
263+
};
264+
145265
let object: KernelObject<'_> =
146266
KernelObject::parse(&elf).map_err(LoadKernelError::ParseKernelError)?;
147267

@@ -204,14 +324,9 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
204324
let mut mem = MmapMemory::new(memory_size, guest_address, false, false);
205325

206326
// TODO: file_mapping not in kernel_info
207-
let file_mapping = Mutex::new(UhyveFileMap::new(
208-
&params.file_mapping,
209-
params.tempdir.clone(),
210-
#[cfg(target_os = "linux")]
211-
params.io_mode,
212-
));
213-
let mut mounts: Vec<_> = file_mapping.lock().unwrap().get_all_guest_dirs().collect();
327+
let mut mounts: Vec<_> = file_mapping.get_all_guest_dirs().collect();
214328
mounts.dedup();
329+
let file_mapping = Mutex::new(file_mapping);
215330

216331
let serial = UhyveSerial::from_params(&params.output)?;
217332

0 commit comments

Comments
 (0)