feat(vfs): CoW for memfs, init start

This commit is contained in:
2021-10-26 00:59:43 +03:00
parent 6d8f0d01ef
commit c706428ed6
20 changed files with 291 additions and 71 deletions
Generated
+10
View File
@@ -108,6 +108,7 @@ dependencies = [
"cortex-a",
"error",
"fdt-rs",
"libcommon",
"memfs",
"tock-registers",
"vfs",
@@ -119,6 +120,13 @@ version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce"
[[package]]
name = "libcommon"
version = "0.1.0"
dependencies = [
"error",
]
[[package]]
name = "libusr"
version = "0.1.0"
@@ -128,6 +136,7 @@ name = "memfs"
version = "0.1.0"
dependencies = [
"error",
"libcommon",
"vfs",
]
@@ -259,6 +268,7 @@ version = "0.1.0"
dependencies = [
"error",
"hashbrown",
"libcommon",
]
[[package]]
+1
View File
@@ -11,6 +11,7 @@ edition = "2018"
members = [
"fs/vfs",
"fs/memfs",
"libcommon",
"kernel",
"libusr",
"init",
+6 -4
View File
@@ -55,13 +55,10 @@ endif
.PHONY: address error etc kernel src
all: kernel
all: kernel initrd
kernel:
cd kernel && cargo build $(CARGO_BUILD_OPTS)
cd init && cargo build --target=../etc/$(ARCH)-osdev5.json -Z build-std=core,alloc,compiler_builtins
echo "This is a test file" >$(O)/test.txt
cd $(O) && tar cf initrd.img test.txt
ifeq ($(ARCH),aarch64)
$(LLVM_BASE)/llvm-strip -o $(O)/kernel.strip $(O)/kernel
$(LLVM_BASE)/llvm-size $(O)/kernel.strip
@@ -80,6 +77,11 @@ ifeq ($(MACH),orangepi3)
$(O)/uImage
endif
initrd:
cd init && cargo build --target=../etc/$(ARCH)-osdev5.json -Z build-std=core,alloc,compiler_builtins
cp target/$(ARCH)-osdev5/debug/init $(O)
cd $(O) && tar cf initrd.img init
test:
cd fs/vfs && cargo test
cd fs/memfs && cargo test
+5
View File
@@ -8,3 +8,8 @@ edition = "2021"
[dependencies]
vfs = { path = "../vfs" }
error = { path = "../../error" }
libcommon = { path = "../../libcommon" }
[features]
cow = []
default = ["cow"]
+43
View File
@@ -12,6 +12,8 @@ pub struct Bvec<'a, A: BlockAllocator + Copy> {
l0: [MaybeUninit<BlockRef<'a, A>>; L0_BLOCKS],
l1: [MaybeUninit<BlockRef<'a, A>>; L1_BLOCKS],
l2: MaybeUninit<BlockRef<'a, A>>,
#[cfg(feature = "cow")]
cow_source: *const u8,
alloc: A,
}
impl<'a, A: BlockAllocator + Copy> Bvec<'a, A> {
@@ -23,6 +25,8 @@ impl<'a, A: BlockAllocator + Copy> Bvec<'a, A> {
l1: MaybeUninit::uninit_array(),
l2: MaybeUninit::uninit(),
alloc,
#[cfg(feature = "cow")]
cow_source: core::ptr::null_mut()
};
for it in res.l0.iter_mut() {
it.write(BlockRef::null());
@@ -33,7 +37,31 @@ impl<'a, A: BlockAllocator + Copy> Bvec<'a, A> {
res.l2.write(BlockRef::null());
res
}
#[cfg(feature = "cow")]
pub unsafe fn setup_cow(&mut self, src: *const u8, size: usize) {
self.cow_source = src;
self.size = size;
}
pub const fn size(&self) -> usize {
self.size
}
#[cfg(feature = "cow")]
pub fn drop_cow(&mut self) {
assert!(!self.cow_source.is_null());
let src_slice = unsafe { core::slice::from_raw_parts(self.cow_source, self.size) };
self.cow_source = core::ptr::null_mut();
self.resize((self.size + 4095) / 4096).unwrap();
self.write(0, src_slice).unwrap();
}
pub fn resize(&mut self, cap: usize) -> Result<(), Errno> {
#[cfg(feature = "cow")]
assert!(self.cow_source.is_null());
if cap <= self.capacity {
let mut curr = self.capacity;
while curr != cap {
@@ -126,6 +154,12 @@ impl<'a, A: BlockAllocator + Copy> Bvec<'a, A> {
if pos > self.size {
return Err(Errno::InvalidArgument);
}
#[cfg(feature = "cow")]
if !self.cow_source.is_null() {
self.drop_cow();
}
let mut rem = data.len();
let mut doff = 0usize;
if pos + rem > self.size {
@@ -150,7 +184,16 @@ impl<'a, A: BlockAllocator + Copy> Bvec<'a, A> {
if pos > self.size {
return Err(Errno::InvalidArgument);
}
let mut rem = min(self.size - pos, data.len());
#[cfg(feature = "cow")]
if !self.cow_source.is_null() {
let cow_data = unsafe { core::slice::from_raw_parts(self.cow_source, self.size) };
data[..rem].copy_from_slice(&cow_data[pos..pos + rem]);
return Ok(rem);
}
let mut doff = 0usize;
while rem > 0 {
let index = pos / block::SIZE;
+52 -9
View File
@@ -13,7 +13,9 @@ extern crate std;
use alloc::{boxed::Box, rc::Rc};
use core::cell::RefCell;
use core::ffi::c_void;
use error::Errno;
use libcommon::path_component_left;
use vfs::{node::VnodeData, Filesystem, Vnode, VnodeImpl, VnodeKind, VnodeRef};
pub mod block;
@@ -34,6 +36,12 @@ pub struct FileInode<'a, A: BlockAllocator + Copy + 'static> {
pub struct DirInode;
struct SetupFileParam {
data: &'static [u8]
}
const FILE_SETUP_COW: u64 = 0x1001;
impl<'a, A: BlockAllocator + Copy + 'static> VnodeImpl for FileInode<'a, A> {
fn create(&mut self, _parent: VnodeRef, _node: VnodeRef) -> Result<(), Errno> {
panic!()
@@ -51,17 +59,35 @@ impl<'a, A: BlockAllocator + Copy + 'static> VnodeImpl for FileInode<'a, A> {
Ok(())
}
fn read(&mut self, node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno> {
fn read(&mut self, _node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno> {
self.data.read(pos, data)
}
fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno> {
fn write(&mut self, _node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno> {
self.data.write(pos, data)
}
fn truncate(&mut self, _node: VnodeRef, size: usize) -> Result<(), Errno> {
self.data.resize((size + 4095) / 4096)
}
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
Ok(self.data.size())
}
fn ioctl(&mut self, node: VnodeRef, cmd: u64, value: *mut c_void) -> Result<isize, Errno> {
match cmd {
#[cfg(feature = "cow")]
FILE_SETUP_COW => {
let data = unsafe { (value as *mut SetupFileParam).as_ref().unwrap() };
unsafe {
self.data.setup_cow(data.data.as_ptr(), data.data.len());
}
Ok(0)
},
_ => Err(Errno::InvalidArgument)
}
}
}
impl VnodeImpl for DirInode {
@@ -92,6 +118,14 @@ impl VnodeImpl for DirInode {
fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> {
todo!()
}
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
todo!()
}
fn ioctl(&mut self, node: VnodeRef, cmd: u64, value: *mut c_void) -> Result<isize, Errno> {
todo!()
}
}
impl<A: BlockAllocator + Copy + 'static> Filesystem for Ramfs<A> {
@@ -100,7 +134,7 @@ impl<A: BlockAllocator + Copy + 'static> Filesystem for Ramfs<A> {
}
fn create_node(self: Rc<Self>, name: &str, kind: VnodeKind) -> Result<VnodeRef, Errno> {
let mut node = Vnode::new(name, kind);
let mut node = Vnode::new(name, kind, Vnode::SEEKABLE);
let data: Box<dyn VnodeImpl> = match kind {
VnodeKind::Regular => Box::new(FileInode {
data: Bvec::new(self.alloc),
@@ -134,7 +168,7 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> {
kind: VnodeKind,
do_create: bool,
) -> Result<VnodeRef, Errno> {
let (element, rest) = vfs::util::path_component_left(path);
let (element, rest) = path_component_left(path);
assert!(!element.is_empty());
let node_kind = if rest.is_empty() {
@@ -177,7 +211,6 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> {
// 2. Setup data blocks
for block in TarIterator::new(base, base.add(size)) {
if block.is_file() {
let size = block.size();
let node = self.clone().make_path(
root.clone(),
block.path()?,
@@ -185,10 +218,20 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> {
false,
)?;
node.truncate(size).unwrap();
let res = node.write(0, block.data()).unwrap();
if res != size {
panic!("Expected to write {}B, got {}B", size, res);
#[cfg(feature = "cow")]
{
let mut param = SetupFileParam {
data: block.data()
};
node.ioctl(FILE_SETUP_COW, &mut param as *mut _ as *mut _)?;
}
#[cfg(not(feature = "cow"))]
{
let size = block.size();
node.truncate(size)?;
if node.write(0, block.data())? != size {
return Err(Errno::InvalidArgument);
}
}
}
}
+1
View File
@@ -7,4 +7,5 @@ edition = "2021"
[dependencies]
error = { path = "../../error" }
libcommon = { path = "../../libcommon" }
hashbrown = "0.11.*"
+35 -8
View File
@@ -1,7 +1,9 @@
use crate::VnodeRef;
use alloc::rc::Rc;
use core::cell::RefCell;
use core::cmp::min;
use error::Errno;
use libcommon::{Read, Seek, SeekDir, Write};
struct NormalFile {
vnode: VnodeRef,
@@ -16,14 +18,8 @@ pub struct File {
inner: FileInner,
}
impl File {
pub fn normal(vnode: VnodeRef, pos: usize) -> Self {
Self {
inner: FileInner::Normal(NormalFile { vnode, pos }),
}
}
pub fn read(&mut self, data: &mut [u8]) -> Result<usize, Errno> {
impl Read for File {
fn read(&mut self, data: &mut [u8]) -> Result<usize, Errno> {
match &mut self.inner {
FileInner::Normal(inner) => {
let count = inner.vnode.read(inner.pos, data)?;
@@ -35,6 +31,37 @@ impl File {
}
}
impl Seek for File {
fn seek(&mut self, off: isize, whence: SeekDir) -> Result<usize, Errno> {
match &mut self.inner {
FileInner::Normal(inner) => {
if !inner.vnode.is_seekable() {
return Err(Errno::InvalidArgument);
}
let size = inner.vnode.size()?;
let pos = match whence {
SeekDir::Set => min(off as usize, size),
_ => todo!(),
};
inner.pos = pos;
Ok(pos)
}
_ => unimplemented!(),
}
}
}
impl File {
pub fn normal(vnode: VnodeRef, pos: usize) -> Self {
Self {
inner: FileInner::Normal(NormalFile { vnode, pos }),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
+4 -3
View File
@@ -1,5 +1,6 @@
use crate::{util, FileMode, VnodeRef};
use crate::{FileMode, VnodeRef};
use error::Errno;
use libcommon::{path_component_left, path_component_right};
pub struct Ioctx {
root: VnodeRef,
@@ -19,7 +20,7 @@ impl Ioctx {
let mut rest = path;
loop {
(element, rest) = util::path_component_left(rest);
(element, rest) = path_component_left(rest);
if !at.is_directory() {
return Err(Errno::NotADirectory);
@@ -62,7 +63,7 @@ impl Ioctx {
}
pub fn mkdir(&self, at: Option<VnodeRef>, path: &str, mode: FileMode) -> Result<(), Errno> {
let (parent, name) = util::path_component_right(path);
let (parent, name) = path_component_right(path);
let parent_node = self.find(at, parent)?;
parent_node.mkdir(name, mode).map(|_| ())
-1
View File
@@ -13,4 +13,3 @@ pub mod ioctx;
pub use ioctx::Ioctx;
pub mod file;
pub use file::File;
pub mod util;
+47 -1
View File
@@ -1,6 +1,7 @@
use crate::{File, FileMode, Filesystem};
use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, string::String, vec::Vec};
use core::cell::{RefCell, RefMut};
use core::ffi::c_void;
use core::fmt;
use error::Errno;
@@ -34,6 +35,7 @@ pub struct Vnode {
props: RefCell<VnodeProps>,
kind: VnodeKind,
flags: u32,
pub data: RefCell<Option<VnodeData>>,
}
@@ -48,13 +50,19 @@ pub trait VnodeImpl {
fn truncate(&mut self, node: VnodeRef, size: usize) -> Result<(), Errno>;
fn read(&mut self, node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno>;
fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno>;
fn size(&mut self, node: VnodeRef) -> Result<usize, Errno>;
fn ioctl(&mut self, node: VnodeRef, cmd: u64, value: *mut c_void) -> Result<isize, Errno>;
}
impl Vnode {
pub fn new(name: &str, kind: VnodeKind) -> VnodeRef {
pub const SEEKABLE: u32 = 1 << 0;
pub fn new(name: &str, kind: VnodeKind, flags: u32) -> VnodeRef {
Rc::new(Self {
name: name.to_owned(),
kind,
flags,
props: RefCell::new(VnodeProps {
mode: FileMode::empty(),
}),
@@ -78,6 +86,10 @@ impl Vnode {
self.kind == VnodeKind::Directory
}
pub fn is_seekable(&self) -> bool {
self.flags & Self::SEEKABLE != 0
}
#[inline(always)]
pub const fn kind(&self) -> VnodeKind {
self.kind
@@ -218,6 +230,22 @@ impl Vnode {
Err(Errno::NotImplemented)
}
}
pub fn size(self: &VnodeRef) -> Result<usize, Errno> {
if let Some(ref mut data) = *self.data.borrow_mut() {
data.node.size(self.clone())
} else {
Err(Errno::NotImplemented)
}
}
pub fn ioctl(self: &VnodeRef, cmd: u64, value: *mut c_void) -> Result<isize, Errno> {
if let Some(ref mut data) = *self.data.borrow_mut() {
data.node.ioctl(self.clone(), cmd, value)
} else {
Err(Errno::NotImplemented)
}
}
}
impl fmt::Debug for Vnode {
@@ -262,6 +290,24 @@ mod tests {
fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> {
Err(Errno::NotImplemented)
}
fn ioctl(
&mut self,
_node: VnodeRef,
_cmd: u64,
_value: *mut c_void,
) -> Result<isize, Errno> {
Err(Errno::NotImplemented)
}
fn seek(
&mut self,
_node: VnodeRef,
_pos: usize,
_whence: SeekWhence,
) -> Result<usize, Errno> {
Err(Errno::NotImplemented)
}
}
impl Filesystem for DummyFs {
-15
View File
@@ -1,15 +0,0 @@
pub fn path_component_left(path: &str) -> (&str, &str) {
if let Some((left, right)) = path.split_once('/') {
(left, right.trim_start_matches('/'))
} else {
(path, "")
}
}
pub fn path_component_right(path: &str) -> (&str, &str) {
if let Some((left, right)) = path.rsplit_once('/') {
(left.trim_end_matches('/'), right)
} else {
(path, "")
}
}
+1
View File
@@ -13,6 +13,7 @@ test = false
error = { path = "../error" }
vfs = { path = "../fs/vfs" }
memfs = { path = "../fs/memfs" }
libcommon = { path = "../libcommon" }
cfg-if = "1.x.x"
tock-registers = "0.7.x"
fdt-rs = { version = "0.x.x", default-features = false }
+2 -2
View File
@@ -1,11 +1,11 @@
use crate::debug::Level;
use crate::util;
use error::Errno;
use fdt_rs::prelude::*;
use fdt_rs::{
base::DevTree,
index::{DevTreeIndex, DevTreeIndexNode, DevTreeIndexProp},
};
use libcommon::path_component_left;
#[repr(align(16))]
struct Wrap {
@@ -74,7 +74,7 @@ fn dump_node(level: Level, node: &INode, depth: usize) {
}
fn find_node<'a>(at: INode<'a>, path: &str) -> Option<INode<'a>> {
let (item, path) = util::path_component_left(path);
let (item, path) = path_component_left(path);
if item == "" {
assert_eq!(path, "");
Some(at)
+29 -11
View File
@@ -5,7 +5,9 @@ use crate::mem::{
phys::{self, PageUsage},
virt::{MapAttributes, Space},
};
use core::mem::{size_of, MaybeUninit};
use error::Errno;
use libcommon::{Read, Seek, SeekDir};
trait Elf {
type Addr;
@@ -86,12 +88,12 @@ fn map_flags(elf_flags: usize) -> MapAttributes {
unsafe fn load_bytes<F>(
space: &mut Space,
dst_virt: usize,
read: F,
mut read: F,
size: usize,
flags: usize,
) -> Result<(), Errno>
where
F: Fn(usize, &mut [u8]) -> Result<(), Errno>,
F: FnMut(usize, &mut [u8]) -> Result<(), Errno>,
{
let dst_page_off = dst_virt & 0xFFF;
let dst_page = dst_virt & !0xFFF;
@@ -125,17 +127,32 @@ where
Ok(())
}
unsafe fn read_struct<T, F: Seek + Read>(src: &mut F, pos: usize) -> Result<T, Errno> {
let mut storage: MaybeUninit<T> = MaybeUninit::uninit();
let size = size_of::<T>();
src.seek(pos as isize, SeekDir::Set)?;
let res = src.read(core::slice::from_raw_parts_mut(
storage.as_mut_ptr() as *mut u8,
size,
))?;
if res != size {
Err(Errno::InvalidArgument)
} else {
Ok(storage.assume_init())
}
}
///
pub fn load_elf(space: &mut Space, elf_base: *const u8) -> Result<usize, Errno> {
let ehdr: &Ehdr<Elf64> = unsafe { &*(elf_base as *const _) };
pub fn load_elf<F: Seek + Read>(space: &mut Space, source: &mut F) -> Result<usize, Errno> {
let ehdr: Ehdr<Elf64> = unsafe { read_struct(source, 0).unwrap() };
if &ehdr.ident[0..4] != b"\x7FELF" {
return Err(Errno::InvalidArgument);
}
for i in 0..(ehdr.phnum as usize) {
let phdr: &Phdr<Elf64> = unsafe {
&*(elf_base.add(ehdr.phoff as usize + ehdr.phentsize as usize * i) as *const _)
let phdr: Phdr<Elf64> = unsafe {
read_struct(source, ehdr.phoff as usize + ehdr.phentsize as usize * i).unwrap()
};
if phdr.typ == 1
@@ -154,11 +171,12 @@ pub fn load_elf(space: &mut Space, elf_base: *const u8) -> Result<usize, Errno>
space,
phdr.vaddr as usize,
|off, dst| {
let src = elf_base.add(phdr.offset as usize + off);
assert!(off + dst.len() <= phdr.filesz as usize);
let src_slice = core::slice::from_raw_parts(src, dst.len());
dst.copy_from_slice(src_slice);
Ok(())
source.seek(phdr.offset as isize + off as isize, SeekDir::Set)?;
if source.read(dst)? == dst.len() {
Ok(())
} else {
Err(Errno::InvalidArgument)
}
},
phdr.filesz as usize,
phdr.flags as usize,
+4 -8
View File
@@ -66,21 +66,17 @@ pub unsafe fn enter(initrd: Option<(usize, usize)>) -> ! {
let (start, end) = unsafe { *(initrd_ptr as *const (usize, usize)) };
let size = end - start;
infoln!("Constructing initrd filesystem in memory, this may take a while...");
let fs = Ramfs::open(start as *mut u8, size, MemfsBlockAlloc {}).unwrap();
infoln!("Done constructing ramfs");
let root = fs.root().unwrap();
let ioctx = Ioctx::new(root);
// Open a test file
let node = ioctx.find(None, "/test.txt").unwrap();
let node = ioctx.find(None, "/init").unwrap();
let mut file = node.open().unwrap();
let mut buf = [0u8; 16];
while let Ok(count) = file.read(&mut buf) {
if count == 0 {
break;
}
debugln!("Read {} bytes: {:?}", count, &buf[0..count]);
}
Process::execve(|space| elf::load_elf(space, &mut file), 0).unwrap();
}, initrd as usize);
}
SCHED.enter();
-9
View File
@@ -51,12 +51,3 @@ impl<T> InitOnce<T> {
}
unsafe impl<T> Sync for InitOnce<T> {}
///
pub fn path_component_left(path: &str) -> (&str, &str) {
if let Some((left, right)) = path.split_once('/') {
(left, right.trim_start_matches('/'))
} else {
(path, "")
}
}
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "libcommon"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
error = { path = "../error" }
+42
View File
@@ -0,0 +1,42 @@
#![no_std]
use error::Errno;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SeekDir {
Set,
End,
Current,
}
pub trait Read {
fn read(&mut self, data: &mut [u8]) -> Result<usize, Errno>;
}
pub trait Seek {
fn seek(&mut self, off: isize, whence: SeekDir) -> Result<usize, Errno>;
}
pub trait Write {
fn write(&mut self, data: &[u8]) -> Result<usize, Errno>;
}
pub fn path_component_left(path: &str) -> (&str, &str) {
if let Some((left, right)) = path.split_once('/') {
(left, right.trim_start_matches('/'))
} else {
(path, "")
}
}
pub fn path_component_right(path: &str) -> (&str, &str) {
if let Some((left, right)) = path.rsplit_once('/') {
(left.trim_end_matches('/'), right)
} else {
(path, "")
}
}
#[cfg(test)]
mod tests {
}
View File