diff --git a/Cargo.lock b/Cargo.lock index 514e6e035a..1fee6fff03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,6 +863,7 @@ dependencies = [ "hermit-entry", "hermit-macro", "hermit-sync", + "hex", "llvm-tools", "log", "mem-barrier", @@ -882,6 +883,7 @@ dependencies = [ "smoltcp", "take-static", "talc", + "tar-no-std", "thiserror", "time", "tock-registers 0.10.1", @@ -917,6 +919,12 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "home" version = "0.5.12" @@ -1954,6 +1962,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.11.1", + "log", + "num-traits", +] + [[package]] name = "thiserror" version = "2.0.18" diff --git a/Cargo.toml b/Cargo.toml index a9904c8533..854e2b07b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -326,10 +326,12 @@ simple-shell = { version = "0.0.1", optional = true } smallvec = { version = "1", features = ["const_new"] } take-static = "0.1" talc = { version = "5" } +tar-no-std = { version = "0.4", features = ["alloc"] } thiserror = { version = "2", default-features = false } time = { version = "0.3", default-features = false } volatile = "0.6" uhyve-interface = "0.1.4" +hex = { version = "0.4", default-features = false, features = ["alloc"] } [dependencies.smoltcp] version = "0.13" diff --git a/src/fs/mem.rs b/src/fs/mem.rs index 61002da066..d247c6711e 100644 --- a/src/fs/mem.rs +++ b/src/fs/mem.rs @@ -122,15 +122,6 @@ pub(crate) struct RamFileInner { pub attr: FileAttr, } -impl RamFileInner { - pub fn new(attr: FileAttr) -> Self { - Self { - data: Vec::new(), - attr, - } - } -} - pub struct RamFileInterface { /// Position within the file pos: Mutex, @@ -354,6 +345,10 @@ impl VfsNode for RamFile { impl RamFile { pub fn new(mode: AccessPermission) -> Self { + Self::new_with_data(Vec::new(), mode) + } + + fn new_with_data(data: Vec, mode: AccessPermission) -> Self { let microseconds = arch::kernel::systemtime::now_micros(); let t = timespec::from_usec(microseconds as i64); let attr = FileAttr { @@ -365,7 +360,7 @@ impl RamFile { }; Self { - data: Arc::new(RwLock::new(RamFileInner::new(attr))), + data: Arc::new(RwLock::new(RamFileInner { data, attr })), } } } @@ -464,6 +459,67 @@ impl MemDirectory { } } + pub fn try_from_image(image: &'static [u8]) -> io::Result { + let this = Self::new(AccessPermission::S_IRUSR); + + for i in image.chunks(1024) { + debug!("[DUMP] {}", hex::encode(i)); + } + + let taref = tar_no_std::TarArchiveRef::new(image).map_err(|e| { + error!("[Hermit image] Tar file has invalid format: {e:?}"); + Errno::Inval + })?; + + for i in taref.entries() { + let filename = i.filename(); + let filename = filename.as_str().map_err(|e| { + error!( + "[Hermit image] Tar entry has not supported filename (non UTF-8): {filename:?}; {e}", + ); + Errno::Inval + })?; + if filename.is_empty() { + continue; + } + debug!("[Hermit image] Processing tar entry: {}", filename); + + let mode = i.posix_header().mode.to_flags().map_err(|e| { + error!( + "[Hermit image] Tar entry {filename:?} has invalid mode: {:?}; {e}", + i.posix_header().mode, + ); + Errno::Inval + })?; + let mode = AccessPermission::from_bits(mode.bits() as u32).ok_or_else(|| { + error!("[Hermit image] Tar entry {filename:?} has invalid mode: {mode:?}"); + Errno::Inval + })?; + + for (i, _) in filename.match_indices("/") { + let part = &filename[..i]; + if this.traverse_lstat(part).is_err() { + this.traverse_mkdir( + part, + AccessPermission::S_IRUSR + | AccessPermission::S_IWUSR + | AccessPermission::S_IRGRP, + ) + .inspect_err(|e| { + error!("[Hermit image] Unable to mkdir {part:?}: {e}"); + })?; + } + } + + this.traverse_create_file(filename, i.data(), mode) + .inspect_err(|e| { + error!("[Hermit image] Unable to write entry {filename:?}: {e}"); + })?; + } + + Ok(this) + } + async fn async_traverse_open( &self, path: &str, @@ -693,11 +749,16 @@ impl VfsNode for MemDirectory { return directory.traverse_create_file(rest, data, mode); } - let file = RomFile::new(data, mode); - self.inner - .write() - .await - .insert(component.to_owned(), Box::new(file)); + let file: Box = if mode.contains(AccessPermission::S_IWUSR) + || mode.contains(AccessPermission::S_IWGRP) + || mode.contains(AccessPermission::S_IWOTH) + { + Box::new(RamFile::new_with_data(data.to_vec(), mode)) + } else { + Box::new(RomFile::new(data, mode)) + }; + + self.inner.write().await.insert(component.to_owned(), file); Ok(()) }, None, diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 95600d1166..877cf09870 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -304,7 +304,13 @@ pub(crate) fn init() { const VERSION: &str = env!("CARGO_PKG_VERSION"); const UTC_BUILT_TIME: &str = build_time::build_time_utc!(); - let root_filesystem = Filesystem::new(); + let mut root_filesystem = Filesystem::new(); + + // Handle optional Hermit Image specified in FDT. + if let Some(tar_image) = crate::mm::hermit_tar_image() { + root_filesystem.root = + MemDirectory::try_from_image(tar_image).expect("Unable to parse Hermit Image"); + } root_filesystem .mkdir("/tmp", AccessPermission::from_bits(0o777).unwrap()) diff --git a/src/mm/mod.rs b/src/mm/mod.rs index 1aa9f6a694..168d8b5c78 100644 --- a/src/mm/mod.rs +++ b/src/mm/mod.rs @@ -90,6 +90,52 @@ pub(crate) fn kernel_end_address() -> VirtAddr { KERNEL_ADDR_RANGE.end } +/// Physical and virtual address range of the Hermit image, in case it is present +/// (indicated via FDT). +static HERMIT_IMAGE_START_AND_LEN: Lazy> = Lazy::new(|| { + let fdt = env::fdt()?; + + // per FDT specification, /chosen always exists + let chosen = fdt.find_node("/chosen").unwrap(); + + let 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 { + return None; + } + + let (addr, len) = image_reg.split_at(split_point); + + if addr.len() == size_of::<*const u8>() && len.len() == size_of::() { + let addr = usize::from_be_bytes(addr.try_into().unwrap()); + let len = usize::from_be_bytes(len.try_into().unwrap()); + info!("Hermit image at {addr:x} with length {len:x}"); + Some(( + VirtAddr::from_ptr(core::ptr::with_exposed_provenance::(addr)), + len, + )) + } else { + error!( + "Hermit image supplied with invalid address range (#addr = {}, #len = {})", + addr.len(), + len.len(), + ); + None + } +}); + +pub(crate) fn hermit_tar_image() -> Option<&'static [u8]> { + // technically, the following is UB, because the kernel might be contained within... + HERMIT_IMAGE_START_AND_LEN + .map(|(addr, len)| unsafe { core::slice::from_raw_parts(addr.as_ptr(), len) }) +} + #[cfg(target_os = "none")] pub(crate) fn init() { use crate::arch::mm::paging; @@ -100,13 +146,24 @@ pub(crate) fn init() { arch::mm::init(); } + Lazy::force(&HERMIT_IMAGE_START_AND_LEN); + let total_mem = physicalmem::total_memory_size(); let kernel_addr_range = KERNEL_ADDR_RANGE.clone(); + let protect_addr_max = if let Some(hisnl) = HERMIT_IMAGE_START_AND_LEN.clone() { + core::cmp::max( + kernel_addr_range.end, + (hisnl.0 + hisnl.1).align_up(LargePageSize::SIZE), + ) + } else { + kernel_addr_range.end + }; info!("Total memory size: {} MiB", total_mem >> 20); info!( "Kernel region: {:p}..{:p}", kernel_addr_range.start, kernel_addr_range.end ); + info!("Maximum protected address: {:p}", protect_addr_max); // we reserve physical memory for the required page tables // In worst case, we use page size of BasePageSize::SIZE @@ -126,7 +183,7 @@ pub(crate) fn init() { // On UEFI, the given memory is guaranteed free memory and the kernel is located before the given memory reserved_space } else { - (kernel_addr_range.end.as_u64() - env::get_ram_address().as_u64() + reserved_space as u64) + (protect_addr_max.as_u64() - env::get_ram_address().as_u64() + reserved_space as u64) as usize }; info!("Minimum memory size: {} MiB", min_mem >> 20);