Skip to content

Commit ae6ad06

Browse files
committed
feat(fs/mem): tarfs implementation
1 parent 3448d37 commit ae6ad06

File tree

4 files changed

+133
-10
lines changed

4 files changed

+133
-10
lines changed

Cargo.lock

Lines changed: 3 additions & 2 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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ free-list = "0.3"
118118
fuse-abi = { version = "0.2", features = ["linux"], optional = true }
119119
hashbrown = { version = "0.16", default-features = false }
120120
heapless = "0.9"
121-
hermit-entry = { version = "0.10.6", features = ["kernel"] }
121+
hermit-entry = { version = "0.10.6", git = "https://github.com/fogti/hermit-entry.git", branch = "image-reader", features = ["kernel"] }
122122
hermit-sync = "0.1"
123123
lock_api = "0.4"
124124
log = { version = "0.4", default-features = false }

src/fs/mem.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use core::mem::{MaybeUninit, offset_of};
2121
use align_address::Align;
2222
use async_lock::{Mutex, RwLock};
2323
use async_trait::async_trait;
24+
use hermit_entry::ThinTree;
2425

2526
use crate::errno::Errno;
2627
use crate::executor::block_on;
@@ -319,6 +320,10 @@ impl RomFile {
319320
pub fn new(data: &'static [u8], mode: AccessPermission) -> Self {
320321
let microseconds = arch::kernel::systemtime::now_micros();
321322
let t = timespec::from_usec(microseconds as i64);
323+
Self::new_with_timestamp(data, mode, t)
324+
}
325+
326+
pub fn new_with_timestamp(data: &'static [u8], mode: AccessPermission, t: timespec) -> Self {
322327
let attr = FileAttr {
323328
st_size: data.len().try_into().unwrap(),
324329
st_mode: mode | AccessPermission::S_IFREG,
@@ -477,6 +482,43 @@ pub(crate) struct MemDirectory {
477482
attr: FileAttr,
478483
}
479484

485+
fn thin_tree_to_vfs_node(
486+
thin_tree: ThinTree<'static>,
487+
t: timespec,
488+
) -> io::Result<Box<dyn VfsNode + Send + Sync>> {
489+
Ok(match thin_tree {
490+
ThinTree::File { content, metadata } => {
491+
let mut st_mode = AccessPermission::S_IRUSR;
492+
if metadata.is_exec {
493+
st_mode |= AccessPermission::S_IXUSR;
494+
}
495+
Box::new(RomFile::new_with_timestamp(content, st_mode, t))
496+
as Box<dyn VfsNode + Send + Sync>
497+
}
498+
ThinTree::Directory(d) => Box::new(MemDirectory {
499+
inner: Arc::new(RwLock::new(
500+
d.into_iter()
501+
.map(|(key, value)| {
502+
Ok((
503+
core::str::from_utf8(key)
504+
.expect("unable to interpret Hermit image entry filename as string")
505+
.to_owned(),
506+
thin_tree_to_vfs_node(value, t)?,
507+
))
508+
})
509+
.collect::<io::Result<_>>()?,
510+
)),
511+
attr: FileAttr {
512+
st_mode: AccessPermission::S_IRUSR | AccessPermission::S_IFDIR,
513+
st_atim: t,
514+
st_mtim: t,
515+
st_ctim: t,
516+
..Default::default()
517+
},
518+
}) as Box<dyn VfsNode + Send + Sync>,
519+
})
520+
}
521+
480522
impl MemDirectory {
481523
pub fn new(mode: AccessPermission) -> Self {
482524
let microseconds = arch::kernel::systemtime::now_micros();
@@ -494,6 +536,52 @@ impl MemDirectory {
494536
}
495537
}
496538

539+
/// Create a read-only memory tree from a tar image
540+
///
541+
/// This ignores top-level files in the image
542+
pub fn try_from_image(image: &'static hermit_entry::tar_parser::Bytes) -> io::Result<Self> {
543+
let microseconds = arch::kernel::systemtime::now_micros();
544+
let t = timespec::from_usec(microseconds as i64);
545+
546+
// This is not perfectly memory-efficient, but we expect this
547+
// to be invoked usually once per kernel boot, so this cost should
548+
// be acceptable, given that it reduces code duplication and
549+
// makes implementation way easier.
550+
let thin_tree = ThinTree::try_from_image(image).map_err(|e| {
551+
error!("unable to parse tar image: {e:?}");
552+
Errno::Inval
553+
})?;
554+
555+
let tree = match thin_tree {
556+
ThinTree::Directory(d) => d
557+
.into_iter()
558+
.map(|(key, value)| {
559+
Ok((
560+
core::str::from_utf8(key)
561+
.expect("unable to interpret Hermit image entry filename as string")
562+
.to_owned(),
563+
thin_tree_to_vfs_node(value, t)?,
564+
))
565+
})
566+
.collect::<io::Result<_>>()?,
567+
ThinTree::File { .. } => {
568+
error!("root of image isn't a directory");
569+
return Err(Errno::Inval);
570+
}
571+
};
572+
573+
Ok(Self {
574+
inner: Arc::new(RwLock::new(tree)),
575+
attr: FileAttr {
576+
st_mode: AccessPermission::S_IRUSR | AccessPermission::S_IFDIR,
577+
st_atim: t,
578+
st_mtim: t,
579+
st_ctim: t,
580+
..Default::default()
581+
},
582+
})
583+
}
584+
497585
async fn async_traverse_open(
498586
&self,
499587
components: &mut Vec<&str>,

src/fs/mod.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use hermit_sync::{InterruptSpinMutex, OnceCell};
1616
use mem::MemDirectory;
1717
use num_enum::{IntoPrimitive, TryFromPrimitive};
1818

19+
use crate::env::fdt;
1920
use crate::errno::Errno;
2021
use crate::executor::block_on;
2122
use crate::fd::{AccessPermission, ObjectInterface, OpenOption, insert_object, remove_object};
@@ -340,18 +341,51 @@ pub(crate) fn init() {
340341
const VERSION: &str = env!("CARGO_PKG_VERSION");
341342
const UTC_BUILT_TIME: &str = build_time::build_time_utc!();
342343

343-
FILESYSTEM.set(Filesystem::new()).unwrap();
344-
FILESYSTEM
345-
.get()
346-
.unwrap()
344+
let mut root_filesystem = Filesystem::new();
345+
346+
// Handle optional Hermit Image specified in FDT.
347+
let mut tar_image: Option<&'static [u8]> = None;
348+
if let Some(fdt) = fdt() {
349+
// per FDT specification, /chosen always exists.
350+
let chosen = fdt.find_node("/chosen").unwrap();
351+
if let Some(fdt::node::NodeProperty {
352+
value: image_reg, ..
353+
}) = chosen.property("image_reg")
354+
{
355+
let cell_sizes = fdt.root().cell_sizes();
356+
let split_point = cell_sizes.address_cells * 4;
357+
let end_point = split_point + cell_sizes.size_cells * 4;
358+
if image_reg.len() == end_point {
359+
let (addr, len) = image_reg.split_at(split_point);
360+
if addr.len() == core::mem::size_of::<*const u8>()
361+
&& len.len() == core::mem::size_of::<usize>()
362+
{
363+
// TODO: endian-ness?
364+
let addr = usize::from_ne_bytes(addr.try_into().unwrap());
365+
let len = usize::from_ne_bytes(len.try_into().unwrap());
366+
// technically, the following is UB, because the kerneö might be contained within...
367+
tar_image =
368+
Some(unsafe { core::slice::from_raw_parts(addr as *const u8, len) });
369+
}
370+
}
371+
}
372+
}
373+
if let Some(tar_image) = tar_image {
374+
root_filesystem.root =
375+
MemDirectory::try_from_image(hermit_entry::tar_parser::Bytes::new(tar_image))
376+
.expect("Unable to parse Hermit Image");
377+
}
378+
379+
root_filesystem
347380
.mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap())
348381
.expect("Unable to create /tmp");
349-
FILESYSTEM
350-
.get()
351-
.unwrap()
382+
383+
root_filesystem
352384
.mkdir("/proc", AccessPermission::from_bits(0o777).unwrap())
353385
.expect("Unable to create /proc");
354386

387+
FILESYSTEM.set(root_filesystem).unwrap();
388+
355389
if let Ok(mut file) = File::create("/proc/version") {
356390
if write!(file, "HermitOS version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() {
357391
error!("Unable to write in /proc/version");

0 commit comments

Comments
 (0)