use fuser::INodeNo;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::{Component, Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum InodeMode {
Virtual,
Persistent,
}
struct InodeEntry {
path: PathBuf,
lookup_count: u64,
}
struct InodeTable {
map: HashMap<INodeNo, InodeEntry>,
paths: HashMap<PathBuf, INodeNo>,
}
pub struct InodeStore {
table: Mutex<InodeTable>,
mode: InodeMode,
next_ino: AtomicU64,
}
impl InodeStore {
pub fn new(mode: InodeMode) -> Self {
let mut map = HashMap::new();
let mut paths = HashMap::new();
let root_ino = INodeNo(1);
let root_path = PathBuf::from("");
map.insert(
root_ino,
InodeEntry {
path: root_path.clone(),
lookup_count: 1,
},
);
paths.insert(root_path, root_ino);
Self {
table: Mutex::new(InodeTable { map, paths }),
mode,
next_ino: AtomicU64::new(2),
}
}
pub fn get_path(&self, ino: INodeNo) -> Option<PathBuf> {
self.table
.lock()
.ok()?
.map
.get(&ino)
.map(|e| e.path.clone())
}
pub fn peek_ino(&self, rel_path: &Path) -> INodeNo {
let mut t = self.table.lock().unwrap();
if let Some(&ino) = t.paths.get(rel_path) {
return ino;
}
let new_ino = match self.mode {
InodeMode::Virtual => INodeNo(self.next_ino.fetch_add(1, Ordering::Relaxed)),
InodeMode::Persistent => self.generate_persistent_ino(&t, rel_path),
};
let path_buf = rel_path.to_path_buf();
t.paths.insert(path_buf.clone(), new_ino);
t.map.insert(
new_ino,
InodeEntry {
path: path_buf,
lookup_count: 0,
},
);
new_ino
}
pub fn get_ino(&self, rel_path: &Path) -> INodeNo {
let mut t = self.table.lock().unwrap();
if let Some(&ino) = t.paths.get(rel_path) {
if let Some(entry) = t.map.get_mut(&ino) {
entry.lookup_count += 1;
}
return ino;
}
let new_ino = match self.mode {
InodeMode::Virtual => INodeNo(self.next_ino.fetch_add(1, Ordering::Relaxed)),
InodeMode::Persistent => self.generate_persistent_ino(&t, rel_path),
};
let path_buf = rel_path.to_path_buf();
t.paths.insert(path_buf.clone(), new_ino);
t.map.insert(
new_ino,
InodeEntry {
path: path_buf,
lookup_count: 1,
},
);
new_ino
}
fn generate_persistent_ino(&self, t: &InodeTable, path: &Path) -> INodeNo {
let mut hash: u64 = 0xcbf29ce484222325;
for byte in path.as_os_str().as_encoded_bytes() {
hash ^= *byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
let base = if hash < 100 { hash + 100 } else { hash };
let mut candidate = INodeNo(base);
loop {
match t.map.get(&candidate) {
None => break,
Some(existing) if existing.path == path => break,
_ => candidate = INodeNo(candidate.0.wrapping_add(1)),
}
}
candidate
}
pub fn forget_ino(&self, ino: INodeNo, nlookup: u64) {
if ino.0 == 1 {
return;
}
let mut t = self.table.lock().unwrap();
if let Some(entry) = t.map.get_mut(&ino) {
if entry.lookup_count <= nlookup {
let path = entry.path.clone();
t.map.remove(&ino);
t.paths.remove(&path);
} else {
entry.lookup_count -= nlookup;
}
}
}
pub fn remove_ino(&self, rel_path: &Path) {
let mut t = self.table.lock().unwrap();
if let Some(ino) = t.paths.remove(rel_path) {
t.map.remove(&ino);
}
}
pub fn remove_subtree(&self, rel_path: &Path) {
let prefix = rel_path.to_path_buf();
let mut t = self.table.lock().unwrap();
let to_remove: Vec<PathBuf> = t
.paths
.keys()
.filter(|p| {
*p == &prefix
|| p.strip_prefix(&prefix)
.ok()
.map(|rest| rest.components().next().is_some())
.unwrap_or(false)
})
.cloned()
.collect();
for p in to_remove {
if let Some(ino) = t.paths.remove(&p) {
t.map.remove(&ino);
}
}
}
pub fn child_path(&self, parent: INodeNo, name: &OsStr) -> PathBuf {
let parent_path = self.get_path(parent).unwrap_or_default();
let name_path = Path::new(name);
let mut comps = name_path.components();
match (comps.next(), comps.next()) {
(Some(Component::Normal(s)), None) => parent_path.join(s),
_ => parent_path,
}
}
}