fs: implement in-memory writable filesystem
This commit is contained in:
parent
69a413f6bb
commit
64233db3df
@ -8,6 +8,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
|
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
|
||||||
vfs = { path = "lib/vfs" }
|
vfs = { path = "lib/vfs" }
|
||||||
|
memfs = { path = "lib/memfs" }
|
||||||
|
|
||||||
aarch64-cpu = "9.3.1"
|
aarch64-cpu = "9.3.1"
|
||||||
atomic_enum = "0.2.0"
|
atomic_enum = "0.2.0"
|
||||||
|
15
lib/memfs/Cargo.toml
Normal file
15
lib/memfs/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "memfs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
|
||||||
|
vfs = { path = "../vfs" }
|
||||||
|
static_assertions = "1.1.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
test-io = []
|
260
lib/memfs/src/block.rs
Normal file
260
lib/memfs/src/block.rs
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
//! Block management interfaces and structures
|
||||||
|
use core::{
|
||||||
|
marker::PhantomData,
|
||||||
|
mem::{size_of, MaybeUninit},
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
ptr::NonNull,
|
||||||
|
};
|
||||||
|
|
||||||
|
use yggdrasil_abi::error::Error;
|
||||||
|
|
||||||
|
/// Number of bytes in a block
|
||||||
|
pub const SIZE: usize = 4096;
|
||||||
|
/// Maximum number of indirection pointers a block can hold
|
||||||
|
pub const ENTRY_COUNT: usize = SIZE / size_of::<usize>();
|
||||||
|
|
||||||
|
/// Interface for a block allocator
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This trait is unsafe to implement because it has to provide and accept raw data pointers of
|
||||||
|
/// exactly [SIZE].
|
||||||
|
pub unsafe trait BlockAllocator: 'static {
|
||||||
|
/// Allocates a contiguous block of size [SIZE]
|
||||||
|
fn alloc() -> Result<NonNull<u8>, Error>;
|
||||||
|
|
||||||
|
/// Dealocates a block.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Unsafe: accepts arbitrary data pointers.
|
||||||
|
unsafe fn dealloc(block: NonNull<u8>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
struct BlockRaw<'a, A: BlockAllocator> {
|
||||||
|
inner: Option<&'a mut [u8; SIZE]>,
|
||||||
|
_pd: PhantomData<A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block containing file data
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct BlockData<'a, A: BlockAllocator> {
|
||||||
|
inner: BlockRaw<'a, A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block containing indirection pointers to other blocks
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct BlockIndirect<'a, A: BlockAllocator> {
|
||||||
|
inner: BlockRaw<'a, A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: BlockAllocator> BlockRaw<'a, A> {
|
||||||
|
const fn null() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: None,
|
||||||
|
_pd: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new() -> Result<Self, Error> {
|
||||||
|
let ptr = A::alloc()?;
|
||||||
|
unsafe { Ok(Self::from_raw(ptr)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_raw(ptr: NonNull<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Some(&mut *(ptr.as_ptr() as *mut _)),
|
||||||
|
_pd: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn as_uninit_indirect_mut(
|
||||||
|
&mut self,
|
||||||
|
) -> &'a mut [MaybeUninit<BlockData<'a, A>>; ENTRY_COUNT] {
|
||||||
|
unsafe { &mut *(self.inner.as_ref().unwrap().as_ptr() as *mut _) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_null(&self) -> bool {
|
||||||
|
self.inner.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: BlockAllocator> Drop for BlockRaw<'_, A> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(inner) = self.inner.take() {
|
||||||
|
unsafe {
|
||||||
|
A::dealloc(NonNull::new_unchecked(inner as *mut _));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data block
|
||||||
|
impl<'a, A: BlockAllocator> BlockData<'a, A> {
|
||||||
|
/// Dummy entry representing a missing block
|
||||||
|
pub const fn null() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: BlockRaw::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates a new block for data
|
||||||
|
pub fn new() -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: BlockRaw::new()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces self with a null block and drops any data that might've been allocated
|
||||||
|
pub fn set_null(&mut self) {
|
||||||
|
self.inner = BlockRaw::null();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the block this structure refers to has not yet been allocated
|
||||||
|
#[inline]
|
||||||
|
pub fn is_null(&self) -> bool {
|
||||||
|
self.inner.is_null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: BlockAllocator> Deref for BlockData<'_, A> {
|
||||||
|
type Target = [u8; SIZE];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.inner.inner.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: BlockAllocator> DerefMut for BlockData<'_, A> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.inner.inner.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indirect block
|
||||||
|
impl<'a, A: BlockAllocator> BlockIndirect<'a, A> {
|
||||||
|
/// Dummy entry representing a missing block
|
||||||
|
pub const fn null() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: BlockRaw::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocates a new indirection block
|
||||||
|
pub fn new() -> Result<Self, Error> {
|
||||||
|
let mut inner = BlockRaw::new()?;
|
||||||
|
for item in unsafe { inner.as_uninit_indirect_mut() } {
|
||||||
|
item.write(BlockData::null());
|
||||||
|
}
|
||||||
|
Ok(Self { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the block this structure refers to has not yet been allocated
|
||||||
|
#[inline]
|
||||||
|
pub fn is_null(&self) -> bool {
|
||||||
|
self.inner.is_null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: BlockAllocator> Deref for BlockIndirect<'a, A> {
|
||||||
|
type Target = [BlockData<'a, A>; ENTRY_COUNT];
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*(self.inner.inner.as_ref().unwrap().as_ptr() as *const _) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: BlockAllocator> DerefMut for BlockIndirect<'a, A> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { &mut *(self.inner.inner.as_mut().unwrap().as_mut_ptr() as *mut _) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: BlockAllocator> Drop for BlockIndirect<'a, A> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in self.iter_mut() {
|
||||||
|
item.set_null();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use core::sync::atomic::Ordering;
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
use crate::block::{BlockData, BlockIndirect};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_indirect_allocation() {
|
||||||
|
test_allocator_with_counter!(A_COUNTER, A);
|
||||||
|
|
||||||
|
const N: usize = 7;
|
||||||
|
const M: usize = 13;
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), 0);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut indirect = Vec::new();
|
||||||
|
|
||||||
|
// Allocate indirect blocks
|
||||||
|
{
|
||||||
|
for _ in 0..N {
|
||||||
|
indirect.push(BlockIndirect::<A>::new().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), N);
|
||||||
|
|
||||||
|
// Allocate L1 indirection blocks
|
||||||
|
{
|
||||||
|
for l1_block in indirect.iter_mut() {
|
||||||
|
for i in 0..M {
|
||||||
|
let l0_block = BlockData::new().unwrap();
|
||||||
|
|
||||||
|
l1_block[i] = l0_block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// N * M data blocks and N indirection blocks
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), N * M + N);
|
||||||
|
|
||||||
|
// Drop 1 indirect block for test
|
||||||
|
indirect.pop();
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), (N - 1) * M + (N - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_allocation() {
|
||||||
|
test_allocator_with_counter!(A_COUNTER, A);
|
||||||
|
|
||||||
|
const N: usize = 13;
|
||||||
|
{
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), 0);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut s = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..N {
|
||||||
|
let mut block = BlockData::<A>::new().unwrap();
|
||||||
|
block.fill(1);
|
||||||
|
s.push(block);
|
||||||
|
}
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), N);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
459
lib/memfs/src/bvec.rs
Normal file
459
lib/memfs/src/bvec.rs
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
//! Block vector management structures
|
||||||
|
use core::{
|
||||||
|
cmp::Ordering,
|
||||||
|
mem::MaybeUninit,
|
||||||
|
ops::{Index, IndexMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
use yggdrasil_abi::error::Error;
|
||||||
|
|
||||||
|
use crate::block::{self, BlockAllocator, BlockData, BlockIndirect};
|
||||||
|
|
||||||
|
// 16.125M total
|
||||||
|
const L0_BLOCKS: usize = 32; // 128K in L0
|
||||||
|
const L1_BLOCKS: usize = 8; // 16M in L1
|
||||||
|
|
||||||
|
/// Block vector for efficient in-memory files
|
||||||
|
pub struct BVec<'a, A: BlockAllocator> {
|
||||||
|
capacity: usize,
|
||||||
|
size: usize,
|
||||||
|
l0: [BlockData<'a, A>; L0_BLOCKS],
|
||||||
|
l1: [BlockIndirect<'a, A>; L1_BLOCKS],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: BlockAllocator> BVec<'a, A> {
|
||||||
|
/// Creates an empty block vector.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// The function is guaranteed to make no allocations before the vector is actually written to.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut l0 = MaybeUninit::uninit_array();
|
||||||
|
let mut l1 = MaybeUninit::uninit_array();
|
||||||
|
|
||||||
|
for it in l0.iter_mut() {
|
||||||
|
it.write(BlockData::null());
|
||||||
|
}
|
||||||
|
|
||||||
|
for it in l1.iter_mut() {
|
||||||
|
it.write(BlockIndirect::null());
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
capacity: 0,
|
||||||
|
size: 0,
|
||||||
|
l0: unsafe { MaybeUninit::array_assume_init(l0) },
|
||||||
|
l1: unsafe { MaybeUninit::array_assume_init(l1) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the size of the data inside this vector
|
||||||
|
#[inline]
|
||||||
|
pub const fn size(&self) -> usize {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grow_l1(&mut self, old_l1_cap: usize, new_l1_cap: usize) -> Result<(), Error> {
|
||||||
|
for i in old_l1_cap..new_l1_cap {
|
||||||
|
assert!(self.l1[i].is_null());
|
||||||
|
self.l1[i] = BlockIndirect::new()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shrink_l1(&mut self, old_l1_cap: usize, new_l1_cap: usize) {
|
||||||
|
debug_assert!(new_l1_cap <= old_l1_cap);
|
||||||
|
for i in new_l1_cap..old_l1_cap {
|
||||||
|
assert!(!self.l1[i].is_null());
|
||||||
|
self.l1[i] = BlockIndirect::null();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn caps(cap: usize) -> (usize, usize) {
|
||||||
|
let l0_cap = core::cmp::min(cap, L0_BLOCKS);
|
||||||
|
let l1_cap = if cap > L0_BLOCKS {
|
||||||
|
core::cmp::min(
|
||||||
|
(cap - L0_BLOCKS + block::ENTRY_COUNT - 1) / block::ENTRY_COUNT,
|
||||||
|
L1_BLOCKS,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
(l0_cap, l1_cap)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resizes the vector to hold exactly `new_capacity` data blocks
|
||||||
|
pub fn resize(&mut self, new_capacity: usize) -> Result<(), Error> {
|
||||||
|
// TODO handle L2 capacity
|
||||||
|
match new_capacity.cmp(&self.capacity) {
|
||||||
|
Ordering::Less => {
|
||||||
|
let (_, new_l1_cap) = Self::caps(new_capacity);
|
||||||
|
let (_, old_l1_cap) = Self::caps(self.capacity);
|
||||||
|
|
||||||
|
// Shrink data blocks
|
||||||
|
for index in new_capacity..self.capacity {
|
||||||
|
let block = &mut self[index];
|
||||||
|
assert!(!block.is_null());
|
||||||
|
block.set_null();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink L1 blocks
|
||||||
|
self.shrink_l1(old_l1_cap, new_l1_cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ordering::Greater => {
|
||||||
|
let (_, new_l1_cap) = Self::caps(new_capacity);
|
||||||
|
let (_, old_l1_cap) = Self::caps(self.capacity);
|
||||||
|
|
||||||
|
// Allocate L1 indirection blocks
|
||||||
|
assert!(new_l1_cap >= old_l1_cap);
|
||||||
|
if new_l1_cap > old_l1_cap {
|
||||||
|
self.grow_l1(old_l1_cap, new_l1_cap)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow data blocks
|
||||||
|
for index in self.capacity..new_capacity {
|
||||||
|
let block = unsafe { self.index_unchecked_mut(index) };
|
||||||
|
assert!(block.is_null());
|
||||||
|
*block = BlockData::new()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ordering::Equal => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.capacity = new_capacity;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_write_capacity(&mut self, pos: usize, need_to_write: usize) -> Result<(), Error> {
|
||||||
|
let current_capacity = self.capacity * block::SIZE;
|
||||||
|
let need_capacity =
|
||||||
|
(core::cmp::max(pos + need_to_write, self.size) + block::SIZE - 1) / block::SIZE;
|
||||||
|
|
||||||
|
if need_capacity > current_capacity {
|
||||||
|
self.resize(need_capacity)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes data to the vector, growing it if needed
|
||||||
|
pub fn write(&mut self, pos: u64, data: &[u8]) -> Result<usize, Error> {
|
||||||
|
let mut pos = pos as usize;
|
||||||
|
// if pos > self.size {
|
||||||
|
// return Err(Error::InvalidFile);
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut rem = data.len();
|
||||||
|
let mut doff = 0usize;
|
||||||
|
|
||||||
|
self.ensure_write_capacity(pos, rem)?;
|
||||||
|
|
||||||
|
if pos + rem > self.size {
|
||||||
|
self.size = pos + rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
while rem > 0 {
|
||||||
|
let index = pos / block::SIZE;
|
||||||
|
let offset = pos % block::SIZE;
|
||||||
|
let count = core::cmp::min(rem, block::SIZE - offset);
|
||||||
|
|
||||||
|
let block = &mut self[index];
|
||||||
|
|
||||||
|
let dst = &mut block[offset..offset + count];
|
||||||
|
let src = &data[doff..doff + count];
|
||||||
|
dst.copy_from_slice(src);
|
||||||
|
|
||||||
|
doff += count;
|
||||||
|
pos += count;
|
||||||
|
rem -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(doff)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads data from the vector
|
||||||
|
pub fn read(&self, pos: u64, data: &mut [u8]) -> Result<usize, Error> {
|
||||||
|
let mut pos = pos as usize;
|
||||||
|
if pos > self.size {
|
||||||
|
return Err(Error::InvalidFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rem = core::cmp::min(self.size - pos, data.len());
|
||||||
|
let mut doff = 0usize;
|
||||||
|
|
||||||
|
while rem > 0 {
|
||||||
|
let index = pos / block::SIZE;
|
||||||
|
let offset = pos % block::SIZE;
|
||||||
|
let count = core::cmp::min(block::SIZE - offset, rem);
|
||||||
|
|
||||||
|
let block = &self[index];
|
||||||
|
|
||||||
|
let src = &block[offset..offset + count];
|
||||||
|
let dst = &mut data[doff..doff + count];
|
||||||
|
|
||||||
|
dst.copy_from_slice(src);
|
||||||
|
|
||||||
|
doff += count;
|
||||||
|
pos += count;
|
||||||
|
rem -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(doff)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn index_unchecked(&self, mut index: usize) -> &BlockData<'a, A> {
|
||||||
|
if index < L0_BLOCKS {
|
||||||
|
return &self.l0[index];
|
||||||
|
}
|
||||||
|
index -= L0_BLOCKS;
|
||||||
|
if index < L1_BLOCKS * block::ENTRY_COUNT {
|
||||||
|
let l1i = index / block::ENTRY_COUNT;
|
||||||
|
let l0i = index % block::ENTRY_COUNT;
|
||||||
|
|
||||||
|
let l1r = &self.l1[l1i];
|
||||||
|
assert!(!l1r.is_null());
|
||||||
|
|
||||||
|
return &l1r[l0i];
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn index_unchecked_mut(&mut self, mut index: usize) -> &mut BlockData<'a, A> {
|
||||||
|
if index < L0_BLOCKS {
|
||||||
|
return &mut self.l0[index];
|
||||||
|
}
|
||||||
|
index -= L0_BLOCKS;
|
||||||
|
if index < L1_BLOCKS * block::ENTRY_COUNT {
|
||||||
|
let l1i = index / block::ENTRY_COUNT;
|
||||||
|
let l0i = index % block::ENTRY_COUNT;
|
||||||
|
|
||||||
|
let l1r = &mut self.l1[l1i];
|
||||||
|
assert!(!l1r.is_null());
|
||||||
|
|
||||||
|
return &mut l1r[l0i];
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: BlockAllocator> Index<usize> for BVec<'a, A> {
|
||||||
|
type Output = BlockData<'a, A>;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
if index > self.capacity {
|
||||||
|
panic!(
|
||||||
|
"Block index out of bounds: capacity={}, index={}",
|
||||||
|
self.capacity, index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { self.index_unchecked(index) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: BlockAllocator> IndexMut<usize> for BVec<'a, A> {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
if index > self.capacity {
|
||||||
|
panic!(
|
||||||
|
"Block index out of bounds: capacity={}, index={}",
|
||||||
|
self.capacity, index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { self.index_unchecked_mut(index) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: BlockAllocator> TryFrom<&[u8]> for BVec<'a, A> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
let mut res = Self::new();
|
||||||
|
res.write(0, value)?;
|
||||||
|
assert_eq!(res.size(), value.len());
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod bvec_allocation {
|
||||||
|
use core::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
block,
|
||||||
|
bvec::{BVec, L0_BLOCKS, L1_BLOCKS},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bvec_grow_shrink() {
|
||||||
|
test_allocator_with_counter!(A_COUNTER, A);
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), 0);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut bvec = BVec::<A>::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
A_COUNTER.load(Ordering::Acquire),
|
||||||
|
0,
|
||||||
|
"BVec should not allocate on creation"
|
||||||
|
);
|
||||||
|
|
||||||
|
const N: usize = 123;
|
||||||
|
bvec.resize(N).unwrap();
|
||||||
|
|
||||||
|
// N data blocks (12 in L0 + 111 in L1)
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), N + 1);
|
||||||
|
|
||||||
|
// Test the index interface
|
||||||
|
for i in 0..N {
|
||||||
|
assert!(!bvec[i].is_null(), "Index {} must be allocated", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the data structure
|
||||||
|
for i in 0..L0_BLOCKS {
|
||||||
|
assert!(!bvec.l0[i].is_null());
|
||||||
|
}
|
||||||
|
assert!(!bvec.l1[0].is_null());
|
||||||
|
for i in L0_BLOCKS..N {
|
||||||
|
let l1i = (i - L0_BLOCKS) / block::ENTRY_COUNT;
|
||||||
|
let l0i = (i - L0_BLOCKS) % block::ENTRY_COUNT;
|
||||||
|
|
||||||
|
let l1r = &bvec.l1[l1i];
|
||||||
|
assert!(!l1r.is_null());
|
||||||
|
assert!(!l1r[l0i].is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 1..L1_BLOCKS {
|
||||||
|
assert!(bvec.l1[i].is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink to 100 blocks, test if L1 is still allocated
|
||||||
|
const M: usize = 100;
|
||||||
|
bvec.resize(M).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), M + 1);
|
||||||
|
|
||||||
|
// Test the index interface
|
||||||
|
for i in 0..M {
|
||||||
|
assert!(!bvec[i].is_null(), "Index {} must be allocated", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the data structure
|
||||||
|
for i in 0..L0_BLOCKS {
|
||||||
|
assert!(!bvec.l0[i].is_null());
|
||||||
|
}
|
||||||
|
assert!(!bvec.l1[0].is_null());
|
||||||
|
for i in L0_BLOCKS..M {
|
||||||
|
let l1i = (i - L0_BLOCKS) / block::ENTRY_COUNT;
|
||||||
|
let l0i = (i - L0_BLOCKS) % block::ENTRY_COUNT;
|
||||||
|
|
||||||
|
let l1r = &bvec.l1[l1i];
|
||||||
|
assert!(!l1r.is_null());
|
||||||
|
assert!(!l1r[l0i].is_null());
|
||||||
|
}
|
||||||
|
for i in M..N {
|
||||||
|
let l1i = (i - L0_BLOCKS) / block::ENTRY_COUNT;
|
||||||
|
let l0i = (i - L0_BLOCKS) % block::ENTRY_COUNT;
|
||||||
|
|
||||||
|
let l1r = &bvec.l1[l1i];
|
||||||
|
assert!(!l1r.is_null());
|
||||||
|
assert!(l1r[l0i].is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 1..L1_BLOCKS {
|
||||||
|
assert!(bvec.l1[i].is_null());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink to 13 blocks, test if L1 got deallocated
|
||||||
|
const O: usize = 13;
|
||||||
|
bvec.resize(O).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), O);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(A_COUNTER.load(Ordering::Acquire), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "test-io"))]
|
||||||
|
mod bvec_io {
|
||||||
|
use crate::{block, bvec::L0_BLOCKS};
|
||||||
|
|
||||||
|
use super::BVec;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bvec_write() {
|
||||||
|
test_allocator_with_counter!(A_COUNTER, A);
|
||||||
|
|
||||||
|
{
|
||||||
|
let data = [1, 2, 3, 4, 5];
|
||||||
|
let mut bvec = BVec::<A>::new();
|
||||||
|
|
||||||
|
// Write at 0
|
||||||
|
assert_eq!(bvec.write(0, &data).unwrap(), data.len());
|
||||||
|
assert_eq!(bvec.capacity, 1);
|
||||||
|
assert_eq!(bvec.size(), data.len());
|
||||||
|
|
||||||
|
assert_eq!(&bvec[0][..bvec.size()], &data[..]);
|
||||||
|
|
||||||
|
// Write at 3
|
||||||
|
assert_eq!(bvec.write(3, &data).unwrap(), data.len());
|
||||||
|
assert_eq!(bvec.capacity, 1);
|
||||||
|
assert_eq!(bvec.size(), 3 + data.len());
|
||||||
|
|
||||||
|
assert_eq!(&bvec[0][..bvec.size()], &[1, 2, 3, 1, 2, 3, 4, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let data = [5, 4, 3, 2, 1];
|
||||||
|
let mut bvec = BVec::<A>::new();
|
||||||
|
|
||||||
|
// Write at the end of L0-region
|
||||||
|
assert_eq!(
|
||||||
|
bvec.write((L0_BLOCKS * block::SIZE) as u64, &data).unwrap(),
|
||||||
|
data.len()
|
||||||
|
);
|
||||||
|
// L0_BLOCKS + 1 L1 data block
|
||||||
|
assert_eq!(bvec.capacity, L0_BLOCKS + 1);
|
||||||
|
assert_eq!(bvec.size(), L0_BLOCKS * block::SIZE + data.len());
|
||||||
|
|
||||||
|
assert_eq!(&bvec[L0_BLOCKS][..data.len()], &data[..]);
|
||||||
|
|
||||||
|
// Write at zero
|
||||||
|
assert_eq!(bvec.write(0, &data).unwrap(), data.len());
|
||||||
|
assert_eq!(bvec.capacity, L0_BLOCKS + 1);
|
||||||
|
assert_eq!(bvec.size(), L0_BLOCKS * block::SIZE + data.len());
|
||||||
|
|
||||||
|
assert_eq!(&bvec[0][..data.len()], &data[..]);
|
||||||
|
|
||||||
|
// Test write crossing L0 block boundary
|
||||||
|
assert_eq!(
|
||||||
|
bvec.write((block::SIZE - 3) as u64, &data).unwrap(),
|
||||||
|
data.len()
|
||||||
|
);
|
||||||
|
assert_eq!(bvec.capacity, L0_BLOCKS + 1);
|
||||||
|
assert_eq!(bvec.size(), L0_BLOCKS * block::SIZE + data.len());
|
||||||
|
|
||||||
|
assert_eq!(&bvec[0][block::SIZE - 3..], &[5, 4, 3]);
|
||||||
|
assert_eq!(&bvec[1][..2], &[2, 1]);
|
||||||
|
|
||||||
|
// Test write crossing L0-L1 boundary
|
||||||
|
assert_eq!(
|
||||||
|
bvec.write((L0_BLOCKS * block::SIZE) as u64 - 2, &data)
|
||||||
|
.unwrap(),
|
||||||
|
data.len()
|
||||||
|
);
|
||||||
|
assert_eq!(bvec.capacity, L0_BLOCKS + 1);
|
||||||
|
assert_eq!(bvec.size(), L0_BLOCKS * block::SIZE + data.len());
|
||||||
|
|
||||||
|
assert_eq!(&bvec[L0_BLOCKS - 1][block::SIZE - 2..], &[5, 4]);
|
||||||
|
assert_eq!(&bvec[L0_BLOCKS][..data.len()], &[3, 2, 1, 2, 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
lib/memfs/src/dir.rs
Normal file
32
lib/memfs/src/dir.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
|
use vfs::{Vnode, VnodeImpl, VnodeKind, VnodeRef};
|
||||||
|
use yggdrasil_abi::error::Error;
|
||||||
|
|
||||||
|
use crate::{block::BlockAllocator, bvec::BVec, file::FileNode};
|
||||||
|
|
||||||
|
pub(crate) struct DirectoryNode<A: BlockAllocator> {
|
||||||
|
_pd: PhantomData<A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: BlockAllocator> VnodeImpl for DirectoryNode<A> {
|
||||||
|
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(Self { _pd: PhantomData })),
|
||||||
|
VnodeKind::Regular => child.set_data(Box::new(FileNode {
|
||||||
|
data: BVec::<A>::new(),
|
||||||
|
})),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
Ok(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: BlockAllocator> DirectoryNode<A> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { _pd: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
37
lib/memfs/src/file.rs
Normal file
37
lib/memfs/src/file.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use vfs::{VnodeImpl, VnodeRef};
|
||||||
|
use yggdrasil_abi::{
|
||||||
|
error::Error,
|
||||||
|
io::{FileMode, OpenOptions},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{block::BlockAllocator, bvec::BVec};
|
||||||
|
|
||||||
|
pub(crate) struct FileNode<A: BlockAllocator> {
|
||||||
|
pub(crate) data: BVec<'static, A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: BlockAllocator> VnodeImpl for FileNode<A> {
|
||||||
|
fn open(&mut self, _node: &VnodeRef, opts: OpenOptions, _mode: FileMode) -> Result<u64, Error> {
|
||||||
|
if opts.contains(OpenOptions::APPEND) {
|
||||||
|
Ok(self.data.size() as u64)
|
||||||
|
} else {
|
||||||
|
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> {
|
||||||
|
self.data.read(pos, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, _node: &VnodeRef, pos: u64, data: &[u8]) -> Result<usize, Error> {
|
||||||
|
self.data.write(pos, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&mut self, _node: &VnodeRef) -> Result<u64, Error> {
|
||||||
|
Ok(self.data.size() as u64)
|
||||||
|
}
|
||||||
|
}
|
287
lib/memfs/src/lib.rs
Normal file
287
lib/memfs/src/lib.rs
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
//! In-memory filesystem driver
|
||||||
|
#![no_std]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
#![allow(clippy::new_without_default)]
|
||||||
|
#![feature(
|
||||||
|
const_mut_refs,
|
||||||
|
maybe_uninit_uninit_array,
|
||||||
|
const_maybe_uninit_uninit_array,
|
||||||
|
maybe_uninit_array_assume_init
|
||||||
|
)]
|
||||||
|
|
||||||
|
use core::{
|
||||||
|
any::Any,
|
||||||
|
cell::{Ref, RefCell},
|
||||||
|
marker::PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloc::{boxed::Box, rc::Rc};
|
||||||
|
use block::BlockAllocator;
|
||||||
|
use vfs::{BlockDevice, Filesystem, Vnode, VnodeKind, VnodeRef};
|
||||||
|
use yggdrasil_abi::{error::Error, path};
|
||||||
|
|
||||||
|
use crate::{bvec::BVec, dir::DirectoryNode, file::FileNode, tar::TarIterator};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate std;
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
macro_rules! test_allocator_with_counter {
|
||||||
|
($counter:ident, $allocator:ident) => {
|
||||||
|
static $counter: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
|
||||||
|
|
||||||
|
struct $allocator;
|
||||||
|
|
||||||
|
unsafe impl $crate::block::BlockAllocator for $allocator {
|
||||||
|
fn alloc() -> Result<core::ptr::NonNull<u8>, yggdrasil_abi::error::Error> {
|
||||||
|
let b = std::boxed::Box::into_raw(std::boxed::Box::new([0; $crate::block::SIZE]));
|
||||||
|
$counter.fetch_add(1, core::sync::atomic::Ordering::Release);
|
||||||
|
Ok(unsafe { core::ptr::NonNull::new_unchecked(b as _) })
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn dealloc(block: core::ptr::NonNull<u8>) {
|
||||||
|
$counter.fetch_sub(1, core::sync::atomic::Ordering::Release);
|
||||||
|
drop(std::boxed::Box::from_raw(
|
||||||
|
block.as_ptr() as *mut [u8; $crate::block::SIZE]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod block;
|
||||||
|
pub mod bvec;
|
||||||
|
|
||||||
|
mod dir;
|
||||||
|
mod file;
|
||||||
|
mod tar;
|
||||||
|
|
||||||
|
/// In-memory read/write filesystem
|
||||||
|
pub struct MemoryFilesystem<A: BlockAllocator> {
|
||||||
|
root: RefCell<Option<VnodeRef>>,
|
||||||
|
_pd: PhantomData<A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: BlockAllocator> Filesystem for MemoryFilesystem<A> {
|
||||||
|
fn dev(self: Rc<Self>) -> Option<&'static dyn BlockDevice> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Option<Ref<dyn Any>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root(self: Rc<Self>) -> Result<VnodeRef, Error> {
|
||||||
|
Ok(self.root.borrow().clone().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: BlockAllocator> MemoryFilesystem<A> {
|
||||||
|
fn make_path(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
at: &VnodeRef,
|
||||||
|
path: &str,
|
||||||
|
kind: VnodeKind,
|
||||||
|
create: bool,
|
||||||
|
) -> Result<VnodeRef, Error> {
|
||||||
|
if path.is_empty() {
|
||||||
|
return Ok(at.clone());
|
||||||
|
}
|
||||||
|
let (element, rest) = path::split_left(path);
|
||||||
|
assert!(!element.is_empty());
|
||||||
|
|
||||||
|
let node = at.lookup(element);
|
||||||
|
let node = match node {
|
||||||
|
Some(node) => node,
|
||||||
|
None => {
|
||||||
|
if !create {
|
||||||
|
return Err(Error::DoesNotExist);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(DirectoryNode::<A>::new())),
|
||||||
|
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(DirectoryNode::<A>::new()));
|
||||||
|
|
||||||
|
// 1. Create paths in tar
|
||||||
|
for item in TarIterator::new(tar_data) {
|
||||||
|
let Ok((hdr, _)) = item else {
|
||||||
|
return Err(Error::InvalidArgument);
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = hdr.name.as_str()?.trim_matches('/');
|
||||||
|
let (dirname, filename) = 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)?;
|
||||||
|
|
||||||
|
let bvec = BVec::<A>::try_from(data)?;
|
||||||
|
assert_eq!(bvec.size(), data.len());
|
||||||
|
node.set_data(Box::new(FileNode { data: bvec }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(Self {
|
||||||
|
root: RefCell::new(None),
|
||||||
|
_pd: PhantomData,
|
||||||
|
});
|
||||||
|
let root = fs.from_slice_internal(tar_data)?;
|
||||||
|
fs.root.replace(Some(root));
|
||||||
|
|
||||||
|
Ok(fs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs an empty memory filesystem
|
||||||
|
pub fn empty() -> Rc<Self> {
|
||||||
|
let fs = Rc::new(Self {
|
||||||
|
root: RefCell::new(None),
|
||||||
|
_pd: PhantomData,
|
||||||
|
});
|
||||||
|
let root = Vnode::new("", VnodeKind::Directory);
|
||||||
|
root.set_data(Box::new(DirectoryNode::<A>::new()));
|
||||||
|
root.set_fs(fs.clone());
|
||||||
|
fs.root.replace(Some(root));
|
||||||
|
fs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
use vfs::{Filesystem, IoContext, Read, Seek, SeekFrom, VnodeKind, Write};
|
||||||
|
use yggdrasil_abi::io::{FileMode, OpenOptions};
|
||||||
|
|
||||||
|
use crate::MemoryFilesystem;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memfs_construction() {
|
||||||
|
fn check_file(ioctx: &IoContext, path: &str, expected_data: &str) {
|
||||||
|
let node = ioctx.find(None, path, false, false).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(node.kind(), VnodeKind::Regular);
|
||||||
|
assert_eq!(node.size().unwrap(), expected_data.len() as u64);
|
||||||
|
|
||||||
|
let file = node.open(OpenOptions::READ, FileMode::empty()).unwrap();
|
||||||
|
let mut buf = [0; 512];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
file.borrow_mut().read(&mut buf).unwrap(),
|
||||||
|
expected_data.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(&buf[..expected_data.len()], expected_data.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
static TEST_IMAGE: &[u8] = include_bytes!("../test/test_image.tar");
|
||||||
|
test_allocator_with_counter!(A_COUNTER, A);
|
||||||
|
|
||||||
|
let fs = MemoryFilesystem::<A>::from_slice(TEST_IMAGE).unwrap();
|
||||||
|
let root = fs.root().unwrap();
|
||||||
|
|
||||||
|
let ioctx = IoContext::new(root.clone());
|
||||||
|
|
||||||
|
assert!(Rc::ptr_eq(
|
||||||
|
&root,
|
||||||
|
&ioctx.find(None, "/", false, false).unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
|
check_file(&ioctx, "/test1.txt", include_str!("../test/test1.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memfs_create_and_write() {
|
||||||
|
test_allocator_with_counter!(A_COUNTER, A);
|
||||||
|
|
||||||
|
let fs = MemoryFilesystem::<A>::empty();
|
||||||
|
let root = fs.root().unwrap();
|
||||||
|
|
||||||
|
let ioctx = IoContext::new(root.clone());
|
||||||
|
|
||||||
|
// Create, write, seek and read file
|
||||||
|
{
|
||||||
|
// TODO CREATE option handling
|
||||||
|
root.create("test1.txt", VnodeKind::Regular).unwrap();
|
||||||
|
|
||||||
|
let file = ioctx
|
||||||
|
.open(
|
||||||
|
None,
|
||||||
|
"/test1.txt",
|
||||||
|
OpenOptions::WRITE | OpenOptions::READ,
|
||||||
|
FileMode::empty(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let write_data = [1, 2, 3, 4];
|
||||||
|
let mut read_data = [0; 512];
|
||||||
|
|
||||||
|
let mut file = file.borrow_mut();
|
||||||
|
assert_eq!(file.write(&write_data).unwrap(), write_data.len());
|
||||||
|
assert_eq!(file.seek(SeekFrom::Start(0)).unwrap(), 0);
|
||||||
|
assert_eq!(file.read(&mut read_data).unwrap(), write_data.len());
|
||||||
|
assert_eq!(&read_data[..write_data.len()], &write_data[..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a directory
|
||||||
|
{
|
||||||
|
// TODO read directory
|
||||||
|
root.create("dir1", VnodeKind::Directory).unwrap();
|
||||||
|
|
||||||
|
let dir1 = ioctx.find(None, "/dir1", false, false).unwrap();
|
||||||
|
let node = dir1.create("file1.txt", VnodeKind::Regular).unwrap();
|
||||||
|
assert!(Rc::ptr_eq(
|
||||||
|
&ioctx.find(None, "/dir1/file1.txt", false, false).unwrap(),
|
||||||
|
&node
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
lib/memfs/src/tar.rs
Normal file
136
lib/memfs/src/tar.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
use vfs::VnodeKind;
|
||||||
|
use yggdrasil_abi::error::Error;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct OctalField<const N: usize> {
|
||||||
|
data: [u8; N],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) struct TarString<const N: usize> {
|
||||||
|
data: [u8; N],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct TarIterator<'a> {
|
||||||
|
data: &'a [u8],
|
||||||
|
offset: usize,
|
||||||
|
zero_blocks: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
pub(crate) struct TarEntry {
|
||||||
|
pub name: TarString<100>,
|
||||||
|
_mode: OctalField<8>,
|
||||||
|
_uid: OctalField<8>,
|
||||||
|
_gid: OctalField<8>,
|
||||||
|
pub 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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
lib/memfs/test/dir1/test1.txt
Normal file
1
lib/memfs/test/dir1/test1.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is another test file
|
1
lib/memfs/test/test1.txt
Normal file
1
lib/memfs/test/test1.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is a test file
|
BIN
lib/memfs/test/test_image.tar
Normal file
BIN
lib/memfs/test/test_image.tar
Normal file
Binary file not shown.
@ -1,12 +1,21 @@
|
|||||||
//! Filesystem implementations
|
//! Filesystem implementations
|
||||||
|
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
|
||||||
|
use memfs::block::{self, BlockAllocator};
|
||||||
use vfs::VnodeRef;
|
use vfs::VnodeRef;
|
||||||
use yggdrasil_abi::{error::Error, io::MountOptions};
|
use yggdrasil_abi::{error::Error, io::MountOptions};
|
||||||
|
|
||||||
use crate::util::OneTimeInit;
|
use crate::{
|
||||||
|
mem::{
|
||||||
|
self,
|
||||||
|
phys::{self, PageUsage},
|
||||||
|
ConvertAddress,
|
||||||
|
},
|
||||||
|
util::OneTimeInit,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod devfs;
|
pub mod devfs;
|
||||||
pub mod tar;
|
|
||||||
|
|
||||||
/// Describes in-memory filesystem image used as initial root
|
/// Describes in-memory filesystem image used as initial root
|
||||||
pub struct Initrd {
|
pub struct Initrd {
|
||||||
@ -21,6 +30,24 @@ pub struct Initrd {
|
|||||||
/// Holds reference to the data of initrd as well as its page-aligned physical memory region
|
/// Holds reference to the data of initrd as well as its page-aligned physical memory region
|
||||||
pub static INITRD_DATA: OneTimeInit<Initrd> = OneTimeInit::new();
|
pub static INITRD_DATA: OneTimeInit<Initrd> = OneTimeInit::new();
|
||||||
|
|
||||||
|
/// Implementation of [memfs::block::BlockAllocator] for the kernel
|
||||||
|
pub struct FileBlockAllocator;
|
||||||
|
|
||||||
|
unsafe impl BlockAllocator for FileBlockAllocator {
|
||||||
|
fn alloc() -> Result<NonNull<u8>, Error> {
|
||||||
|
// TODO make this a static assertion
|
||||||
|
assert_eq!(block::SIZE, 4096);
|
||||||
|
let page = phys::alloc_page(PageUsage::Used)?;
|
||||||
|
Ok(unsafe { NonNull::new_unchecked(page.virtualize() as *mut _) })
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn dealloc(block: NonNull<u8>) {
|
||||||
|
let page = block.as_ptr() as usize;
|
||||||
|
assert!(page > mem::KERNEL_VIRT_OFFSET);
|
||||||
|
todo!("Release physical memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Constructs an instance of a filesystem for given set of [MountOptions]
|
/// Constructs an instance of a filesystem for given set of [MountOptions]
|
||||||
pub fn create_filesystem(options: &MountOptions) -> Result<VnodeRef, Error> {
|
pub fn create_filesystem(options: &MountOptions) -> Result<VnodeRef, Error> {
|
||||||
let fs_name = options.filesystem.unwrap();
|
let fs_name = options.filesystem.unwrap();
|
||||||
|
310
src/fs/tar.rs
310
src/fs/tar.rs
@ -1,310 +0,0 @@
|
|||||||
//! 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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,7 +19,8 @@ use abi::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
io::{FileMode, OpenOptions},
|
io::{FileMode, OpenOptions},
|
||||||
};
|
};
|
||||||
use fs::{tar::TarFilesystem, INITRD_DATA};
|
use fs::{FileBlockAllocator, INITRD_DATA};
|
||||||
|
use memfs::MemoryFilesystem;
|
||||||
use task::process::Process;
|
use task::process::Process;
|
||||||
use vfs::{Filesystem, IoContext, VnodeRef};
|
use vfs::{Filesystem, IoContext, VnodeRef};
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ pub mod util;
|
|||||||
|
|
||||||
fn setup_root() -> Result<VnodeRef, Error> {
|
fn setup_root() -> Result<VnodeRef, Error> {
|
||||||
let initrd_data = INITRD_DATA.get();
|
let initrd_data = INITRD_DATA.get();
|
||||||
let fs = TarFilesystem::from_slice(initrd_data.data)?;
|
let fs = MemoryFilesystem::<FileBlockAllocator>::from_slice(initrd_data.data).unwrap();
|
||||||
fs.root()
|
fs.root()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user