summary refs log tree commit diff
path: root/ripple/fossil/src
diff options
context:
space:
mode:
Diffstat (limited to 'ripple/fossil/src')
-rw-r--r--ripple/fossil/src/bin/mount.rs299
-rw-r--r--ripple/fossil/src/lib.rs4
2 files changed, 245 insertions, 58 deletions
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<u64>,
+		_flags: i32,
+		_lock_owner: Option<u64>,
 		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<u32, Node>,
+		by_name: BTreeMap<String, u32>,
+		size: u32,
+	}
+
+	impl Directory {
+		pub fn iter(&self) -> impl Iterator<Item = (&str, u32, &Node)> {
+			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<String, Node>) -> 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",