Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ free-list = "0.3"
fuse-abi = { version = "0.2", features = ["linux"], optional = true }
hashbrown = { version = "0.16", default-features = false }
heapless = "0.9"
hermit-entry = { version = "0.10.6", features = ["kernel"] }
hermit-entry = { version = "0.10.6", git = "https://github.com/fogti/hermit-entry.git", branch = "image-reader", features = ["kernel"] }
hermit-sync = "0.1"
lock_api = "0.4"
log = { version = "0.4", default-features = false }
Expand Down
102 changes: 93 additions & 9 deletions src/fs/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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;
Expand Down Expand Up @@ -319,6 +320,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,
Expand Down Expand Up @@ -389,19 +394,16 @@ impl RamFile {
}
}

type MemDirectoryMap = BTreeMap<String, Box<dyn VfsNode + core::marker::Send + core::marker::Sync>>;

pub struct MemDirectoryInterface {
/// Directory entries
inner:
Arc<RwLock<BTreeMap<String, Box<dyn VfsNode + core::marker::Send + core::marker::Sync>>>>,
inner: Arc<RwLock<MemDirectoryMap>>,
read_idx: Mutex<usize>,
}

impl MemDirectoryInterface {
pub fn new(
inner: Arc<
RwLock<BTreeMap<String, Box<dyn VfsNode + core::marker::Send + core::marker::Sync>>>,
>,
) -> Self {
pub fn new(inner: Arc<RwLock<MemDirectoryMap>>) -> Self {
Self {
inner,
read_idx: Mutex::new(0),
Expand Down Expand Up @@ -472,11 +474,47 @@ impl ObjectInterface for MemDirectoryInterface {

#[derive(Debug)]
pub(crate) struct MemDirectory {
inner:
Arc<RwLock<BTreeMap<String, Box<dyn VfsNode + core::marker::Send + core::marker::Sync>>>>,
inner: Arc<RwLock<MemDirectoryMap>>,
attr: FileAttr,
}

fn thin_tree_to_vfs_node(
thin_tree: ThinTree<'static>,
t: timespec,
) -> io::Result<Box<dyn VfsNode + Send + Sync>> {
Ok(match thin_tree {
ThinTree::File { content, metadata } => {
let mut st_mode = AccessPermission::S_IRUSR;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: what file permissions should we actually set?

if metadata.is_exec {
st_mode |= AccessPermission::S_IXUSR;
}
Box::new(RomFile::new_with_timestamp(content, st_mode, t))
as Box<dyn VfsNode + Send + Sync>
}
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::<io::Result<_>>()?,
)),
attr: FileAttr {
st_mode: AccessPermission::S_IRUSR | AccessPermission::S_IFDIR,
st_atim: t,
st_mtim: t,
st_ctim: t,
..Default::default()
},
}) as Box<dyn VfsNode + Send + Sync>,
})
}

impl MemDirectory {
pub fn new(mode: AccessPermission) -> Self {
let microseconds = arch::kernel::systemtime::now_micros();
Expand All @@ -494,6 +532,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<Self> {
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::<io::Result<_>>()?,
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>,
Expand Down
48 changes: 41 additions & 7 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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::<usize>()
{
// 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 kerneö 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");
Expand Down