From 6f38135886511ebc9d41862273b81396ad90becc Mon Sep 17 00:00:00 2001 From: edef Date: Tue, 12 Apr 2022 20:07:39 +0000 Subject: ripple/fossil/mount: serve an in-memory filesystem tree Change-Id: I92073aae32bcf603799b83717635a13edbbef190 --- ripple/fossil/src/bin/mount.rs | 299 +++++++++++++++++++++++++++++++++-------- ripple/fossil/src/lib.rs | 4 +- 2 files changed, 245 insertions(+), 58 deletions(-) (limited to 'ripple') diff --git a/ripple/fossil/src/bin/mount.rs b/ripple/fossil/src/bin/mount.rs index 4aaa0e0..ceeb01e 100644 --- a/ripple/fossil/src/bin/mount.rs +++ b/ripple/fossil/src/bin/mount.rs @@ -2,10 +2,13 @@ // SPDX-License-Identifier: OSL-3.0 use { + fossil::store, lazy_static::lazy_static, - libc::{c_int, ENOENT, ENOSYS, EROFS}, - log::{debug, warn}, + libc::{c_int, EINVAL, ENOENT, ENOSYS, EROFS}, + log::debug, + prost::Message, std::{ + io::{self, Read}, time::{Duration, SystemTime, UNIX_EPOCH}, }, }; @@ -68,22 +71,37 @@ fn main() { env_logger::init(); let store = fossil::Store::open("fossil.db").unwrap(); + let root = memtree::load_root(&store, { + let mut stdin = io::stdin(); + + let mut bytes = Vec::new(); + stdin.read_to_end(&mut bytes).unwrap(); + + store::Directory::decode(&*bytes).unwrap() + }); fuser::mount2( - Filesystem::open(store), + Filesystem::open(store, root), "mnt", &[fuser::MountOption::DefaultPermissions], ) .unwrap(); } - struct Filesystem { store: fossil::Store, + root: memtree::Node, } impl Filesystem { - fn open(store: fossil::Store) -> Filesystem { - Filesystem { store } + fn open(store: fossil::Store, root: memtree::Directory) -> Filesystem { + Filesystem { + store, + root: memtree::Node::Directory(root), + } + } + + fn find(&self, ino: u64) -> Option<&memtree::Node> { + self.root.find(ino.checked_sub(1)?.try_into().ok()?) } } @@ -105,38 +123,55 @@ impl fuser::Filesystem for Filesystem { name: &std::ffi::OsStr, reply: fuser::ReplyEntry, ) { - if parent != 1 { - warn!( - "[Not Implemented] lookup(parent: {:#x?}, name {:?})", - parent, name - ); - reply.error(ENOSYS); - return; - } - if name != "hello" { - reply.error(ENOENT); - return; + let dir = match self.find(parent) { + Some(memtree::Node::Directory(d)) => d, + Some(_) => { + reply.error(EINVAL); + return; + } + None => { + reply.error(ENOENT); + return; + } + }; + + let entry = name.to_str().and_then(|name| dir.lookup(name)); + match entry { + None => reply.error(ENOENT), + Some((idx, node)) => { + let attr; + let ino = parent + idx as u64 + 1; + match node { + memtree::Node::Directory(d) => { + attr = file_attr(ino, Kind::Directory, d.len() as u64); + } + memtree::Node::File(f) => { + attr = file_attr(ino, Kind::File, f.size as u64); + } + memtree::Node::Link { target } => todo!(), + } + reply.entry(&Duration::ZERO, &attr, 0); + } } - reply.entry( - &Duration::ZERO, - &file_attr(2, Kind::File, "world".len() as u64), - 0, - ); } fn forget(&mut self, _req: &fuser::Request<'_>, _ino: u64, _nlookup: u64) {} fn getattr(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyAttr) { - match ino { - 1 => reply.attr(&Duration::ZERO, &file_attr(ino, Kind::Directory, 2)), - 2 => reply.attr( - &Duration::ZERO, - &file_attr(ino, Kind::File, "world".len() as u64), - ), - _ => { - warn!("[Not Implemented] getattr(ino: {:#x?})", ino); - reply.error(ENOSYS); + if let Some(node) = self.find(ino) { + let attr; + match node { + memtree::Node::Directory(d) => { + attr = file_attr(ino, Kind::Directory, d.len() as u64); + } + memtree::Node::File(f) => { + attr = file_attr(ino, Kind::File, f.size as u64); + } + memtree::Node::Link { target } => todo!(), } + reply.attr(&Duration::ZERO, &attr); + } else { + reply.error(ENOENT); } } @@ -258,33 +293,27 @@ impl fuser::Filesystem for Filesystem { &mut self, _req: &fuser::Request<'_>, ino: u64, - fh: u64, + _fh: u64, offset: i64, size: u32, - flags: i32, - lock_owner: Option, + _flags: i32, + _lock_owner: Option, reply: fuser::ReplyData, ) { - match ino { - 2 => { + match self.find(ino) { + Some(memtree::Node::File(f)) => { let offset = offset as usize; let size = size as usize; - let content = b"world"; + let content = self.store.read_blob(f.ident); let mut buffer = content.get(offset..).unwrap_or_default(); if buffer.len() > size { buffer = &buffer[..size]; } reply.data(buffer); } - _ => { - warn!( - "[Not Implemented] read(ino: {:#x?}, fh: {}, offset: {}, size: {}, \ - flags: {:#x?}, lock_owner: {:?})", - ino, fh, offset, size, flags, lock_owner - ); - reply.error(ENOSYS); - } + Some(_) => reply.error(EINVAL), + None => reply.error(ENOENT), } } @@ -360,25 +389,36 @@ impl fuser::Filesystem for Filesystem { &mut self, _req: &fuser::Request<'_>, ino: u64, - fh: u64, + _fh: u64, offset: i64, mut reply: fuser::ReplyDirectory, ) { - if ino != 1 { - warn!( - "[Not Implemented] readdir(ino: {:#x?}, fh: {}, offset: {})", - ino, fh, offset - ); - reply.error(ENOSYS); - return; - } + let dir = match self.find(ino) { + Some(memtree::Node::Directory(d)) => d, + Some(_) => { + reply.error(EINVAL); + return; + } + None => { + reply.error(ENOENT); + return; + } + }; - let children = &[ + let mut children = vec![ (ino, fuser::FileType::Directory, "."), (ino, fuser::FileType::Directory, ".."), - (0x2, fuser::FileType::RegularFile, "hello"), ]; + for (name, idx, node) in dir.iter() { + let kind = match node { + memtree::Node::Directory(_) => fuser::FileType::Directory, + memtree::Node::File(_) => fuser::FileType::RegularFile, + memtree::Node::Link { .. } => fuser::FileType::Symlink, + }; + children.push((ino + idx as u64 + 1, kind, name)); + } + for (offset, &(ino, kind, name)) in children.iter().enumerate().skip(offset as usize) { if reply.add(ino, (offset + 1) as i64, kind, name) { break; @@ -596,3 +636,150 @@ impl fuser::Filesystem for Filesystem { reply.error(EROFS); } } + +mod memtree { + pub use fossil::FileRef; + use { + fossil::store, + prost::Message, + std::{collections::BTreeMap, fmt}, + }; + + #[derive(Debug)] + pub enum Node { + Directory(Directory), + File(FileRef), + Link { target: String }, + } + + #[derive(Default)] + pub struct Directory { + by_index: BTreeMap, + by_name: BTreeMap, + size: u32, + } + + impl Directory { + pub fn iter(&self) -> impl Iterator { + let by_name = self.by_name.iter(); + let by_index = self.by_index.iter(); + + by_name.zip(by_index).map(|((name, &idx), (&idy, node))| { + assert_eq!(idx, idy); + (name.as_str(), idx, node) + }) + } + + pub fn lookup(&self, name: &str) -> Option<(u32, &Node)> { + let &idx = self.by_name.get(name)?; + Some((idx, &self.by_index[&idx])) + } + } + + impl fmt::Debug for Directory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Directory") + .field("size", &self.size) + .field("entries", &DirectoryMembers(self)) + .finish() + } + } + + struct DirectoryMembers<'a>(&'a Directory); + + impl fmt::Debug for DirectoryMembers<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .entries(self.0.iter().map(|(name, idx, node)| ((name, idx), node))) + .finish() + } + } + + pub fn load_root(store: &fossil::Store, pb: store::Directory) -> Directory { + let mut children = BTreeMap::new(); + + for store::DirectoryNode { + name, + r#ref, + size: _, + } in pb.directories + { + let bytes = store.read_blob(fossil::digest_from_bytes(&r#ref)); + let pb = store::Directory::decode(&*bytes).unwrap(); + let child = load_root(store, pb); + children.insert(name, Node::Directory(child)); + } + + for store::FileNode { + name, + r#ref, + executable, + size: child_size, + } in pb.files + { + let child = fossil::FileRef { + ident: fossil::digest_from_bytes(&r#ref), + executable, + size: child_size, + }; + children.insert(name, Node::File(child)); + } + + for store::LinkNode { name, target } in pb.links { + children.insert(name, Node::Link { target }); + } + + Directory::from_children(children) + } + + impl Directory { + fn from_children(children: BTreeMap) -> Directory { + let mut d = Directory::default(); + for (name, child) in children { + let index = d.size; + let size = match child { + Node::Directory(Directory { size, .. }) => { + size.checked_add(1).expect("overflow") + } + Node::File(_) | Node::Link { .. } => 1, + }; + + d.size = index.checked_add(size).expect("overflow"); + d.by_name.insert(name, index); + d.by_index.insert(index, child); + } + d + } + + pub fn len(&self) -> usize { + let len = self.by_index.len(); + let lem = self.by_name.len(); + debug_assert_eq!(len, lem); + len + } + } + + impl Node { + pub fn find(&self, mut index: u32) -> Option<&Node> { + let mut root = self; + loop { + let d = match (index, root) { + (0, _) => { + break Some(root); + } + (_, Node::Directory(d)) => { + index -= 1; + d + } + _ => { + break None; + } + }; + + let (&child_index, child) = d.by_index.range(..=index).next_back()?; + root = child; + index = index.checked_sub(child_index).unwrap(); + } + } + } +} diff --git a/ripple/fossil/src/lib.rs b/ripple/fossil/src/lib.rs index 6576847..8ed5410 100644 --- a/ripple/fossil/src/lib.rs +++ b/ripple/fossil/src/lib.rs @@ -138,7 +138,7 @@ pub struct DirectoryRef { pub size: u32, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct FileRef { pub ident: Digest, pub executable: bool, @@ -228,7 +228,7 @@ impl Directory { } #[track_caller] -fn digest_from_bytes(bytes: &[u8]) -> Digest { +pub fn digest_from_bytes(bytes: &[u8]) -> Digest { if bytes.len() != DIGEST_BYTES { panic!( "digest is {} bytes, expecting {} bytes", -- cgit 1.4.1