use crate::{EntryName, FolderPath, Inode};
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use std::sync::Arc;
pub type Generation = u64;
pub type LookupCount = u64;
pub(crate) trait InodeToPath: std::fmt::Debug {
fn add_leaf(&self, parent: Inode, name: &OsStr) -> Option<(Inode, Generation)>;
fn add_dir(&self, parent: Inode, name: &OsStr) -> Option<(Inode, Generation)>;
fn add_or_get_leaf(&self, parent: Inode, name: &OsStr) -> Option<(Inode, Generation)>;
fn add_or_get_dir(&self, parent: Inode, name: &OsStr) -> Option<(Inode, Generation)>;
fn create_or_get_leaf(&self, parent: Inode, name: &OsStr) -> Option<(bool, Inode, Generation)>;
fn forget(&self, inode: Inode, n: LookupCount) -> LookupCount;
fn get_path(&self, inode: Inode) -> Option<EntryName>;
fn resolve_from_parent(&self, parent: Inode, name: OsString) -> Option<EntryName> {
let parent = self.get_folder_path(parent)?;
Some(EntryName::new(parent, name.into()))
}
fn get_folder_path(&self, inode: Inode) -> Option<FolderPath>;
fn get_parent_inode(&self, ino: Inode) -> Option<Inode>;
fn lookup(&self, inode: Inode);
fn rename(
&self,
oldparent: Inode,
oldname: &OsStr,
newparent: Inode,
newname: &OsStr,
) -> Option<()>;
fn unlink(&self, parent: Inode, name: &OsStr);
}
mod child_key {
use super::Inode;
use std::borrow::Borrow;
use std::ffi::OsStr;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
#[derive(Clone, Copy, Debug)]
pub(super) struct ChildKeyRef<'a> {
parent: Inode,
name: &'a OsStr,
}
impl<'a> ChildKeyRef<'a> {
pub(super) fn new(parent: Inode, name: &'a OsStr) -> Self {
Self { parent, name }
}
pub(super) fn name(&self) -> &OsStr {
self.name
}
}
impl<'a> Hash for ChildKeyRef<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.parent.hash(state);
self.name().hash(state);
}
}
impl<'a> PartialEq for ChildKeyRef<'a> {
fn eq(&self, other: &Self) -> bool {
self.parent == other.parent && self.name() == other.name()
}
}
impl<'a> Eq for ChildKeyRef<'a> {}
#[derive(Debug)]
pub(super) struct ChildKey {
_name: Arc<OsStr>,
borrowed: ChildKeyRef<'static>,
}
impl ChildKey {
pub(super) fn new(parent: Inode, name: Arc<OsStr>) -> Self {
let ref_name: &'static OsStr = unsafe { &(*(name.as_ref() as *const OsStr)) };
let borrowed = ChildKeyRef::new(parent, ref_name);
Self {
_name: name,
borrowed,
}
}
}
impl<'a> Borrow<ChildKeyRef<'a>> for ChildKey {
fn borrow(&self) -> &ChildKeyRef<'a> {
&self.borrowed
}
}
impl Hash for ChildKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.borrowed.hash(state);
}
}
impl PartialEq for ChildKey {
fn eq(&self, other: &Self) -> bool {
self.borrowed == other.borrowed
}
}
impl Eq for ChildKey {}
}
#[derive(Debug)]
struct FolderEntry {
parent: Inode,
path: Arc<PathBuf>,
parent_path: Arc<PathBuf>,
name: Arc<OsStr>,
}
impl FolderEntry {
fn with_parent(parent: Inode, parent_path: Arc<PathBuf>, name: Arc<OsStr>) -> Self {
Self {
path: Arc::new(parent_path.join(name.as_ref())),
parent,
parent_path,
name,
}
}
fn set_path(&mut self, full_path: Arc<PathBuf>) -> Option<()> {
self.name = Arc::from(full_path.file_name()?.to_os_string());
self.parent_path = Arc::new(full_path.parent()?.into());
self.path = full_path;
Some(())
}
fn path(&self) -> Arc<PathBuf> {
self.path.clone()
}
fn name(&self) -> &OsStr {
&self.name
}
}
#[derive(Debug)]
struct LeafEntry {
parent: Inode,
name: Arc<OsStr>,
}
#[derive(Debug)]
struct RootEntry {
path: Arc<PathBuf>,
name: Arc<OsStr>,
}
impl RootEntry {
fn to_pair(&self) -> (Arc<PathBuf>, Arc<OsStr>) {
(self.path.clone(), self.name.clone())
}
}
#[derive(Debug)]
enum Entry {
Vacant,
Root(RootEntry),
Folder(FolderEntry),
Leaf(LeafEntry),
}
#[inline(always)]
fn root_path() -> Arc<PathBuf> {
Arc::from(PathBuf::from(OsStr::new("/")))
}
#[inline(always)]
fn empty_name() -> Arc<OsStr> {
Arc::from(OsStr::new(""))
}
#[derive(Debug)]
struct InodeEntry {
entry: Entry,
linked: bool,
child_count: usize,
lookups: LookupCount,
generation: Generation,
}
impl InodeEntry {
fn path(&self, table: &[InodeEntry]) -> Option<(Arc<PathBuf>, Arc<OsStr>)> {
match &self.entry {
Entry::Vacant => None,
Entry::Root(root_entry) => Some(root_entry.to_pair()),
Entry::Folder(folder) => Some((folder.parent_path.clone(), folder.name.clone())),
Entry::Leaf(leaf) => {
let parent_idx = leaf.parent as usize - 1;
match &table.get(parent_idx)?.entry {
Entry::Root(root_entry) => Some((root_entry.path.clone(), leaf.name.clone())),
Entry::Folder(parent) => Some((parent.path(), leaf.name.clone())),
_ => None,
}
}
}
}
}
#[derive(Debug)]
pub(crate) struct InodeTable {
inner: parking_lot::RwLock<InnerInodeTable>,
}
impl InodeTable {
pub(crate) fn new() -> Self {
Self {
inner: InnerInodeTable::new().into(),
}
}
fn access_write<R, F: FnOnce(&mut InnerInodeTable) -> R>(&self, cb: F) -> R {
let mut this = self.inner.write();
cb(&mut this)
}
fn access_read<R, F: FnOnce(&InnerInodeTable) -> R>(&self, cb: F) -> R {
let this = self.inner.read();
cb(&this)
}
}
impl Default for InodeTable {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
struct InnerInodeTable {
table: Vec<InodeEntry>,
free_list: VecDeque<usize>,
children: HashMap<child_key::ChildKey, usize>,
folder_list: BTreeMap<Arc<PathBuf>, usize>,
}
impl InnerInodeTable {
fn new() -> Self {
let root = root_path();
let mut folder_list = BTreeMap::new();
folder_list.insert(root.clone(), 0);
Self {
table: vec![InodeEntry {
entry: Entry::Root(RootEntry {
path: root,
name: empty_name(),
}),
linked: true,
child_count: 0,
lookups: 0,
generation: 0,
}],
free_list: VecDeque::new(),
children: HashMap::new(),
folder_list,
}
}
fn child_key(parent: Inode, name: Arc<OsStr>) -> child_key::ChildKey {
child_key::ChildKey::new(parent, name)
}
fn child_key_ref<'a>(parent: Inode, name: &'a OsStr) -> child_key::ChildKeyRef<'a> {
child_key::ChildKeyRef::new(parent, name)
}
fn inode_for_idx(idx: usize) -> Inode {
(idx + 1) as Inode
}
fn get_entry(&self, inode: Inode) -> Option<&InodeEntry> {
let idx = inode.checked_sub(1)? as usize;
let entry = self.table.get(idx)?;
match entry.entry {
Entry::Vacant => None,
_ => Some(entry),
}
}
fn live_dir_path(&self, inode: Inode) -> Option<Arc<PathBuf>> {
let idx = (inode as usize).checked_sub(1)?;
match &self.table[idx].entry {
Entry::Root(root_entry) => Some(root_entry.path.clone()),
Entry::Folder(folder) if self.table[idx].linked => Some(folder.path()),
_ => {
error!("inode {inode} is not a live directory!");
None
}
}
}
fn get_inode_entry<'a>(
free_list: &mut VecDeque<usize>,
table: &'a mut Vec<InodeEntry>,
) -> (Inode, &'a mut InodeEntry) {
let idx = match free_list.pop_front() {
Some(idx) => {
debug!("re-using inode {}", idx + 1);
table[idx].generation += 1;
idx
}
None => {
table.push(InodeEntry {
entry: Entry::Vacant,
linked: false,
child_count: 0,
lookups: 0,
generation: 0,
});
table.len() - 1
}
};
((idx + 1) as Inode, &mut table[idx])
}
fn add_child(
&mut self,
parent: Inode,
name: &OsStr,
is_dir: bool,
initial_lookups: LookupCount,
allow_existing: bool,
) -> Option<(bool, Inode, Generation)> {
let key = Self::child_key_ref(parent, name);
if let Some(idx) = self.children.get(&key).copied() {
let entry = &self.table[idx].entry;
match (entry, is_dir) {
(Entry::Folder(_), true) | (Entry::Leaf(_), false) => {}
_ => {
error!(
"inode type mismatch for parent {parent} and child {:?}",
name
);
return None;
}
}
if allow_existing {
return Some((false, Self::inode_for_idx(idx), self.table[idx].generation));
}
error!(
"attempted to insert duplicate child under inode {parent}: {:?}",
name
);
return None;
}
let parent_path = self.live_dir_path(parent)?;
self.table[parent as usize - 1].child_count += 1;
let child_name: Arc<OsStr> = Arc::from(name);
let (inode, generation, folder_path) = {
let (inode, entry) = Self::get_inode_entry(&mut self.free_list, &mut self.table);
entry.entry = if is_dir {
Entry::Folder(FolderEntry::with_parent(
parent,
parent_path.clone(),
child_name.clone(),
))
} else {
Entry::Leaf(LeafEntry {
parent,
name: child_name.clone(),
})
};
entry.linked = true;
entry.child_count = 0;
entry.lookups = initial_lookups;
let folder_path = match &entry.entry {
Entry::Folder(folder) => Some(folder.path()),
_ => None,
};
(inode, entry.generation, folder_path)
};
let idx = inode as usize - 1;
self.children
.insert(Self::child_key(parent, child_name), idx);
if let Some(path) = folder_path {
self.folder_list.insert(path, idx);
}
Some((true, inode, generation))
}
fn remove_live_indexes(&mut self, idx: usize) {
if !self.table[idx].linked {
return;
}
let (child_key, folder_path) = match &self.table[idx].entry {
Entry::Folder(folder) => (
Some(Self::child_key_ref(folder.parent, folder.name())),
Some(folder.path()),
),
Entry::Leaf(leaf) => (Some(Self::child_key_ref(leaf.parent, &leaf.name)), None),
_ => (None, None),
};
if let Some(key) = child_key {
self.children.remove(&key);
}
if let Some(path) = folder_path {
self.folder_list.remove(&path);
}
self.table[idx].linked = false;
}
fn maybe_free_inode(&mut self, idx: usize) {
if idx == 0 {
return;
}
let (parent_inode, linked, child_key, folder_path) = {
let entry = &self.table[idx];
if entry.lookups != 0 || entry.child_count != 0 {
return;
}
match &entry.entry {
Entry::Folder(folder) => (
folder.parent,
entry.linked,
Some(Self::child_key_ref(folder.parent, folder.name())),
Some(folder.path()),
),
Entry::Leaf(leaf) => (
leaf.parent,
entry.linked,
Some(Self::child_key_ref(leaf.parent, &leaf.name)),
None,
),
_ => return,
}
};
if linked {
if let Some(key) = child_key {
self.children.remove(&key);
}
if let Some(path) = folder_path.as_ref() {
self.folder_list.remove(path);
}
}
self.table[idx].entry = Entry::Vacant;
self.table[idx].linked = false;
self.table[idx].child_count = 0;
self.table[idx].lookups = 0;
self.free_list.push_back(idx);
let parent_idx = parent_inode as usize - 1;
self.table[parent_idx].child_count -= 1;
self.maybe_free_inode(parent_idx);
}
fn rename_folder_subtree(
&mut self,
old_path: Arc<PathBuf>,
new_path: Arc<PathBuf>,
) -> Option<()> {
let mut suffix = self.folder_list.split_off(&old_path);
if let Some(right_start) = suffix
.keys()
.find(|path| !path.as_path().starts_with(old_path.as_path()))
.cloned()
{
let mut right = suffix.split_off(&right_start);
self.folder_list.append(&mut right);
}
for (path, idx) in suffix {
let next_path = if path.as_path() == old_path.as_path() {
new_path.clone()
} else {
let suffix = path.strip_prefix(old_path.as_path()).ok()?;
Arc::new(new_path.join(suffix))
};
match &mut self.table[idx].entry {
Entry::Folder(folder) => folder.set_path(next_path.clone())?,
_ => {
error!("folder_list contains a non-folder entry!");
return None;
}
}
self.folder_list.insert(next_path, idx);
}
Some(())
}
}
impl InodeToPath for InodeTable {
fn add_leaf(&self, parent: Inode, name: &OsStr) -> Option<(Inode, Generation)> {
self.access_write(|inner| {
inner
.add_child(parent, name, false, 1, false)
.map(|(_, inode, generation)| (inode, generation))
})
}
fn add_dir(&self, parent: Inode, name: &OsStr) -> Option<(Inode, Generation)> {
self.access_write(|inner| {
inner
.add_child(parent, name, true, 1, false)
.map(|(_, inode, generation)| (inode, generation))
})
}
fn add_or_get_leaf(&self, parent: Inode, name: &OsStr) -> Option<(Inode, Generation)> {
self.access_write(|inner| {
inner
.add_child(parent, name, false, 0, true)
.map(|(_, inode, generation)| (inode, generation))
})
}
fn create_or_get_leaf(&self, parent: Inode, name: &OsStr) -> Option<(bool, Inode, Generation)> {
self.access_write(|inner| inner.add_child(parent, name, false, 1, true))
}
fn add_or_get_dir(&self, parent: Inode, name: &OsStr) -> Option<(Inode, Generation)> {
self.access_write(|inner| {
inner
.add_child(parent, name, true, 0, true)
.map(|(_, inode, generation)| (inode, generation))
})
}
fn forget(&self, inode: Inode, n: LookupCount) -> LookupCount {
if inode == 1 {
return 1;
}
self.access_write(|inner| {
let idx = inode as usize - 1;
let entry = &mut inner.table[idx];
assert!(!matches!(entry.entry, Entry::Vacant));
assert!(n <= entry.lookups);
entry.lookups -= n;
let lookups = entry.lookups;
inner.maybe_free_inode(idx);
lookups
})
}
fn get_path(&self, inode: Inode) -> Option<EntryName> {
self.access_read(|inner| {
inner
.get_entry(inode)?
.path(&inner.table)
.map(|val| EntryName::new(val.0.into(), val.1))
})
}
fn get_folder_path(&self, inode: Inode) -> Option<FolderPath> {
self.access_read(|inner| match &inner.get_entry(inode)?.entry {
Entry::Root(root_entry) => Some(root_entry.path.clone().into()),
Entry::Folder(folder) => Some(folder.path().into()),
_ => None,
})
}
fn get_parent_inode(&self, ino: Inode) -> Option<Inode> {
self.access_read(|inner| match &inner.get_entry(ino)?.entry {
Entry::Folder(folder) => Some(folder.parent),
Entry::Leaf(leaf) => Some(leaf.parent),
_ => None,
})
}
fn lookup(&self, inode: Inode) {
if inode == 1 {
return;
}
self.access_write(|inner| {
let entry = &mut inner.table[inode as usize - 1];
assert!(!matches!(entry.entry, Entry::Vacant));
entry.lookups += 1;
})
}
fn rename(
&self,
oldparent: Inode,
oldname: &OsStr,
newparent: Inode,
newname: &OsStr,
) -> Option<()> {
if oldparent == newparent && oldname == newname {
return Some(());
}
let old_key = InnerInodeTable::child_key_ref(oldparent, oldname);
self.access_write(|inner| {
let idx = *inner.children.get(&old_key)?;
let new_parent_path = inner.live_dir_path(newparent)?;
let old_folder_path = match &inner.table[idx].entry {
Entry::Folder(folder) => Some(folder.path()),
Entry::Leaf(_) => None,
_ => return None, };
let new_key = InnerInodeTable::child_key_ref(newparent, newname);
let replaced_idx = inner.children.get(&new_key).copied();
if let Some(replaced_idx) = replaced_idx {
inner.remove_live_indexes(replaced_idx);
inner.maybe_free_inode(replaced_idx);
}
inner.children.remove(&old_key);
let newname: Arc<OsStr> = Arc::from(newname);
inner
.children
.insert(InnerInodeTable::child_key(newparent, newname.clone()), idx);
if oldparent != newparent {
inner.table[oldparent as usize - 1].child_count -= 1;
inner.table[newparent as usize - 1].child_count += 1;
}
let mut moved_folder = None;
match &mut inner.table[idx].entry {
Entry::Folder(folder) => {
let new_path = Arc::new(new_parent_path.join(newname.as_ref()));
folder.parent = newparent;
folder.set_path(new_path.clone());
moved_folder = Some((old_folder_path?, new_path));
}
Entry::Leaf(leaf) => {
leaf.parent = newparent;
leaf.name = newname;
}
_ => return None, }
if let Some((old_path, new_path)) = moved_folder {
inner.rename_folder_subtree(old_path, new_path)?;
}
inner.table[idx].linked = true;
Some(())
})
}
fn unlink(&self, parent: Inode, name: &OsStr) {
let key = InnerInodeTable::child_key_ref(parent, name);
self.access_write(|inner| {
if let Some(idx) = inner.children.remove(&key) {
if let Entry::Folder(folder) = &inner.table[idx].entry {
inner.folder_list.remove(&folder.path);
}
inner.table[idx].linked = false;
inner.maybe_free_inode(idx);
}
})
}
}
#[cfg(test)]
mod tests {
use super::InodeToPath;
use super::{Inode, InodeTable};
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
fn name(value: &'static str) -> &'static OsStr {
OsStr::new(value)
}
fn assert_path(table: &InodeTable, inode: Inode, expected: &str) {
assert_eq!(
PathBuf::from(expected),
table.get_path(inode).unwrap().full_path()
);
}
fn assert_folder_path(table: &InodeTable, inode: Inode, expected: &str) {
let path = table.get_folder_path(inode).unwrap();
assert_eq!(Path::new(expected), path.as_path());
}
fn assert_no_path(table: &InodeTable, inode: Inode) {
assert!(table.get_path(inode).is_none());
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ModelKind {
Dir,
Leaf,
}
#[derive(Clone, Debug)]
struct ModelNode {
parent: Inode,
name: &'static str,
kind: ModelKind,
live: bool,
}
#[derive(Debug)]
struct Model {
nodes: HashMap<Inode, ModelNode>,
}
impl Model {
fn new() -> Self {
let mut nodes = HashMap::new();
nodes.insert(
1,
ModelNode {
parent: 1,
name: "",
kind: ModelKind::Dir,
live: true,
},
);
Self { nodes }
}
fn add(&mut self, inode: Inode, parent: Inode, name: &'static str, kind: ModelKind) {
assert_eq!(
Some(ModelKind::Dir),
self.nodes.get(&parent).map(|node| node.kind)
);
assert!(self.live_child(parent, name).is_none());
assert!(
self.nodes
.insert(
inode,
ModelNode {
parent,
name,
kind,
live: true,
},
)
.is_none()
);
}
fn live_child(&self, parent: Inode, name: &str) -> Option<Inode> {
self.nodes.iter().find_map(|(inode, node)| {
(node.live && node.parent == parent && node.name == name).then_some(*inode)
})
}
fn rename(
&mut self,
oldparent: Inode,
oldname: &str,
newparent: Inode,
newname: &'static str,
) {
let moved = self.live_child(oldparent, oldname).unwrap();
if let Some(replaced) = self.live_child(newparent, newname) {
self.nodes.get_mut(&replaced).unwrap().live = false;
}
let moved = self.nodes.get_mut(&moved).unwrap();
moved.parent = newparent;
moved.name = newname;
moved.live = true;
}
fn unlink(&mut self, parent: Inode, name: &str) {
let removed = self.live_child(parent, name).unwrap();
self.nodes.get_mut(&removed).unwrap().live = false;
}
fn path(&self, inode: Inode) -> PathBuf {
if inode == 1 {
return PathBuf::from("/");
}
let node = &self.nodes[&inode];
self.path(node.parent).join(node.name)
}
fn assert_paths_match(&self, table: &InodeTable) {
for (&inode, node) in &self.nodes {
assert_eq!(self.path(inode), table.get_path(inode).unwrap().full_path());
if node.kind == ModelKind::Dir {
let folder_path = table
.get_folder_path(inode)
.map(|path| path.as_path().to_path_buf());
assert_eq!(Some(self.path(inode)), folder_path);
} else {
assert!(table.get_folder_path(inode).is_none());
}
}
}
}
#[test]
fn inode_numbers_are_reused_after_forget() {
let table = InodeTable::new();
let (inode1, generation1) = table.add_leaf(1, name("a")).unwrap();
assert_ne!(1, inode1);
assert_eq!(0, generation1);
assert_path(&table, inode1, "/a");
let (inode2, _) = table.add_leaf(1, name("b")).unwrap();
assert_ne!(inode1, inode2);
assert_ne!(1, inode2);
assert_path(&table, inode2, "/b");
assert_eq!(0, table.forget(inode1, 1));
assert!(table.get_path(inode1).is_none());
let (inode3, generation3) = table.add_leaf(1, name("c")).unwrap();
assert_eq!(inode1, inode3);
assert_eq!(generation1 + 1, generation3);
assert_path(&table, inode3, "/c");
}
#[test]
fn add_or_get_returns_existing_inode_without_lookup() {
let table = InodeTable::new();
let (inode1, generation1) = table.add_or_get_leaf(1, name("a")).unwrap();
assert_eq!(0, generation1);
assert_path(&table, inode1, "/a");
table.lookup(inode1);
let (inode2, generation2) = table.add_leaf(1, name("b")).unwrap();
assert_path(&table, inode2, "/b");
let (inode2_again, generation2_again) = table.add_or_get_leaf(1, name("b")).unwrap();
assert_eq!(inode2, inode2_again);
assert_eq!(generation2, generation2_again);
table.lookup(inode2);
assert_eq!(0, table.forget(inode1, 1));
assert_eq!(1, table.forget(inode2, 1));
}
#[test]
fn create_or_get_leaf_creates_with_lookup_and_reports_existing_without_lookup() {
let table = InodeTable::new();
let (created, inode, generation) = table.create_or_get_leaf(1, name("a")).unwrap();
assert!(created);
assert_eq!(0, generation);
assert_path(&table, inode, "/a");
assert_eq!(0, table.forget(inode, 1));
assert_no_path(&table, inode);
let (existing, existing_generation) = table.add_leaf(1, name("b")).unwrap();
let (created_again, same, same_generation) =
table.create_or_get_leaf(1, name("b")).unwrap();
assert!(!created_again);
assert_eq!(existing, same);
assert_eq!(existing_generation, same_generation);
assert_eq!(0, table.forget(existing, 1));
}
#[test]
fn rename_leaf_keeps_inode_and_updates_live_name() {
let table = InodeTable::new();
let (inode, generation) = table.add_leaf(1, name("a")).unwrap();
assert_path(&table, inode, "/a");
assert_eq!(Some(()), table.rename(1, name("a"), 1, name("b")));
assert_path(&table, inode, "/b");
assert_eq!(None, table.rename(1, name("a"), 1, name("c")));
let (inode_again, generation_again) = table.add_or_get_leaf(1, name("b")).unwrap();
assert_eq!(inode, inode_again);
assert_eq!(generation, generation_again);
}
#[test]
fn unlink_removes_live_name_but_keeps_open_inode_path() {
let table = InodeTable::new();
let (inode, _) = table.add_leaf(1, name("bar")).unwrap();
table.unlink(1, name("bar"));
assert_path(&table, inode, "/bar");
let (new_inode, _) = table.add_or_get_leaf(1, name("bar")).unwrap();
assert_ne!(inode, new_inode);
assert_eq!(0, table.forget(inode, 1));
assert!(table.get_path(inode).is_none());
assert_path(&table, new_inode, "/bar");
}
#[test]
fn folders_report_paths_and_parent_inodes() {
let table = InodeTable::new();
let (dir, _) = table.add_dir(1, name("dir")).unwrap();
let (child, _) = table.add_leaf(dir, name("child")).unwrap();
assert_path(&table, dir, "/dir");
assert_folder_path(&table, dir, "/dir");
assert_eq!(Some(1), table.get_parent_inode(dir));
assert_path(&table, child, "/dir/child");
assert!(table.get_folder_path(child).is_none());
assert_eq!(Some(dir), table.get_parent_inode(child));
}
#[test]
fn duplicate_names_and_wrong_parent_kinds_are_rejected() {
let table = InodeTable::new();
let (dir, dir_generation) = table.add_dir(1, name("dir")).unwrap();
let (leaf, leaf_generation) = table.add_leaf(dir, name("leaf")).unwrap();
assert!(table.add_dir(1, name("dir")).is_none());
assert!(table.add_leaf(1, name("dir")).is_none());
assert_eq!(
Some((dir, dir_generation)),
table.add_or_get_dir(1, name("dir"))
);
assert!(table.add_or_get_leaf(1, name("dir")).is_none());
assert!(table.add_leaf(dir, name("leaf")).is_none());
assert!(table.add_dir(dir, name("leaf")).is_none());
assert_eq!(
Some((leaf, leaf_generation)),
table.add_or_get_leaf(dir, name("leaf"))
);
assert!(table.add_or_get_dir(dir, name("leaf")).is_none());
assert!(table.add_leaf(leaf, name("child")).is_none());
assert!(table.add_dir(leaf, name("child")).is_none());
}
#[test]
fn moving_leaf_between_directories_updates_live_binding() {
let table = InodeTable::new();
let (left, _) = table.add_dir(1, name("left")).unwrap();
let (right, _) = table.add_dir(1, name("right")).unwrap();
let (file, generation) = table.add_leaf(left, name("file")).unwrap();
assert_eq!(
Some(()),
table.rename(left, name("file"), right, name("file"))
);
assert_path(&table, file, "/right/file");
assert_eq!(Some(right), table.get_parent_inode(file));
let (again, again_generation) = table.add_or_get_leaf(right, name("file")).unwrap();
assert_eq!(file, again);
assert_eq!(generation, again_generation);
let (new_left_file, _) = table.add_or_get_leaf(left, name("file")).unwrap();
assert_ne!(file, new_left_file);
assert_path(&table, new_left_file, "/left/file");
}
#[test]
fn unlinking_directory_with_open_descendants_keeps_paths_until_forget_chain() {
let table = InodeTable::new();
let (dir, _) = table.add_dir(1, name("dir")).unwrap();
let (child_dir, _) = table.add_dir(dir, name("child")).unwrap();
let (leaf, _) = table.add_leaf(child_dir, name("leaf")).unwrap();
table.unlink(1, name("dir"));
assert_path(&table, dir, "/dir");
assert_path(&table, child_dir, "/dir/child");
assert_path(&table, leaf, "/dir/child/leaf");
let (new_dir, _) = table.add_or_get_dir(1, name("dir")).unwrap();
assert_ne!(dir, new_dir);
assert_eq!(0, table.forget(dir, 1));
assert_path(&table, dir, "/dir");
assert_eq!(0, table.forget(child_dir, 1));
assert_path(&table, child_dir, "/dir/child");
assert_eq!(0, table.forget(leaf, 1));
assert_no_path(&table, dir);
assert_no_path(&table, child_dir);
assert_no_path(&table, leaf);
assert_path(&table, new_dir, "/dir");
}
#[test]
fn scripted_model_matches_inode_table_paths() {
let table = InodeTable::new();
let mut model = Model::new();
let (a, _) = table.add_dir(1, name("a")).unwrap();
model.add(a, 1, "a", ModelKind::Dir);
let (b, _) = table.add_dir(1, name("b")).unwrap();
model.add(b, 1, "b", ModelKind::Dir);
let (a_file, _) = table.add_leaf(a, name("file")).unwrap();
model.add(a_file, a, "file", ModelKind::Leaf);
let (sub, _) = table.add_dir(a, name("sub")).unwrap();
model.add(sub, a, "sub", ModelKind::Dir);
let (deep, _) = table.add_leaf(sub, name("deep")).unwrap();
model.add(deep, sub, "deep", ModelKind::Leaf);
let (b_file, _) = table.add_leaf(b, name("file")).unwrap();
model.add(b_file, b, "file", ModelKind::Leaf);
assert_eq!(Some(()), table.rename(a, name("sub"), b, name("moved")));
model.rename(a, "sub", b, "moved");
assert_eq!(Some(()), table.rename(b, name("file"), a, name("file")));
model.rename(b, "file", a, "file");
table.unlink(b, name("moved"));
model.unlink(b, "moved");
model.assert_paths_match(&table);
assert_path(&table, a_file, "/a/file");
assert_path(&table, b_file, "/a/file");
assert_path(&table, deep, "/b/moved/deep");
let (live_a_file, _) = table.add_or_get_leaf(a, name("file")).unwrap();
assert_eq!(b_file, live_a_file);
let (new_moved, _) = table.add_or_get_dir(b, name("moved")).unwrap();
assert_ne!(sub, new_moved);
assert_path(&table, new_moved, "/b/moved");
}
#[test]
fn forget_keeps_parent_directory_until_children_are_gone() {
let table = InodeTable::new();
let (dir, _) = table.add_dir(1, name("dir")).unwrap();
let (child, _) = table.add_leaf(dir, name("child")).unwrap();
assert_eq!(0, table.forget(dir, 1));
assert_path(&table, dir, "/dir");
assert_path(&table, child, "/dir/child");
assert_eq!(0, table.forget(child, 1));
assert!(table.get_path(child).is_none());
assert!(table.get_path(dir).is_none());
}
#[test]
fn renaming_parent_directory_updates_descendant_paths() {
let table = InodeTable::new();
let (parent, parent_generation) = table.add_dir(1, name("parent")).unwrap();
let (child_dir, _) = table.add_dir(parent, name("child")).unwrap();
let (grandchild_dir, _) = table.add_dir(child_dir, name("grandchild")).unwrap();
let (leaf, _) = table.add_leaf(grandchild_dir, name("leaf")).unwrap();
assert_eq!(
Some(()),
table.rename(1, name("parent"), 1, name("renamed"))
);
assert_path(&table, parent, "/renamed");
assert_folder_path(&table, parent, "/renamed");
assert_path(&table, child_dir, "/renamed/child");
assert_folder_path(&table, child_dir, "/renamed/child");
assert_path(&table, grandchild_dir, "/renamed/child/grandchild");
assert_folder_path(&table, grandchild_dir, "/renamed/child/grandchild");
assert_path(&table, leaf, "/renamed/child/grandchild/leaf");
assert_eq!(Some(parent), table.get_parent_inode(child_dir));
assert_eq!(Some(grandchild_dir), table.get_parent_inode(leaf));
assert_eq!(None, table.rename(1, name("parent"), 1, name("again")));
let (parent_again, generation_again) = table.add_or_get_dir(1, name("renamed")).unwrap();
assert_eq!(parent, parent_again);
assert_eq!(parent_generation, generation_again);
}
#[test]
fn moving_directory_to_another_parent_updates_descendant_paths() {
let table = InodeTable::new();
let (source, _) = table.add_dir(1, name("source")).unwrap();
let (destination, _) = table.add_dir(1, name("destination")).unwrap();
let (child_dir, child_generation) = table.add_dir(source, name("child")).unwrap();
let (leaf, _) = table.add_leaf(child_dir, name("leaf")).unwrap();
assert_eq!(
Some(()),
table.rename(source, name("child"), destination, name("moved"))
);
assert_path(&table, child_dir, "/destination/moved");
assert_folder_path(&table, child_dir, "/destination/moved");
assert_path(&table, leaf, "/destination/moved/leaf");
assert_eq!(Some(destination), table.get_parent_inode(child_dir));
assert_eq!(Some(child_dir), table.get_parent_inode(leaf));
let (child_again, generation_again) =
table.add_or_get_dir(destination, name("moved")).unwrap();
assert_eq!(child_dir, child_again);
assert_eq!(child_generation, generation_again);
}
#[test]
fn renaming_directory_does_not_move_path_prefix_siblings() {
let table = InodeTable::new();
let (foo, _) = table.add_dir(1, name("foo")).unwrap();
let (foo_child, _) = table.add_dir(foo, name("child")).unwrap();
let (foo_file, _) = table.add_leaf(foo, name("file")).unwrap();
let (fo, _) = table.add_dir(1, name("fo")).unwrap();
let (foo2, foo2_generation) = table.add_dir(1, name("foo2")).unwrap();
let (foo_bar, _) = table.add_dir(1, name("foo_bar")).unwrap();
assert_eq!(Some(()), table.rename(1, name("foo"), 1, name("bar")));
assert_path(&table, foo, "/bar");
assert_path(&table, foo_child, "/bar/child");
assert_path(&table, foo_file, "/bar/file");
assert_path(&table, fo, "/fo");
assert_path(&table, foo2, "/foo2");
assert_folder_path(&table, foo2, "/foo2");
assert_path(&table, foo_bar, "/foo_bar");
assert_folder_path(&table, foo_bar, "/foo_bar");
let (foo2_again, foo2_generation_again) = table.add_or_get_dir(1, name("foo2")).unwrap();
assert_eq!(foo2, foo2_again);
assert_eq!(foo2_generation, foo2_generation_again);
}
#[test]
fn rename_over_existing_leaf_keeps_new_name_bound_to_moved_inode() {
let table = InodeTable::new();
let (moved, moved_generation) = table.add_leaf(1, name("a")).unwrap();
let (replaced, _) = table.add_leaf(1, name("b")).unwrap();
assert_eq!(Some(()), table.rename(1, name("a"), 1, name("b")));
assert_path(&table, moved, "/b");
assert_path(&table, replaced, "/b");
let (live_inode, live_generation) = table.add_or_get_leaf(1, name("b")).unwrap();
assert_eq!(moved, live_inode);
assert_eq!(moved_generation, live_generation);
assert_eq!(0, table.forget(replaced, 1));
assert!(table.get_path(replaced).is_none());
}
}