yggdrasil/src/fs/tar.rs

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)
}
}