diff --git a/Cargo.lock b/Cargo.lock index f09a7bedb1..c48f93eece 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -860,6 +860,7 @@ dependencies = [ "smoltcp", "take-static", "talc", + "tar-no-std", "thiserror", "time", "tock-registers 0.10.1", @@ -1915,6 +1916,17 @@ dependencies = [ "xattr", ] +[[package]] +name = "tar-no-std" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715f9a4586706a61c571cb5ee1c3ac2bbb2cf63e15bce772307b95befef5f5ee" +dependencies = [ + "bitflags 2.10.0", + "log", + "num-traits", +] + [[package]] name = "thiserror" version = "2.0.18" diff --git a/Cargo.toml b/Cargo.toml index 933c9c1097..b0edb55f7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,6 +142,7 @@ time = { version = "0.3", default-features = false } volatile = "0.6" zerocopy = { version = "0.8", default-features = false } uhyve-interface = "0.1.4" +tar-no-std = "0.4.2" [dependencies.smoltcp] version = "0.12" diff --git a/src/fs/mem.rs b/src/fs/mem.rs index 16305cfa64..555ec1ae3e 100644 --- a/src/fs/mem.rs +++ b/src/fs/mem.rs @@ -12,6 +12,7 @@ use core::mem::{MaybeUninit, offset_of}; use align_address::Align; use async_lock::{Mutex, RwLock}; use async_trait::async_trait; +use hermit_entry::ThinTree; use crate::errno::Errno; use crate::executor::block_on; @@ -302,6 +303,10 @@ impl RomFile { pub fn new(data: &'static [u8], mode: AccessPermission) -> Self { let microseconds = arch::kernel::systemtime::now_micros(); let t = timespec::from_usec(microseconds as i64); + Self::new_with_timestamp(data, mode, t) + } + + pub fn new_with_timestamp(data: &'static [u8], mode: AccessPermission, t: timespec) -> Self { let attr = FileAttr { st_size: data.len().try_into().unwrap(), st_mode: mode | AccessPermission::S_IFREG, @@ -372,19 +377,16 @@ impl RamFile { } } +type MemDirectoryMap = BTreeMap>; + pub struct MemDirectoryInterface { /// Directory entries - inner: - Arc>>>, + inner: Arc>, read_idx: Mutex, } impl MemDirectoryInterface { - pub fn new( - inner: Arc< - RwLock>>, - >, - ) -> Self { + pub fn new(inner: Arc>) -> Self { Self { inner, read_idx: Mutex::new(0), @@ -455,11 +457,47 @@ impl ObjectInterface for MemDirectoryInterface { #[derive(Debug)] pub(crate) struct MemDirectory { - inner: - Arc>>>, + inner: Arc>, attr: FileAttr, } +fn thin_tree_to_vfs_node( + thin_tree: ThinTree<'static>, + t: timespec, +) -> io::Result> { + Ok(match thin_tree { + ThinTree::File { content, metadata } => { + let mut st_mode = AccessPermission::S_IRUSR; + if metadata.is_exec { + st_mode |= AccessPermission::S_IXUSR; + } + Box::new(RomFile::new_with_timestamp(content, st_mode, t)) + as Box + } + ThinTree::Directory(d) => Box::new(MemDirectory { + inner: Arc::new(RwLock::new( + d.into_iter() + .map(|(key, value)| { + Ok(( + core::str::from_utf8(key) + .expect("unable to interpret Hermit image entry filename as string") + .to_owned(), + thin_tree_to_vfs_node(value, t)?, + )) + }) + .collect::>()?, + )), + attr: FileAttr { + st_mode: AccessPermission::S_IRUSR | AccessPermission::S_IFDIR, + st_atim: t, + st_mtim: t, + st_ctim: t, + ..Default::default() + }, + }) as Box, + }) +} + impl MemDirectory { pub fn new(mode: AccessPermission) -> Self { let microseconds = arch::kernel::systemtime::now_micros(); @@ -477,6 +515,52 @@ impl MemDirectory { } } + /// Create a read-only memory tree from a tar image + /// + /// This ignores top-level files in the image + pub fn try_from_image(image: &'static hermit_entry::tar_parser::Bytes) -> io::Result { + let microseconds = arch::kernel::systemtime::now_micros(); + let t = timespec::from_usec(microseconds as i64); + + // This is not perfectly memory-efficient, but we expect this + // to be invoked usually once per kernel boot, so this cost should + // be acceptable, given that it reduces code duplication and + // makes implementation way easier. + let thin_tree = ThinTree::try_from_image(image).map_err(|e| { + error!("unable to parse tar image: {e:?}"); + Errno::Inval + })?; + + let tree = match thin_tree { + ThinTree::Directory(d) => d + .into_iter() + .map(|(key, value)| { + Ok(( + core::str::from_utf8(key) + .expect("unable to interpret Hermit image entry filename as string") + .to_owned(), + thin_tree_to_vfs_node(value, t)?, + )) + }) + .collect::>()?, + ThinTree::File { .. } => { + error!("root of image isn't a directory"); + return Err(Errno::Inval); + } + }; + + Ok(Self { + inner: Arc::new(RwLock::new(tree)), + attr: FileAttr { + st_mode: AccessPermission::S_IRUSR | AccessPermission::S_IFDIR, + st_atim: t, + st_mtim: t, + st_ctim: t, + ..Default::default() + }, + }) + } + async fn async_traverse_open( &self, components: &mut Vec<&str>, diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 4e56b3f3b3..83d4d36f30 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -16,6 +16,7 @@ use hermit_sync::{InterruptSpinMutex, OnceCell}; use mem::MemDirectory; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use crate::env::fdt; use crate::errno::Errno; use crate::executor::block_on; use crate::fd::{AccessPermission, ObjectInterface, OpenOption, insert_object, remove_object}; @@ -340,18 +341,51 @@ pub(crate) fn init() { const VERSION: &str = env!("CARGO_PKG_VERSION"); const UTC_BUILT_TIME: &str = build_time::build_time_utc!(); - FILESYSTEM.set(Filesystem::new()).unwrap(); - FILESYSTEM - .get() - .unwrap() + let mut root_filesystem = Filesystem::new(); + + // Handle optional Hermit Image specified in FDT. + let mut tar_image: Option<&'static [u8]> = None; + if let Some(fdt) = fdt() { + // per FDT specification, /chosen always exists. + let chosen = fdt.find_node("/chosen").unwrap(); + if let Some(fdt::node::NodeProperty { + value: image_reg, .. + }) = chosen.property("image_reg") + { + let cell_sizes = fdt.root().cell_sizes(); + let split_point = cell_sizes.address_cells * 4; + let end_point = split_point + cell_sizes.size_cells * 4; + if image_reg.len() == end_point { + let (addr, len) = image_reg.split_at(split_point); + if addr.len() == core::mem::size_of::<*const u8>() + && len.len() == core::mem::size_of::() + { + // TODO: endian-ness? + let addr = usize::from_ne_bytes(addr.try_into().unwrap()); + let len = usize::from_ne_bytes(len.try_into().unwrap()); + // technically, the following is UB, because the kernel might be contained within... + tar_image = + Some(unsafe { core::slice::from_raw_parts(addr as *const u8, len) }); + } + } + } + } + if let Some(tar_image) = tar_image { + root_filesystem.root = + MemDirectory::try_from_image(hermit_entry::tar_parser::Bytes::new(tar_image)) + .expect("Unable to parse Hermit Image"); + } + + root_filesystem .mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap()) .expect("Unable to create /tmp"); - FILESYSTEM - .get() - .unwrap() + + root_filesystem .mkdir("/proc", AccessPermission::from_bits(0o777).unwrap()) .expect("Unable to create /proc"); + FILESYSTEM.set(root_filesystem).unwrap(); + if let Ok(mut file) = File::create("/proc/version") { if write!(file, "HermitOS version {VERSION} # UTC {UTC_BUILT_TIME}").is_err() { error!("Unable to write in /proc/version");