311 lines
8.3 KiB
Rust
311 lines
8.3 KiB
Rust
//! In-memory filesystem implementation
|
|
use abi::{error::Error, io::FileMode};
|
|
use alloc::{boxed::Box, rc::Rc};
|
|
use vfs::{Filesystem, Vnode, VnodeImpl, VnodeKind, VnodeRef};
|
|
use yggdrasil_abi::io::OpenOptions;
|
|
|
|
use crate::util::OneTimeInit;
|
|
|
|
#[repr(C)]
|
|
struct OctalField<const N: usize> {
|
|
data: [u8; N],
|
|
}
|
|
|
|
#[repr(C)]
|
|
struct TarString<const N: usize> {
|
|
data: [u8; N],
|
|
}
|
|
|
|
struct TarIterator<'a> {
|
|
data: &'a [u8],
|
|
offset: usize,
|
|
zero_blocks: usize,
|
|
}
|
|
|
|
#[repr(packed)]
|
|
struct TarEntry {
|
|
name: TarString<100>,
|
|
_mode: OctalField<8>,
|
|
_uid: OctalField<8>,
|
|
_gid: OctalField<8>,
|
|
size: OctalField<12>,
|
|
_mtime: OctalField<12>,
|
|
_checksum: OctalField<8>,
|
|
type_: u8,
|
|
_link_name: TarString<100>,
|
|
_magic: [u8; 8],
|
|
_user: TarString<32>,
|
|
_group: TarString<32>,
|
|
_dev_major: OctalField<8>,
|
|
_dev_minor: OctalField<8>,
|
|
_prefix: TarString<155>,
|
|
__pad: [u8; 12],
|
|
}
|
|
|
|
impl<'a> TarIterator<'a> {
|
|
pub const fn new(data: &'a [u8]) -> Self {
|
|
Self {
|
|
data,
|
|
offset: 0,
|
|
zero_blocks: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for TarIterator<'a> {
|
|
type Item = Result<(&'a TarEntry, Option<&'a [u8]>), Error>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
loop {
|
|
if self.offset + 512 > self.data.len() {
|
|
break None;
|
|
}
|
|
|
|
let hdr_ptr = &self.data[self.offset..];
|
|
let hdr = unsafe { &*(hdr_ptr.as_ptr() as *const TarEntry) };
|
|
|
|
if hdr.is_empty() {
|
|
if self.zero_blocks == 1 {
|
|
self.offset = self.data.len();
|
|
return None;
|
|
}
|
|
self.zero_blocks += 1;
|
|
continue;
|
|
}
|
|
|
|
let (data, size_aligned) = match hdr.type_ {
|
|
0 | b'0' => {
|
|
let size = usize::from(&hdr.size);
|
|
|
|
if self.offset + 512 + size > self.data.len() {
|
|
return Some(Err(Error::InvalidArgument));
|
|
}
|
|
|
|
let data = &self.data[self.offset + 512..self.offset + 512 + size];
|
|
let size_aligned = (size + 511) & !511;
|
|
|
|
(Some(data), size_aligned)
|
|
}
|
|
// Directory
|
|
b'5' => (None, 0),
|
|
_ => todo!("Unknown node kind: {}", hdr.type_),
|
|
};
|
|
self.offset += size_aligned + 512;
|
|
|
|
break Some(Ok((hdr, data)));
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<const N: usize> From<&OctalField<N>> for usize {
|
|
fn from(value: &OctalField<N>) -> Self {
|
|
let mut acc = 0;
|
|
for i in 0..N {
|
|
if value.data[i] == 0 {
|
|
break;
|
|
}
|
|
acc <<= 3;
|
|
acc |= (value.data[i] - b'0') as usize;
|
|
}
|
|
acc
|
|
}
|
|
}
|
|
|
|
impl<const N: usize> TarString<N> {
|
|
pub fn as_str(&self) -> Result<&str, Error> {
|
|
core::str::from_utf8(&self.data[..self.len()]).map_err(|_| Error::InvalidArgument)
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
for i in 0..N {
|
|
if self.data[i] == 0 {
|
|
return i;
|
|
}
|
|
}
|
|
N
|
|
}
|
|
}
|
|
|
|
impl TarEntry {
|
|
pub fn is_empty(&self) -> bool {
|
|
self.name.data[0] == 0
|
|
}
|
|
|
|
pub fn node_kind(&self) -> VnodeKind {
|
|
match self.type_ {
|
|
0 | b'0' => VnodeKind::Regular,
|
|
b'5' => VnodeKind::Directory,
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// tar-image based in-memory filesystem
|
|
pub struct TarFilesystem {
|
|
root: OneTimeInit<VnodeRef>,
|
|
}
|
|
|
|
impl Filesystem for TarFilesystem {
|
|
fn dev(self: Rc<Self>) -> Option<&'static dyn vfs::BlockDevice> {
|
|
todo!()
|
|
}
|
|
|
|
fn root(self: Rc<Self>) -> Result<VnodeRef, Error> {
|
|
self.root.try_get().cloned().ok_or(Error::DoesNotExist)
|
|
}
|
|
|
|
fn data(&self) -> Option<core::cell::Ref<dyn core::any::Any>> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
struct DirInode;
|
|
struct RegularInode {
|
|
data: &'static [u8],
|
|
}
|
|
|
|
impl VnodeImpl for DirInode {
|
|
fn create(&mut self, _at: &VnodeRef, name: &str, kind: VnodeKind) -> Result<VnodeRef, Error> {
|
|
let child = Vnode::new(name, kind);
|
|
match kind {
|
|
VnodeKind::Directory => child.set_data(Box::new(DirInode)),
|
|
VnodeKind::Regular => (),
|
|
_ => todo!(),
|
|
}
|
|
Ok(child)
|
|
}
|
|
}
|
|
|
|
impl VnodeImpl for RegularInode {
|
|
fn open(&mut self, _node: &VnodeRef, opts: OpenOptions, _mode: FileMode) -> Result<u64, Error> {
|
|
if opts.contains(OpenOptions::WRITE) {
|
|
panic!("TODO: tarfs write");
|
|
}
|
|
|
|
Ok(0)
|
|
}
|
|
|
|
fn close(&mut self, _node: &VnodeRef) -> Result<(), Error> {
|
|
Ok(())
|
|
}
|
|
|
|
fn read(&mut self, _node: &VnodeRef, pos: u64, data: &mut [u8]) -> Result<usize, Error> {
|
|
let pos = pos as usize;
|
|
if pos > self.data.len() {
|
|
return Err(Error::InvalidFile);
|
|
}
|
|
let rem = core::cmp::min(self.data.len() - pos, data.len());
|
|
data[..rem].copy_from_slice(&self.data[pos..pos + rem]);
|
|
Ok(rem)
|
|
}
|
|
|
|
fn size(&mut self, _node: &VnodeRef) -> Result<u64, Error> {
|
|
Ok(self.data.len() as u64)
|
|
}
|
|
}
|
|
|
|
impl TarFilesystem {
|
|
fn make_path(
|
|
self: &Rc<Self>,
|
|
at: &VnodeRef,
|
|
path: &str,
|
|
kind: VnodeKind,
|
|
create: bool,
|
|
) -> Result<VnodeRef, Error> {
|
|
debugln!("make_path {:?}", path);
|
|
if path.is_empty() {
|
|
return Ok(at.clone());
|
|
}
|
|
let (element, rest) = abi::path::split_left(path);
|
|
assert!(!element.is_empty());
|
|
|
|
let node = at.lookup(element);
|
|
let node = match node {
|
|
Some(node) => node,
|
|
None => {
|
|
if !create {
|
|
debugln!("path {:?} does not exist", path);
|
|
return Err(Error::DoesNotExist);
|
|
}
|
|
|
|
infoln!("Create {:?}", element);
|
|
let node = self.create_node_initial(element, kind);
|
|
at.add_child(node.clone());
|
|
|
|
node
|
|
}
|
|
};
|
|
|
|
if rest.is_empty() {
|
|
Ok(node)
|
|
} else {
|
|
assert!(node.is_directory());
|
|
self.make_path(&node, rest, kind, create)
|
|
}
|
|
}
|
|
|
|
fn create_node_initial(self: &Rc<Self>, name: &str, kind: VnodeKind) -> VnodeRef {
|
|
assert!(!name.is_empty());
|
|
assert!(!name.contains('/'));
|
|
|
|
let node = Vnode::new(name, kind);
|
|
node.set_fs(self.clone());
|
|
|
|
match kind {
|
|
VnodeKind::Directory => node.set_data(Box::new(DirInode)),
|
|
VnodeKind::Regular => {}
|
|
_ => todo!(),
|
|
}
|
|
|
|
node
|
|
}
|
|
|
|
fn from_slice_internal(self: &Rc<Self>, tar_data: &'static [u8]) -> Result<VnodeRef, Error> {
|
|
let root = Vnode::new("", VnodeKind::Directory);
|
|
root.set_fs(self.clone());
|
|
root.set_data(Box::new(DirInode));
|
|
|
|
// 1. Create paths in tar
|
|
for item in TarIterator::new(tar_data) {
|
|
let Ok((hdr, _)) = item else {
|
|
warnln!("Tar image is truncated");
|
|
return Err(Error::InvalidArgument);
|
|
};
|
|
|
|
let path = hdr.name.as_str()?.trim_matches('/');
|
|
infoln!("path = {:?}", path);
|
|
let (dirname, filename) = abi::path::split_right(path);
|
|
let parent = self.make_path(&root, dirname, VnodeKind::Directory, true)?;
|
|
let node = self.create_node_initial(filename, hdr.node_kind());
|
|
|
|
parent.add_child(node);
|
|
}
|
|
|
|
// 2. Associate files with their data
|
|
for item in TarIterator::new(tar_data) {
|
|
let Ok((hdr, data)) = item else {
|
|
panic!("Unreachable");
|
|
};
|
|
if hdr.node_kind() == VnodeKind::Regular {
|
|
let data = data.unwrap();
|
|
let path = hdr.name.as_str()?.trim_matches('/');
|
|
let node = self.make_path(&root, path, VnodeKind::Directory, false)?;
|
|
node.set_data(Box::new(RegularInode { data }));
|
|
}
|
|
}
|
|
|
|
Ok(root)
|
|
}
|
|
|
|
/// Constructs a filesystem tree from a tar image in memory
|
|
pub fn from_slice(tar_data: &'static [u8]) -> Result<Rc<Self>, Error> {
|
|
let fs = Rc::new(TarFilesystem {
|
|
root: OneTimeInit::new(),
|
|
});
|
|
let root = fs.from_slice_internal(tar_data)?;
|
|
fs.root.init(root);
|
|
|
|
Ok(fs)
|
|
}
|
|
}
|