chore(tvix): move store/fs to castore/fs
With the recent introduction of the RootNodes trait, there's nothing in the fs module pulling in tvix-store dependencies, so it can live in tvix-castore. This allows other crates to make use of TvixStoreFS, without having to pull in tvix-store. For example, a tvix-build using a fuse mountpoint at /nix/store doesn't need a PathInfoService to hold the root nodes that should be present, but just a list. tvix-store now has a pathinfoservice/fs module, which contains the necessary glue logic to implement the RootNodes trait for a PathInfoService. To satisfy Rust orphan rules for trait implementations, we had to add a small wrapper struct. It's mostly hidden away by the make_fs helper function returning a TvixStoreFs. It can't be entirely private, as its still leaking into the concrete type of TvixStoreFS. tvix-store still has `fuse` and `virtiofs` features, but they now simply enable these features in the `tvix-castore` crate they depend on. The tests for the fuse functionality stay in tvix-store for now, as they populate the root nodes through a PathInfoService. Once above mentioned "list of root nodes" implementation exists, we might want to shuffle this around one more time. Fixes b/341. Change-Id: I989f664827a5a361b23b34368d242d10c157c756 Reviewed-on: https://cl.tvl.fyi/c/depot/+/10378 Autosubmit: flokli <flokli@flokli.de> Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org>
This commit is contained in:
parent
52cad86195
commit
a5167c508c
18 changed files with 257 additions and 199 deletions
|
|
@ -1,207 +0,0 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use super::inodes::{DirectoryInodeData, InodeData};
|
||||
use tvix_castore::B3Digest;
|
||||
|
||||
/// InodeTracker keeps track of inodes, stores data being these inodes and deals
|
||||
/// with inode allocation.
|
||||
pub struct InodeTracker {
|
||||
data: HashMap<u64, Arc<InodeData>>,
|
||||
|
||||
// lookup table for blobs by their B3Digest
|
||||
blob_digest_to_inode: HashMap<B3Digest, u64>,
|
||||
|
||||
// lookup table for symlinks by their target
|
||||
symlink_target_to_inode: HashMap<bytes::Bytes, u64>,
|
||||
|
||||
// lookup table for directories by their B3Digest.
|
||||
// Note the corresponding directory may not be present in data yet.
|
||||
directory_digest_to_inode: HashMap<B3Digest, u64>,
|
||||
|
||||
// the next inode to allocate
|
||||
next_inode: u64,
|
||||
}
|
||||
|
||||
impl Default for InodeTracker {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data: Default::default(),
|
||||
|
||||
blob_digest_to_inode: Default::default(),
|
||||
symlink_target_to_inode: Default::default(),
|
||||
directory_digest_to_inode: Default::default(),
|
||||
|
||||
next_inode: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InodeTracker {
|
||||
// Retrieves data for a given inode, if it exists.
|
||||
pub fn get(&self, ino: u64) -> Option<Arc<InodeData>> {
|
||||
self.data.get(&ino).cloned()
|
||||
}
|
||||
|
||||
// Replaces data for a given inode.
|
||||
// Panics if the inode doesn't already exist.
|
||||
pub fn replace(&mut self, ino: u64, data: Arc<InodeData>) {
|
||||
if self.data.insert(ino, data).is_none() {
|
||||
panic!("replace called on unknown inode");
|
||||
}
|
||||
}
|
||||
|
||||
// Stores data and returns the inode for it.
|
||||
// In case an inode has already been allocated for the same data, that inode
|
||||
// is returned, otherwise a new one is allocated.
|
||||
// In case data is a [InodeData::Directory], inodes for all items are looked
|
||||
// up
|
||||
pub fn put(&mut self, data: InodeData) -> u64 {
|
||||
match data {
|
||||
InodeData::Regular(ref digest, _, _) => {
|
||||
match self.blob_digest_to_inode.get(digest) {
|
||||
Some(found_ino) => {
|
||||
// We already have it, return the inode.
|
||||
*found_ino
|
||||
}
|
||||
None => self.insert_and_increment(data),
|
||||
}
|
||||
}
|
||||
InodeData::Symlink(ref target) => {
|
||||
match self.symlink_target_to_inode.get(target) {
|
||||
Some(found_ino) => {
|
||||
// We already have it, return the inode.
|
||||
*found_ino
|
||||
}
|
||||
None => self.insert_and_increment(data),
|
||||
}
|
||||
}
|
||||
InodeData::Directory(DirectoryInodeData::Sparse(ref digest, _size)) => {
|
||||
// check the lookup table if the B3Digest is known.
|
||||
match self.directory_digest_to_inode.get(digest) {
|
||||
Some(found_ino) => {
|
||||
// We already have it, return the inode.
|
||||
*found_ino
|
||||
}
|
||||
None => {
|
||||
// insert and return the inode
|
||||
self.insert_and_increment(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Inserting [DirectoryInodeData::Populated] doesn't normally happen,
|
||||
// only via [replace].
|
||||
InodeData::Directory(DirectoryInodeData::Populated(..)) => {
|
||||
unreachable!("should never be called with DirectoryInodeData::Populated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts the data and returns the inode it was stored at, while
|
||||
// incrementing next_inode.
|
||||
fn insert_and_increment(&mut self, data: InodeData) -> u64 {
|
||||
let ino = self.next_inode;
|
||||
// insert into lookup tables
|
||||
match data {
|
||||
InodeData::Regular(ref digest, _, _) => {
|
||||
self.blob_digest_to_inode.insert(digest.clone(), ino);
|
||||
}
|
||||
InodeData::Symlink(ref target) => {
|
||||
self.symlink_target_to_inode.insert(target.clone(), ino);
|
||||
}
|
||||
InodeData::Directory(DirectoryInodeData::Sparse(ref digest, _size)) => {
|
||||
self.directory_digest_to_inode.insert(digest.clone(), ino);
|
||||
}
|
||||
// This is currently not used outside test fixtures.
|
||||
// Usually a [DirectoryInodeData::Sparse] is inserted and later
|
||||
// "upgraded" with more data.
|
||||
// However, as a future optimization, a lookup for a PathInfo could trigger a
|
||||
// [DirectoryService::get_recursive()] request that "forks into
|
||||
// background" and prepopulates all Directories in a closure.
|
||||
InodeData::Directory(DirectoryInodeData::Populated(ref digest, _)) => {
|
||||
self.directory_digest_to_inode.insert(digest.clone(), ino);
|
||||
}
|
||||
}
|
||||
// Insert data
|
||||
self.data.insert(ino, Arc::new(data));
|
||||
|
||||
// increment inode counter and return old inode.
|
||||
self.next_inode += 1;
|
||||
ino
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::fixtures;
|
||||
|
||||
use super::InodeData;
|
||||
use super::InodeTracker;
|
||||
|
||||
/// Getting something non-existent should be none
|
||||
#[test]
|
||||
fn get_nonexistent() {
|
||||
let inode_tracker = InodeTracker::default();
|
||||
assert!(inode_tracker.get(1).is_none());
|
||||
}
|
||||
|
||||
/// Put of a regular file should allocate a uid, which should be the same when inserting again.
|
||||
#[test]
|
||||
fn put_regular() {
|
||||
let mut inode_tracker = InodeTracker::default();
|
||||
let f = InodeData::Regular(
|
||||
fixtures::BLOB_A_DIGEST.clone(),
|
||||
fixtures::BLOB_A.len() as u64,
|
||||
false,
|
||||
);
|
||||
|
||||
// put it in
|
||||
let ino = inode_tracker.put(f.clone());
|
||||
|
||||
// a get should return the right data
|
||||
let data = inode_tracker.get(ino).expect("must be some");
|
||||
match *data {
|
||||
InodeData::Regular(ref digest, _, _) => {
|
||||
assert_eq!(&fixtures::BLOB_A_DIGEST.clone(), digest);
|
||||
}
|
||||
InodeData::Symlink(_) | InodeData::Directory(..) => panic!("wrong type"),
|
||||
}
|
||||
|
||||
// another put should return the same ino
|
||||
assert_eq!(ino, inode_tracker.put(f));
|
||||
|
||||
// inserting another file should return a different ino
|
||||
assert_ne!(
|
||||
ino,
|
||||
inode_tracker.put(InodeData::Regular(
|
||||
fixtures::BLOB_B_DIGEST.clone(),
|
||||
fixtures::BLOB_B.len() as u64,
|
||||
false,
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// Put of a symlink should allocate a uid, which should be the same when inserting again
|
||||
#[test]
|
||||
fn put_symlink() {
|
||||
let mut inode_tracker = InodeTracker::default();
|
||||
let f = InodeData::Symlink("target".into());
|
||||
|
||||
// put it in
|
||||
let ino = inode_tracker.put(f.clone());
|
||||
|
||||
// a get should return the right data
|
||||
let data = inode_tracker.get(ino).expect("must be some");
|
||||
match *data {
|
||||
InodeData::Symlink(ref target) => {
|
||||
assert_eq!(b"target".to_vec(), *target);
|
||||
}
|
||||
InodeData::Regular(..) | InodeData::Directory(..) => panic!("wrong type"),
|
||||
}
|
||||
|
||||
// another put should return the same ino
|
||||
assert_eq!(ino, inode_tracker.put(f));
|
||||
|
||||
// inserting another file should return a different ino
|
||||
assert_ne!(ino, inode_tracker.put(InodeData::Symlink("target2".into())));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue