summary refs log tree commit diff
diff options
context:
space:
mode:
authoredef <edef@unfathomable.blue>2022-05-03 18:46:31 +0000
committeredef <edef@unfathomable.blue>2022-05-03 21:21:50 +0000
commit31211f84c2a2467a3b4b1a400d16c7195485b9d6 (patch)
tree9b5d6efcb81278aa0dffe56a538b73c9a138eb0a
parent41c76d66233db06c029db4fd4e881614a1fdd350 (diff)
ripple/fossil/mount: expose arbitrary directories by digest
This makes the filesystem more like eg /nix/store.

    ~/src/ripple> ./target/release/add fossil
    puma5z7rnb4tmnqk8ixgryobay9ifg8txh69635snkgx8dis6quo
    ~/src/ripple> ./target/release/mount &
    ~/src/ripple> ls mnt/puma5z7rnb4tmnqk8ixgryobay9ifg8txh69635snkgx8dis6quo
    benches  build.rs  Cargo.toml  src

Change-Id: Ic35f81ffec521f49ce2e4a414919e1ff717d7041
-rw-r--r--ripple/fossil/src/bin/mount.rs189
1 files changed, 132 insertions, 57 deletions
diff --git a/ripple/fossil/src/bin/mount.rs b/ripple/fossil/src/bin/mount.rs
index 1dafc41..846e543 100644
--- a/ripple/fossil/src/bin/mount.rs
+++ b/ripple/fossil/src/bin/mount.rs
@@ -10,6 +10,7 @@ use {
 	log::debug,
 	std::{
 		cell::RefCell,
+		collections::{btree_map, hash_map, BTreeMap, HashMap},
 		io::{self, Read, Seek},
 		path::PathBuf,
 		time::{Duration, SystemTime, UNIX_EPOCH},
@@ -77,19 +78,15 @@ fn file_attr(ino: u64, node: memtree::Node) -> fuser::FileAttr {
 struct Args {
 	#[clap(long, default_value = "fossil.db")]
 	store: PathBuf,
-	#[clap(parse(try_from_str = fossil::digest_from_str))]
-	root: fossil::Digest,
 }
 
 fn main() {
 	env_logger::init();
 	let args = Args::parse();
-
 	let store = fossil::Store::open(args.store).unwrap();
-	let root = memtree::load_root(&store, args.root);
 
 	fuser::mount2(
-		Filesystem::open(store, root),
+		Filesystem::open(store),
 		"mnt",
 		&[fuser::MountOption::DefaultPermissions],
 	)
@@ -98,16 +95,56 @@ fn main() {
 
 struct Filesystem {
 	store: fossil::Store,
-	root: memtree::Directory,
+	roots: BTreeMap<u64, memtree::Directory>,
+	roots_by_ident: HashMap<fossil::Digest, u64>,
+	inode_tail: u64,
 }
 
 impl Filesystem {
-	fn open(store: fossil::Store, root: memtree::Directory) -> Filesystem {
-		Filesystem { store, root }
+	fn open(store: fossil::Store) -> Filesystem {
+		Filesystem {
+			store,
+			roots: BTreeMap::new(),
+			roots_by_ident: HashMap::new(),
+			inode_tail: 0x1000,
+		}
 	}
 
 	fn find(&self, ino: u64) -> Option<memtree::Node> {
-		memtree::Node::Directory(&self.root).find(ino.checked_sub(1)?.try_into().ok()?)
+		if ino == 1 {
+			// the mountpoint is special-cased in relevant callers
+			unreachable!();
+		}
+
+		let (&root_ino, root) = self.roots.range(..=ino).next_back()?;
+		let index: u32 = ino.checked_sub(root_ino)?.try_into().ok()?;
+
+		memtree::Node::Directory(root).find(index)
+	}
+
+	fn lookup_root(&mut self, name: &std::ffi::OsStr) -> Option<(u64, &memtree::Directory)> {
+		let name = name.to_str()?;
+		let ident = fossil::digest_from_str(name).ok()?;
+
+		Some(match self.roots_by_ident.entry(ident) {
+			hash_map::Entry::Occupied(e) => {
+				let &inode = e.get();
+				(inode, &self.roots[&inode])
+			}
+			hash_map::Entry::Vacant(e) => {
+				let root = memtree::load_root(&self.store, ident);
+				let inode = self.inode_tail;
+				self.inode_tail += inode + 1 + root.size() as u64;
+
+				e.insert(inode);
+				let root = match self.roots.entry(inode) {
+					btree_map::Entry::Occupied(_) => unreachable!(),
+					btree_map::Entry::Vacant(e2) => e2.insert(root),
+				};
+
+				(inode, root)
+			}
+		})
 	}
 
 	unsafe fn from_fh<'a>(&'a self, fh: u64) -> *mut Handle<'a> {
@@ -138,24 +175,38 @@ impl fuser::Filesystem for Filesystem {
 		name: &std::ffi::OsStr,
 		reply: fuser::ReplyEntry,
 	) {
-		let dir = match self.find(parent) {
-			Some(memtree::Node::Directory(d)) => d,
-			Some(_) => {
-				reply.error(EINVAL);
-				return;
-			}
-			None => {
-				reply.error(ENOENT);
-				return;
-			}
-		};
+		match parent {
+			1 => match self.lookup_root(name) {
+				Some((ino, dir)) => {
+					reply.entry(
+						&Duration::ZERO,
+						&file_attr(ino, memtree::Node::Directory(dir)),
+						0,
+					);
+				}
+				None => reply.error(ENOENT),
+			},
+			_ => {
+				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(entry) => {
-				let ino = parent + entry.index as u64 + 1;
-				reply.entry(&Duration::ZERO, &file_attr(ino, entry.node), 0);
+				let entry = name.to_str().and_then(|name| dir.lookup(name));
+				match entry {
+					None => reply.error(ENOENT),
+					Some(entry) => {
+						let ino = parent + entry.index as u64 + 1;
+						reply.entry(&Duration::ZERO, &file_attr(ino, entry.node), 0);
+					}
+				}
 			}
 		}
 	}
@@ -163,10 +214,19 @@ impl fuser::Filesystem for Filesystem {
 	fn forget(&mut self, _req: &fuser::Request<'_>, _ino: u64, _nlookup: u64) {}
 
 	fn getattr(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyAttr) {
-		if let Some(node) = self.find(ino) {
-			reply.attr(&Duration::ZERO, &file_attr(ino, node));
-		} else {
-			reply.error(ENOENT);
+		match ino {
+			1 => {
+				let node = memtree::Directory::default();
+				let node = memtree::Node::Directory(&node);
+				reply.attr(&Duration::ZERO, &file_attr(ino, node));
+			}
+			_ => {
+				if let Some(node) = self.find(ino) {
+					reply.attr(&Duration::ZERO, &file_attr(ino, node));
+				} else {
+					reply.error(ENOENT);
+				}
+			}
 		}
 	}
 
@@ -308,39 +368,48 @@ impl fuser::Filesystem for Filesystem {
 		offset: i64,
 		mut reply: fuser::ReplyDirectory,
 	) {
-		let dir = match self.find(ino) {
-			Some(memtree::Node::Directory(d)) => d,
-			Some(_) => {
-				reply.error(EINVAL);
-				return;
+		match ino {
+			1 => {
+				reply.ok();
 			}
-			None => {
-				reply.error(ENOENT);
-				return;
-			}
-		};
+			_ => {
+				let dir = match self.find(ino) {
+					Some(memtree::Node::Directory(d)) => d,
+					Some(_) => {
+						reply.error(EINVAL);
+						return;
+					}
+					None => {
+						reply.error(ENOENT);
+						return;
+					}
+				};
 
-		let mut children = vec![
-			(ino, fuser::FileType::Directory, "."),
-			(ino, fuser::FileType::Directory, ".."),
-		];
+				let mut children = vec![
+					(ino, fuser::FileType::Directory, "."),
+					(ino, fuser::FileType::Directory, ".."),
+				];
+
+				for entry in dir.iter() {
+					let kind = match entry.node {
+						memtree::Node::Directory(_) => fuser::FileType::Directory,
+						memtree::Node::File(_) => fuser::FileType::RegularFile,
+						memtree::Node::Link { .. } => fuser::FileType::Symlink,
+					};
+					children.push((ino + entry.index as u64 + 1, kind, entry.name));
+				}
 
-		for entry in dir.iter() {
-			let kind = match entry.node {
-				memtree::Node::Directory(_) => fuser::FileType::Directory,
-				memtree::Node::File(_) => fuser::FileType::RegularFile,
-				memtree::Node::Link { .. } => fuser::FileType::Symlink,
-			};
-			children.push((ino + entry.index as u64 + 1, kind, entry.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;
+					}
+				}
 
-		for (offset, &(ino, kind, name)) in children.iter().enumerate().skip(offset as usize) {
-			if reply.add(ino, (offset + 1) as i64, kind, name) {
-				break;
+				reply.ok();
 			}
 		}
-
-		reply.ok();
 	}
 
 	fn readdirplus(
@@ -727,6 +796,12 @@ mod memtree {
 				node: self.by_index[&index].as_ref(),
 			})
 		}
+
+		/// Get the directory's size.
+		#[must_use]
+		pub fn size(&self) -> u32 {
+			self.size
+		}
 	}
 
 	impl fmt::Debug for Directory {