Skip to main content

hashtree_fuse/
lib.rs

1use std::collections::{HashMap, HashSet};
2use std::hash::{Hash as StdHash, Hasher};
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::{Arc, Mutex, RwLock};
5
6use futures::executor::block_on;
7use hashtree_core::{
8    sha256, to_hex, Cid, HashTree, HashTreeConfig, HashTreeError, LinkType, Store,
9};
10use thiserror::Error;
11
12pub const ROOT_INODE: u64 = 1;
13// Virtual entry used only to wake Linux directory monitors after remote root updates.
14pub const DIRECTORY_REFRESH_SENTINEL_NAME: &str = "iris-drive-refresh";
15const WHOLE_FILE_HASH_META_KEY: &str = "whole_file_hash";
16
17#[derive(Debug, Error)]
18pub enum FsError {
19    #[error("root hash is not a directory")]
20    InvalidRoot,
21    #[error("entry not found")]
22    NotFound,
23    #[error("not a directory")]
24    NotDir,
25    #[error("is a directory")]
26    IsDir,
27    #[error("entry already exists")]
28    AlreadyExists,
29    #[error("directory not empty")]
30    NotEmpty,
31    #[error("invalid entry name")]
32    InvalidName,
33    #[error("tree error: {0}")]
34    Tree(String),
35    #[error("publish error: {0}")]
36    Publish(String),
37}
38
39impl From<HashTreeError> for FsError {
40    fn from(err: HashTreeError) -> Self {
41        FsError::Tree(err.to_string())
42    }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum EntryKind {
47    File,
48    Directory,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct EntryAttr {
53    pub inode: u64,
54    pub size: u64,
55    pub kind: EntryKind,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct DirEntry {
60    pub inode: u64,
61    pub name: String,
62    pub kind: EntryKind,
63}
64
65pub trait RootPublisher: Send + Sync {
66    fn publish(&self, cid: &Cid) -> Result<(), FsError>;
67}
68
69#[derive(Debug, Clone, Eq)]
70struct ChildKey {
71    parent: u64,
72    name: String,
73}
74
75impl PartialEq for ChildKey {
76    fn eq(&self, other: &Self) -> bool {
77        self.parent == other.parent && self.name == other.name
78    }
79}
80
81impl StdHash for ChildKey {
82    fn hash<H: Hasher>(&self, state: &mut H) {
83        self.parent.hash(state);
84        self.name.hash(state);
85    }
86}
87
88#[derive(Debug, Clone, PartialEq)]
89struct ResolvedEntry {
90    cid: Cid,
91    link_type: LinkType,
92    size: u64,
93    meta: Option<HashMap<String, serde_json::Value>>,
94}
95
96#[cfg(feature = "fuse")]
97#[derive(Debug, Clone, PartialEq, Eq)]
98enum FuseInvalidation {
99    Entry {
100        parent: u64,
101        name: String,
102    },
103    Delete {
104        parent: u64,
105        child: u64,
106        name: String,
107    },
108}
109
110pub struct HashtreeFuseInner<S: Store> {
111    tree: HashTree<S>,
112    root: RwLock<Cid>,
113    paths: RwLock<HashMap<u64, Vec<String>>>,
114    children: RwLock<HashMap<ChildKey, u64>>,
115    parents: RwLock<HashMap<u64, u64>>,
116    next_inode: AtomicU64,
117    publisher: Option<Arc<dyn RootPublisher>>,
118    refresh_unlinks: Mutex<HashSet<Vec<String>>>,
119    modify_lock: Mutex<()>,
120    #[cfg(feature = "fuse")]
121    notifier: Mutex<Option<fuser::Notifier>>,
122}
123
124pub struct HashtreeFuse<S: Store> {
125    inner: Arc<HashtreeFuseInner<S>>,
126}
127
128impl<S: Store> Clone for HashtreeFuse<S> {
129    fn clone(&self) -> Self {
130        Self {
131            inner: self.inner.clone(),
132        }
133    }
134}
135
136impl<S: Store> std::ops::Deref for HashtreeFuse<S> {
137    type Target = HashtreeFuseInner<S>;
138
139    fn deref(&self) -> &Self::Target {
140        &self.inner
141    }
142}
143
144impl<S: Store> HashtreeFuse<S> {
145    pub fn new(store: Arc<S>, root: Cid) -> Result<Self, FsError> {
146        Self::new_with_publisher(store, root, None)
147    }
148
149    pub fn new_with_publisher(
150        store: Arc<S>,
151        root: Cid,
152        publisher: Option<Arc<dyn RootPublisher>>,
153    ) -> Result<Self, FsError> {
154        let mut config = HashTreeConfig::new(store);
155        if root.key.is_none() {
156            config = config.public();
157        }
158        let tree = HashTree::new(config);
159
160        let is_dir = block_on(tree.get_directory_node(&root))?.is_some();
161        if !is_dir {
162            return Err(FsError::InvalidRoot);
163        }
164
165        let mut paths = HashMap::new();
166        paths.insert(ROOT_INODE, Vec::new());
167        let mut parents = HashMap::new();
168        parents.insert(ROOT_INODE, ROOT_INODE);
169
170        Ok(Self {
171            inner: Arc::new(HashtreeFuseInner {
172                tree,
173                root: RwLock::new(root),
174                paths: RwLock::new(paths),
175                children: RwLock::new(HashMap::new()),
176                parents: RwLock::new(parents),
177                next_inode: AtomicU64::new(ROOT_INODE + 1),
178                publisher,
179                refresh_unlinks: Mutex::new(HashSet::new()),
180                modify_lock: Mutex::new(()),
181                #[cfg(feature = "fuse")]
182                notifier: Mutex::new(None),
183            }),
184        })
185    }
186
187    pub fn current_root(&self) -> Cid {
188        self.root.read().unwrap().clone()
189    }
190
191    pub fn replace_root(&self, root: Cid) -> Result<(), FsError> {
192        let _guard = self.modify_lock.lock().unwrap();
193        self.replace_root_locked(root)
194    }
195
196    pub fn replace_root_if_current(
197        &self,
198        expected_current: &Cid,
199        root: Cid,
200    ) -> Result<bool, FsError> {
201        let _guard = self.modify_lock.lock().unwrap();
202        if self.current_root() != *expected_current {
203            return Ok(false);
204        }
205        self.replace_root_locked(root)?;
206        Ok(true)
207    }
208
209    fn replace_root_locked(&self, root: Cid) -> Result<(), FsError> {
210        let is_dir = block_on(self.tree.get_directory_node(&root))?.is_some();
211        if !is_dir {
212            return Err(FsError::InvalidRoot);
213        }
214
215        #[cfg(feature = "fuse")]
216        let invalidations = self.changed_known_entries_for_root(&root);
217        #[cfg(feature = "fuse")]
218        let paths_needing_new_inode = self.changed_known_paths_requiring_new_inode_for_root(&root);
219
220        *self.root.write().unwrap() = root;
221
222        self.retain_existing_paths_after_root_update()?;
223        #[cfg(feature = "fuse")]
224        self.drop_paths(paths_needing_new_inode);
225
226        #[cfg(feature = "fuse")]
227        self.notify_entries_invalidated(&invalidations);
228
229        Ok(())
230    }
231
232    #[cfg(feature = "fuse")]
233    pub fn removed_known_entry_paths_for_root(&self, root: &Cid) -> Vec<Vec<String>> {
234        self.changed_known_entries_for_root(root)
235            .into_iter()
236            .filter_map(|invalidation| match invalidation {
237                FuseInvalidation::Delete { parent, name, .. } => {
238                    let mut path = self.path_for_inode(parent).ok()?;
239                    path.push(name);
240                    Some(path)
241                }
242                _ => None,
243            })
244            .collect()
245    }
246
247    #[cfg(not(feature = "fuse"))]
248    pub fn removed_known_entry_paths_for_root(&self, _root: &Cid) -> Vec<Vec<String>> {
249        Vec::new()
250    }
251
252    pub fn begin_refresh_unlinks(&self, paths: impl IntoIterator<Item = Vec<String>>) {
253        self.refresh_unlinks.lock().unwrap().extend(paths);
254    }
255
256    pub fn clear_refresh_unlinks(&self) {
257        self.refresh_unlinks.lock().unwrap().clear();
258    }
259
260    #[cfg(feature = "fuse")]
261    fn set_notifier(&self, notifier: fuser::Notifier) {
262        *self.notifier.lock().unwrap() = Some(notifier);
263    }
264
265    #[cfg(feature = "fuse")]
266    fn clear_notifier(&self) {
267        *self.notifier.lock().unwrap() = None;
268    }
269
270    #[cfg(feature = "fuse")]
271    fn notify_entries_invalidated(&self, invalidations: &[FuseInvalidation]) {
272        let guard = self.notifier.lock().unwrap();
273        let Some(notifier) = guard.as_ref() else {
274            return;
275        };
276        for invalidation in invalidations {
277            match invalidation {
278                FuseInvalidation::Entry { parent, name } => {
279                    let _ = notifier.inval_entry(*parent, std::ffi::OsStr::new(name));
280                }
281                FuseInvalidation::Delete {
282                    parent,
283                    child,
284                    name,
285                } => {
286                    let _ = notifier.delete(*parent, *child, std::ffi::OsStr::new(name));
287                }
288            }
289        }
290    }
291
292    #[cfg(feature = "fuse")]
293    fn changed_known_entries_for_root(&self, new_root: &Cid) -> Vec<FuseInvalidation> {
294        let old_root = self.current_root();
295        let paths = self.paths.read().unwrap().clone();
296        let children = self.children.read().unwrap().clone();
297        let mut invalidations = Vec::new();
298
299        for (inode, path) in paths {
300            let old_entries = self.directory_entry_names_at_root(&old_root, &path);
301            let new_entries = self.directory_entry_names_at_root(new_root, &path);
302            if old_entries.is_none() && new_entries.is_none() {
303                continue;
304            }
305
306            let old_entries = old_entries.unwrap_or_default();
307            let new_entries = new_entries.unwrap_or_default();
308            for removed in old_entries.difference(&new_entries) {
309                invalidations.push(FuseInvalidation::Entry {
310                    parent: inode,
311                    name: removed.clone(),
312                });
313                if let Some(child) = children
314                    .get(&ChildKey {
315                        parent: inode,
316                        name: removed.clone(),
317                    })
318                    .copied()
319                {
320                    invalidations.push(FuseInvalidation::Delete {
321                        parent: inode,
322                        child,
323                        name: removed.clone(),
324                    });
325                }
326            }
327
328            for retained in old_entries.intersection(&new_entries) {
329                if self.entry_changed_between_roots(&old_root, new_root, &path, retained) {
330                    invalidations.push(FuseInvalidation::Entry {
331                        parent: inode,
332                        name: retained.clone(),
333                    });
334                }
335            }
336
337            for added in new_entries.difference(&old_entries) {
338                invalidations.push(FuseInvalidation::Entry {
339                    parent: inode,
340                    name: added.clone(),
341                });
342            }
343        }
344
345        invalidations
346    }
347
348    #[cfg(feature = "fuse")]
349    fn changed_known_paths_requiring_new_inode_for_root(&self, new_root: &Cid) -> Vec<Vec<String>> {
350        let old_root = self.current_root();
351        let paths: Vec<Vec<String>> = self.paths.read().unwrap().values().cloned().collect();
352
353        paths
354            .into_iter()
355            .filter(|path| {
356                !path.is_empty()
357                    && !Self::is_directory_refresh_sentinel_path(path)
358                    && self.entry_path_changed_between_roots(&old_root, new_root, path)
359                    && self.changed_entry_should_get_new_inode(&old_root, new_root, path)
360            })
361            .collect()
362    }
363
364    #[cfg(feature = "fuse")]
365    fn changed_entry_should_get_new_inode(
366        &self,
367        old_root: &Cid,
368        new_root: &Cid,
369        path: &[String],
370    ) -> bool {
371        match (
372            self.resolve_entry_at_root(old_root, path),
373            self.resolve_entry_at_root(new_root, path),
374        ) {
375            (Ok(old), Ok(new)) => old.link_type != LinkType::Dir || new.link_type != LinkType::Dir,
376            _ => true,
377        }
378    }
379
380    #[cfg(feature = "fuse")]
381    fn entry_path_changed_between_roots(
382        &self,
383        old_root: &Cid,
384        new_root: &Cid,
385        path: &[String],
386    ) -> bool {
387        match (
388            self.resolve_entry_at_root(old_root, path),
389            self.resolve_entry_at_root(new_root, path),
390        ) {
391            (Ok(old), Ok(new)) => old != new,
392            (Err(_), Err(_)) => false,
393            _ => true,
394        }
395    }
396
397    #[cfg(feature = "fuse")]
398    fn entry_changed_between_roots(
399        &self,
400        old_root: &Cid,
401        new_root: &Cid,
402        parent_path: &[String],
403        name: &str,
404    ) -> bool {
405        let mut child_path = parent_path.to_vec();
406        child_path.push(name.to_string());
407        match (
408            self.resolve_entry_at_root(old_root, &child_path),
409            self.resolve_entry_at_root(new_root, &child_path),
410        ) {
411            (Ok(old), Ok(new)) => old != new,
412            (Err(_), Err(_)) => false,
413            _ => true,
414        }
415    }
416
417    pub fn lookup_child(&self, parent: u64, name: &str) -> Result<EntryAttr, FsError> {
418        if name.is_empty() {
419            return Err(FsError::NotFound);
420        }
421        if name == "." {
422            return self.get_attr(parent);
423        }
424        if name == ".." {
425            let parent_inode = self.parent_inode(parent);
426            return self.get_attr(parent_inode);
427        }
428        if Self::is_directory_refresh_sentinel(name) {
429            if self.get_attr(parent)?.kind != EntryKind::Directory {
430                return Err(FsError::NotDir);
431            }
432            let inode = self.get_or_create_child_inode(parent, name)?;
433            return Ok(Self::directory_refresh_sentinel_attr(inode));
434        }
435
436        let child_inode = self.get_or_create_child_inode(parent, name)?;
437        let path = self.path_for_inode(child_inode)?;
438
439        match self.resolve_entry(&path) {
440            Ok(entry) => self.entry_attr_from_resolved(child_inode, entry),
441            Err(FsError::NotFound) => {
442                self.drop_inode(child_inode);
443                Err(FsError::NotFound)
444            }
445            Err(err) => Err(err),
446        }
447    }
448
449    pub fn get_attr(&self, inode: u64) -> Result<EntryAttr, FsError> {
450        if inode == ROOT_INODE {
451            return Ok(EntryAttr {
452                inode,
453                size: 0,
454                kind: EntryKind::Directory,
455            });
456        }
457
458        let path = self.path_for_inode(inode)?;
459        if Self::is_directory_refresh_sentinel_path(&path) {
460            let parent_path = &path[..path.len() - 1];
461            self.resolve_dir_cid(parent_path)?;
462            return Ok(Self::directory_refresh_sentinel_attr(inode));
463        }
464        let entry = self.resolve_entry(&path)?;
465        self.entry_attr_from_resolved(inode, entry)
466    }
467
468    pub fn read_file(&self, inode: u64, offset: u64, size: u32) -> Result<Vec<u8>, FsError> {
469        let path = self.path_for_inode(inode)?;
470        if Self::is_directory_refresh_sentinel_path(&path) {
471            return Ok(Vec::new());
472        }
473        let entry = self.resolve_entry(&path)?;
474        if entry.link_type == LinkType::Dir {
475            return Err(FsError::IsDir);
476        }
477
478        let file_size = self.entry_size(&entry)?;
479        if offset >= file_size {
480            return Ok(vec![]);
481        }
482        let read_len = (size as u64).min(file_size - offset);
483        if read_len == 0 {
484            return Ok(vec![]);
485        }
486
487        if entry.cid.key.is_some() {
488            let data = block_on(self.tree.get(&entry.cid, None))?.ok_or(FsError::NotFound)?;
489            let start = usize::try_from(offset).unwrap_or(usize::MAX);
490            if start >= data.len() {
491                return Ok(vec![]);
492            }
493            let end_u64 = offset.saturating_add(read_len);
494            let mut end = usize::try_from(end_u64).unwrap_or(data.len());
495            if end > data.len() {
496                end = data.len();
497            }
498            return Ok(data[start..end].to_vec());
499        }
500
501        let end = offset.saturating_add(read_len);
502        let data = block_on(
503            self.tree
504                .read_file_range(&entry.cid.hash, offset, Some(end)),
505        )?
506        .ok_or(FsError::NotFound)?;
507        Ok(data)
508    }
509
510    pub fn read_dir(&self, inode: u64) -> Result<Vec<DirEntry>, FsError> {
511        let path = self.path_for_inode(inode)?;
512        let dir_cid = self.resolve_dir_cid(&path)?;
513
514        let entries = block_on(self.tree.list_directory(&dir_cid))?;
515        let mut out = Vec::with_capacity(entries.len());
516
517        for entry in entries {
518            if Self::is_directory_refresh_sentinel(&entry.name) {
519                continue;
520            }
521            let child_inode = self.get_or_create_child_inode(inode, &entry.name)?;
522            out.push(DirEntry {
523                inode: child_inode,
524                name: entry.name,
525                kind: Self::kind_from_link(entry.link_type),
526            });
527        }
528
529        Ok(out)
530    }
531
532    pub fn create_file(&self, parent: u64, name: &str) -> Result<EntryAttr, FsError> {
533        self.ensure_valid_name(name)?;
534        let _guard = self.modify_lock.lock().unwrap();
535
536        let parent_path = self.path_for_inode(parent)?;
537        let mut child_path = parent_path.clone();
538        child_path.push(name.to_string());
539
540        if self.resolve_entry(&child_path).is_ok() {
541            return Err(FsError::AlreadyExists);
542        }
543
544        let (cid, size) = block_on(self.tree.put(&[]))?;
545        let link_type = self.link_type_for_size(size);
546        let meta = Self::file_entry_meta(&[]);
547        let new_root = block_on(self.tree.set_entry_with_meta(
548            &self.current_root(),
549            &self.path_refs(&parent_path),
550            name,
551            &cid,
552            size,
553            link_type,
554            Some(meta),
555        ))?;
556
557        self.apply_root_update(new_root)?;
558
559        let inode = self.insert_path(parent, name.to_string(), child_path);
560        Ok(EntryAttr {
561            inode,
562            size,
563            kind: EntryKind::File,
564        })
565    }
566
567    pub fn mkdir(&self, parent: u64, name: &str) -> Result<EntryAttr, FsError> {
568        self.ensure_valid_name(name)?;
569        let _guard = self.modify_lock.lock().unwrap();
570
571        let parent_path = self.path_for_inode(parent)?;
572        let mut child_path = parent_path.clone();
573        child_path.push(name.to_string());
574
575        if self.resolve_entry(&child_path).is_ok() {
576            return Err(FsError::AlreadyExists);
577        }
578
579        let dir_cid = block_on(self.tree.put_directory(Vec::new()))?;
580        let new_root = block_on(self.tree.set_entry(
581            &self.current_root(),
582            &self.path_refs(&parent_path),
583            name,
584            &dir_cid,
585            0,
586            LinkType::Dir,
587        ))?;
588
589        self.apply_root_update(new_root)?;
590
591        let inode = self.insert_path(parent, name.to_string(), child_path);
592        Ok(EntryAttr {
593            inode,
594            size: 0,
595            kind: EntryKind::Directory,
596        })
597    }
598
599    pub fn write_file(&self, inode: u64, offset: u64, data: &[u8]) -> Result<u32, FsError> {
600        let _guard = self.modify_lock.lock().unwrap();
601        let path = self.path_for_inode(inode)?;
602        let existing = self.read_file_full(&path)?;
603        let new_data = Self::apply_write(existing, offset, data);
604        self.update_file_at_path(&path, new_data)?;
605        Ok(data.len() as u32)
606    }
607
608    pub fn truncate_file(&self, inode: u64, size: u64) -> Result<(), FsError> {
609        let _guard = self.modify_lock.lock().unwrap();
610        let path = self.path_for_inode(inode)?;
611        let existing = self.read_file_full(&path)?;
612        let new_data = Self::apply_truncate(existing, size);
613        self.update_file_at_path(&path, new_data)?;
614        Ok(())
615    }
616
617    pub fn unlink(&self, parent: u64, name: &str) -> Result<(), FsError> {
618        if Self::is_directory_refresh_sentinel(name) {
619            return Ok(());
620        }
621        if self.take_refresh_unlink(parent, name)? {
622            return Ok(());
623        }
624        self.ensure_valid_name(name)?;
625        let _guard = self.modify_lock.lock().unwrap();
626
627        let parent_path = self.path_for_inode(parent)?;
628        let mut child_path = parent_path.clone();
629        child_path.push(name.to_string());
630        let entry = self.resolve_entry(&child_path)?;
631        if entry.link_type == LinkType::Dir {
632            return Err(FsError::IsDir);
633        }
634
635        let new_root = block_on(self.tree.remove_entry(
636            &self.current_root(),
637            &self.path_refs(&parent_path),
638            name,
639        ))?;
640
641        self.apply_root_update(new_root)?;
642        self.remove_paths_prefix(&child_path);
643        self.children.write().unwrap().remove(&ChildKey {
644            parent,
645            name: name.to_string(),
646        });
647
648        Ok(())
649    }
650
651    pub fn rmdir(&self, parent: u64, name: &str) -> Result<(), FsError> {
652        if self.take_refresh_unlink(parent, name)? {
653            return Ok(());
654        }
655        self.ensure_valid_name(name)?;
656        let _guard = self.modify_lock.lock().unwrap();
657
658        let parent_path = self.path_for_inode(parent)?;
659        let mut child_path = parent_path.clone();
660        child_path.push(name.to_string());
661        let entry = self.resolve_entry(&child_path)?;
662        if entry.link_type != LinkType::Dir {
663            return Err(FsError::NotDir);
664        }
665
666        let dir_entries = block_on(self.tree.list_directory(&entry.cid))?;
667        if !dir_entries.is_empty() {
668            return Err(FsError::NotEmpty);
669        }
670
671        let new_root = block_on(self.tree.remove_entry(
672            &self.current_root(),
673            &self.path_refs(&parent_path),
674            name,
675        ))?;
676
677        self.apply_root_update(new_root)?;
678        self.remove_paths_prefix(&child_path);
679        self.children.write().unwrap().remove(&ChildKey {
680            parent,
681            name: name.to_string(),
682        });
683
684        Ok(())
685    }
686
687    pub fn rename(
688        &self,
689        parent: u64,
690        name: &str,
691        new_parent: u64,
692        new_name: &str,
693    ) -> Result<(), FsError> {
694        self.ensure_valid_name(name)?;
695        self.ensure_valid_name(new_name)?;
696        let _guard = self.modify_lock.lock().unwrap();
697
698        if parent == new_parent && name == new_name {
699            return Ok(());
700        }
701
702        let parent_path = self.path_for_inode(parent)?;
703        let new_parent_path = self.path_for_inode(new_parent)?;
704
705        let mut old_path = parent_path.clone();
706        old_path.push(name.to_string());
707        let entry = self.resolve_entry(&old_path)?;
708
709        let new_root = block_on(self.tree.set_entry_with_meta(
710            &self.current_root(),
711            &self.path_refs(&new_parent_path),
712            new_name,
713            &entry.cid,
714            entry.size,
715            entry.link_type,
716            entry.meta.clone(),
717        ))?;
718        let new_root = block_on(self.tree.remove_entry(
719            &new_root,
720            &self.path_refs(&parent_path),
721            name,
722        ))?;
723
724        self.apply_root_update(new_root)?;
725
726        let inode = self.get_or_create_child_inode(parent, name)?;
727        let mut new_path = new_parent_path.clone();
728        new_path.push(new_name.to_string());
729
730        self.children.write().unwrap().remove(&ChildKey {
731            parent,
732            name: name.to_string(),
733        });
734        self.children.write().unwrap().insert(
735            ChildKey {
736                parent: new_parent,
737                name: new_name.to_string(),
738            },
739            inode,
740        );
741
742        self.parents.write().unwrap().insert(inode, new_parent);
743        self.update_paths_prefix(&old_path, &new_path);
744
745        Ok(())
746    }
747
748    fn ensure_valid_name(&self, name: &str) -> Result<(), FsError> {
749        if name.is_empty() || name.contains('/') || Self::is_directory_refresh_sentinel(name) {
750            return Err(FsError::InvalidName);
751        }
752        Ok(())
753    }
754
755    fn is_directory_refresh_sentinel(name: &str) -> bool {
756        name == DIRECTORY_REFRESH_SENTINEL_NAME
757    }
758
759    fn is_directory_refresh_sentinel_path(path: &[String]) -> bool {
760        path.last()
761            .is_some_and(|name| Self::is_directory_refresh_sentinel(name))
762    }
763
764    fn directory_refresh_sentinel_attr(inode: u64) -> EntryAttr {
765        EntryAttr {
766            inode,
767            size: 0,
768            kind: EntryKind::File,
769        }
770    }
771
772    fn path_for_inode(&self, inode: u64) -> Result<Vec<String>, FsError> {
773        self.paths
774            .read()
775            .unwrap()
776            .get(&inode)
777            .cloned()
778            .ok_or(FsError::NotFound)
779    }
780
781    fn take_refresh_unlink(&self, parent: u64, name: &str) -> Result<bool, FsError> {
782        let parent_path = self.path_for_inode(parent)?;
783        let mut child_path = parent_path;
784        child_path.push(name.to_string());
785        Ok(self.refresh_unlinks.lock().unwrap().remove(&child_path))
786    }
787
788    fn resolve_entry(&self, path: &[String]) -> Result<ResolvedEntry, FsError> {
789        self.resolve_entry_at_root(&self.current_root(), path)
790    }
791
792    fn resolve_entry_at_root(&self, root: &Cid, path: &[String]) -> Result<ResolvedEntry, FsError> {
793        if path.is_empty() {
794            return Ok(ResolvedEntry {
795                cid: root.clone(),
796                link_type: LinkType::Dir,
797                size: 0,
798                meta: None,
799            });
800        }
801
802        let (parent_path, name) = path.split_at(path.len() - 1);
803        let parent_cid = self.resolve_dir_cid_at_root(root, parent_path)?;
804        let entries = block_on(self.tree.list_directory(&parent_cid))?;
805        let entry = entries
806            .into_iter()
807            .find(|e| e.name == name[0])
808            .ok_or(FsError::NotFound)?;
809
810        Ok(ResolvedEntry {
811            cid: Cid {
812                hash: entry.hash,
813                key: entry.key,
814            },
815            link_type: entry.link_type,
816            size: entry.size,
817            meta: entry.meta,
818        })
819    }
820
821    fn resolve_dir_cid(&self, path: &[String]) -> Result<Cid, FsError> {
822        self.resolve_dir_cid_at_root(&self.current_root(), path)
823    }
824
825    fn resolve_dir_cid_at_root(&self, root: &Cid, path: &[String]) -> Result<Cid, FsError> {
826        if path.is_empty() {
827            return Ok(root.clone());
828        }
829
830        let path_str = path.join("/");
831        let cid = block_on(self.tree.resolve(root, &path_str))?.ok_or(FsError::NotFound)?;
832
833        let is_dir = block_on(self.tree.is_dir(&cid))?;
834        if !is_dir {
835            return Err(FsError::NotDir);
836        }
837
838        Ok(cid)
839    }
840
841    #[cfg(feature = "fuse")]
842    fn directory_entry_names_at_root(
843        &self,
844        root: &Cid,
845        path: &[String],
846    ) -> Option<HashSet<String>> {
847        let dir_cid = self.resolve_dir_cid_at_root(root, path).ok()?;
848        let entries = block_on(self.tree.list_directory(&dir_cid)).ok()?;
849        Some(
850            entries
851                .into_iter()
852                .map(|entry| entry.name)
853                .filter(|name| !Self::is_directory_refresh_sentinel(name))
854                .collect(),
855        )
856    }
857
858    fn retain_existing_paths_after_root_update(&self) -> Result<(), FsError> {
859        let known: Vec<(u64, Vec<String>)> = self
860            .paths
861            .read()
862            .unwrap()
863            .iter()
864            .map(|(inode, path)| (*inode, path.clone()))
865            .collect();
866        let mut keep = HashSet::new();
867        keep.insert(ROOT_INODE);
868
869        for (inode, path) in known {
870            if inode == ROOT_INODE {
871                continue;
872            }
873            if Self::is_directory_refresh_sentinel_path(&path) {
874                if self.resolve_dir_cid(&path[..path.len() - 1]).is_ok() {
875                    keep.insert(inode);
876                }
877                continue;
878            }
879            match self.resolve_entry(&path) {
880                Ok(_) => {
881                    keep.insert(inode);
882                }
883                Err(FsError::NotFound) | Err(FsError::NotDir) => {}
884                Err(error) => return Err(error),
885            }
886        }
887
888        self.paths
889            .write()
890            .unwrap()
891            .retain(|inode, _| keep.contains(inode));
892        self.parents
893            .write()
894            .unwrap()
895            .retain(|inode, parent| keep.contains(inode) && keep.contains(parent));
896        self.children
897            .write()
898            .unwrap()
899            .retain(|key, inode| keep.contains(&key.parent) && keep.contains(inode));
900        Ok(())
901    }
902
903    fn entry_attr_from_resolved(
904        &self,
905        inode: u64,
906        entry: ResolvedEntry,
907    ) -> Result<EntryAttr, FsError> {
908        let kind = Self::kind_from_link(entry.link_type);
909        let size = if kind == EntryKind::Directory {
910            0
911        } else {
912            self.entry_size(&entry)?
913        };
914
915        Ok(EntryAttr { inode, size, kind })
916    }
917
918    fn entry_size(&self, entry: &ResolvedEntry) -> Result<u64, FsError> {
919        if entry.link_type == LinkType::Dir {
920            return Ok(0);
921        }
922        if entry.size > 0 {
923            return Ok(entry.size);
924        }
925
926        let data = block_on(self.tree.get(&entry.cid, None))?.ok_or(FsError::NotFound)?;
927        Ok(data.len() as u64)
928    }
929
930    fn read_file_full(&self, path: &[String]) -> Result<Vec<u8>, FsError> {
931        let entry = self.resolve_entry(path)?;
932        if entry.link_type == LinkType::Dir {
933            return Err(FsError::IsDir);
934        }
935        let data = block_on(self.tree.get(&entry.cid, None))?.ok_or(FsError::NotFound)?;
936        Ok(data)
937    }
938
939    fn update_file_at_path(&self, path: &[String], data: Vec<u8>) -> Result<(), FsError> {
940        let (parent_path, name) = path.split_at(path.len() - 1);
941        let (cid, size) = block_on(self.tree.put(&data))?;
942        let link_type = self.link_type_for_size(size);
943        let meta = Self::file_entry_meta(&data);
944
945        let new_root = block_on(self.tree.set_entry_with_meta(
946            &self.current_root(),
947            &self.path_refs(parent_path),
948            name[0].as_str(),
949            &cid,
950            size,
951            link_type,
952            Some(meta),
953        ))?;
954
955        self.apply_root_update(new_root)
956    }
957
958    fn file_entry_meta(data: &[u8]) -> HashMap<String, serde_json::Value> {
959        HashMap::from([(
960            WHOLE_FILE_HASH_META_KEY.to_string(),
961            serde_json::Value::String(to_hex(&sha256(data))),
962        )])
963    }
964
965    fn apply_root_update(&self, new_root: Cid) -> Result<(), FsError> {
966        if let Some(publisher) = &self.publisher {
967            publisher.publish(&new_root)?;
968        }
969        *self.root.write().unwrap() = new_root;
970        Ok(())
971    }
972
973    fn link_type_for_size(&self, size: u64) -> LinkType {
974        if size as usize > self.tree.chunk_size() {
975            LinkType::File
976        } else {
977            LinkType::Blob
978        }
979    }
980
981    fn get_or_create_child_inode(&self, parent: u64, name: &str) -> Result<u64, FsError> {
982        let key = ChildKey {
983            parent,
984            name: name.to_string(),
985        };
986        if let Some(inode) = self.children.read().unwrap().get(&key).copied() {
987            return Ok(inode);
988        }
989
990        let parent_path = self.path_for_inode(parent)?;
991        let mut child_path = parent_path.clone();
992        child_path.push(name.to_string());
993
994        if let Some(existing) = self.find_inode_by_path(&child_path) {
995            self.children.write().unwrap().insert(key, existing);
996            return Ok(existing);
997        }
998
999        Ok(self.insert_path(parent, name.to_string(), child_path))
1000    }
1001
1002    fn insert_path(&self, parent: u64, name: String, path: Vec<String>) -> u64 {
1003        let inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
1004        self.paths.write().unwrap().insert(inode, path);
1005        self.parents.write().unwrap().insert(inode, parent);
1006        self.children
1007            .write()
1008            .unwrap()
1009            .insert(ChildKey { parent, name }, inode);
1010        inode
1011    }
1012
1013    fn find_inode_by_path(&self, path: &[String]) -> Option<u64> {
1014        self.paths
1015            .read()
1016            .unwrap()
1017            .iter()
1018            .find_map(|(inode, inode_path)| {
1019                if inode_path == path {
1020                    Some(*inode)
1021                } else {
1022                    None
1023                }
1024            })
1025    }
1026
1027    fn update_paths_prefix(&self, old_prefix: &[String], new_prefix: &[String]) {
1028        let mut paths = self.paths.write().unwrap();
1029        for path in paths.values_mut() {
1030            if Self::path_has_prefix(path, old_prefix) {
1031                let mut updated = new_prefix.to_vec();
1032                updated.extend_from_slice(&path[old_prefix.len()..]);
1033                *path = updated;
1034            }
1035        }
1036    }
1037
1038    fn remove_paths_prefix(&self, prefix: &[String]) {
1039        let mut to_remove = Vec::new();
1040        {
1041            let paths = self.paths.read().unwrap();
1042            for (inode, path) in paths.iter() {
1043                if *inode == ROOT_INODE {
1044                    continue;
1045                }
1046                if Self::path_has_prefix(path, prefix) {
1047                    to_remove.push(*inode);
1048                }
1049            }
1050        }
1051
1052        if to_remove.is_empty() {
1053            return;
1054        }
1055
1056        let remove_set: std::collections::HashSet<u64> = to_remove.into_iter().collect();
1057        self.paths
1058            .write()
1059            .unwrap()
1060            .retain(|inode, _| !remove_set.contains(inode));
1061        self.parents
1062            .write()
1063            .unwrap()
1064            .retain(|inode, _| !remove_set.contains(inode));
1065        self.children
1066            .write()
1067            .unwrap()
1068            .retain(|_, inode| !remove_set.contains(inode));
1069    }
1070
1071    #[cfg(feature = "fuse")]
1072    fn drop_paths(&self, paths: impl IntoIterator<Item = Vec<String>>) {
1073        for path in paths {
1074            if let Some(inode) = self.find_inode_by_path(&path) {
1075                self.drop_inode(inode);
1076            }
1077        }
1078    }
1079
1080    fn drop_inode(&self, inode: u64) {
1081        if inode == ROOT_INODE {
1082            return;
1083        }
1084        let mut paths = self.paths.write().unwrap();
1085        let removed_path = paths.remove(&inode);
1086        drop(paths);
1087        self.parents.write().unwrap().remove(&inode);
1088        if let Some(path) = removed_path {
1089            if let Some((name, parent_path)) = path.split_last() {
1090                if let Some(parent_inode) = self.find_inode_by_path(parent_path) {
1091                    self.children.write().unwrap().remove(&ChildKey {
1092                        parent: parent_inode,
1093                        name: name.to_string(),
1094                    });
1095                }
1096            }
1097        }
1098    }
1099
1100    fn parent_inode(&self, inode: u64) -> u64 {
1101        self.parents
1102            .read()
1103            .unwrap()
1104            .get(&inode)
1105            .copied()
1106            .unwrap_or(ROOT_INODE)
1107    }
1108
1109    fn kind_from_link(link_type: LinkType) -> EntryKind {
1110        match link_type {
1111            LinkType::Dir => EntryKind::Directory,
1112            LinkType::Blob | LinkType::File => EntryKind::File,
1113        }
1114    }
1115
1116    fn apply_write(mut existing: Vec<u8>, offset: u64, data: &[u8]) -> Vec<u8> {
1117        let offset_usize = offset as usize;
1118        if existing.len() < offset_usize {
1119            existing.resize(offset_usize, 0);
1120        }
1121        if existing.len() < offset_usize + data.len() {
1122            existing.resize(offset_usize + data.len(), 0);
1123        }
1124        existing[offset_usize..offset_usize + data.len()].copy_from_slice(data);
1125        existing
1126    }
1127
1128    fn apply_truncate(mut existing: Vec<u8>, size: u64) -> Vec<u8> {
1129        let size = size as usize;
1130        if existing.len() > size {
1131            existing.truncate(size);
1132        } else if existing.len() < size {
1133            existing.resize(size, 0);
1134        }
1135        existing
1136    }
1137
1138    fn path_refs<'a>(&self, path: &'a [String]) -> Vec<&'a str> {
1139        path.iter().map(|p| p.as_str()).collect()
1140    }
1141
1142    fn path_has_prefix(path: &[String], prefix: &[String]) -> bool {
1143        if prefix.len() > path.len() {
1144            return false;
1145        }
1146        path.iter().zip(prefix.iter()).all(|(a, b)| a == b)
1147    }
1148}
1149
1150#[cfg(feature = "fuse")]
1151mod fuse_impl {
1152    use super::*;
1153    use fuser::{
1154        FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyCreate, ReplyData,
1155        ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyStatfs, ReplyWrite, Request,
1156    };
1157    use std::ffi::CString;
1158    use std::ffi::OsStr;
1159    use std::path::Path;
1160    use std::time::{Duration, SystemTime};
1161
1162    const TTL: Duration = Duration::ZERO;
1163    const FALLBACK_BLOCK_SIZE: u32 = 4096;
1164    const FALLBACK_TOTAL_BYTES: u64 = 1 << 40;
1165    const FALLBACK_FREE_BYTES: u64 = 1 << 39;
1166    const FALLBACK_TOTAL_FILES: u64 = 1_000_000;
1167    const FALLBACK_FREE_FILES: u64 = 900_000;
1168
1169    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1170    pub(crate) struct FsStats {
1171        pub(crate) blocks: u64,
1172        pub(crate) bfree: u64,
1173        pub(crate) bavail: u64,
1174        pub(crate) files: u64,
1175        pub(crate) ffree: u64,
1176        pub(crate) bsize: u32,
1177        pub(crate) namelen: u32,
1178        pub(crate) frsize: u32,
1179    }
1180
1181    impl FsError {
1182        fn errno(&self) -> i32 {
1183            match self {
1184                FsError::InvalidRoot | FsError::InvalidName => libc::EINVAL,
1185                FsError::NotFound => libc::ENOENT,
1186                FsError::NotDir => libc::ENOTDIR,
1187                FsError::IsDir => libc::EISDIR,
1188                FsError::AlreadyExists => libc::EEXIST,
1189                FsError::NotEmpty => libc::ENOTEMPTY,
1190                FsError::Tree(_) | FsError::Publish(_) => libc::EIO,
1191            }
1192        }
1193    }
1194
1195    pub(crate) fn fallback_fs_stats() -> FsStats {
1196        let blocks = FALLBACK_TOTAL_BYTES / u64::from(FALLBACK_BLOCK_SIZE);
1197        let free_blocks = FALLBACK_FREE_BYTES / u64::from(FALLBACK_BLOCK_SIZE);
1198        FsStats {
1199            blocks,
1200            bfree: free_blocks,
1201            bavail: free_blocks,
1202            files: FALLBACK_TOTAL_FILES,
1203            ffree: FALLBACK_FREE_FILES,
1204            bsize: FALLBACK_BLOCK_SIZE,
1205            namelen: 255,
1206            frsize: FALLBACK_BLOCK_SIZE,
1207        }
1208    }
1209
1210    fn host_fs_stats(path: &Path) -> Option<FsStats> {
1211        let path = CString::new(path.as_os_str().as_encoded_bytes()).ok()?;
1212        let mut stat = std::mem::MaybeUninit::<libc::statfs>::uninit();
1213        let result = unsafe { libc::statfs(path.as_ptr(), stat.as_mut_ptr()) };
1214        if result != 0 {
1215            return None;
1216        }
1217
1218        let stat = unsafe { stat.assume_init() };
1219        let bsize = u32::try_from(stat.f_bsize).ok()?;
1220        Some(FsStats {
1221            blocks: stat.f_blocks,
1222            bfree: stat.f_bfree,
1223            bavail: stat.f_bavail,
1224            files: stat.f_files,
1225            ffree: stat.f_ffree,
1226            bsize,
1227            namelen: 255,
1228            frsize: bsize,
1229        })
1230    }
1231
1232    pub(crate) fn current_fs_stats() -> FsStats {
1233        host_fs_stats(Path::new("/")).unwrap_or_else(fallback_fs_stats)
1234    }
1235
1236    impl<S: Store + Send + Sync + 'static> HashtreeFuse<S> {
1237        pub fn mount(
1238            self,
1239            mountpoint: impl AsRef<Path>,
1240            options: &[MountOption],
1241        ) -> std::io::Result<()> {
1242            let mut session = fuser::Session::new(self.clone(), mountpoint.as_ref(), options)?;
1243            self.set_notifier(session.notifier());
1244            let result = session.run();
1245            self.clear_notifier();
1246            result
1247        }
1248
1249        fn file_attr(&self, attr: &EntryAttr) -> FileAttr {
1250            let (kind, perm, nlink) = match attr.kind {
1251                EntryKind::Directory => (FileType::Directory, 0o755, 2),
1252                EntryKind::File => (FileType::RegularFile, 0o644, 1),
1253            };
1254            let uid = unsafe { libc::geteuid() };
1255            let gid = unsafe { libc::getegid() };
1256            let blocks = attr.size.div_ceil(512);
1257
1258            FileAttr {
1259                ino: attr.inode,
1260                size: attr.size,
1261                blocks,
1262                atime: SystemTime::UNIX_EPOCH,
1263                mtime: SystemTime::UNIX_EPOCH,
1264                ctime: SystemTime::UNIX_EPOCH,
1265                crtime: SystemTime::UNIX_EPOCH,
1266                kind,
1267                perm,
1268                nlink,
1269                uid,
1270                gid,
1271                rdev: 0,
1272                blksize: 512,
1273                flags: 0,
1274            }
1275        }
1276
1277        fn file_type(kind: EntryKind) -> FileType {
1278            match kind {
1279                EntryKind::Directory => FileType::Directory,
1280                EntryKind::File => FileType::RegularFile,
1281            }
1282        }
1283    }
1284
1285    impl<S: Store + Send + Sync + 'static> Filesystem for HashtreeFuse<S> {
1286        fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
1287            let name = match name.to_str() {
1288                Some(value) => value,
1289                None => {
1290                    reply.error(libc::ENOENT);
1291                    return;
1292                }
1293            };
1294
1295            match self.lookup_child(parent, name) {
1296                Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
1297                Err(err) => reply.error(err.errno()),
1298            }
1299        }
1300
1301        fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
1302            match self.get_attr(ino) {
1303                Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
1304                Err(err) => reply.error(err.errno()),
1305            }
1306        }
1307
1308        fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen) {
1309            reply.opened(0, 0);
1310        }
1311
1312        fn read(
1313            &mut self,
1314            _req: &Request<'_>,
1315            ino: u64,
1316            _fh: u64,
1317            offset: i64,
1318            size: u32,
1319            _flags: i32,
1320            _lock_owner: Option<u64>,
1321            reply: ReplyData,
1322        ) {
1323            let offset = if offset < 0 { 0 } else { offset as u64 };
1324            match self.read_file(ino, offset, size) {
1325                Ok(data) => reply.data(&data),
1326                Err(err) => reply.error(err.errno()),
1327            }
1328        }
1329
1330        fn write(
1331            &mut self,
1332            _req: &Request<'_>,
1333            ino: u64,
1334            _fh: u64,
1335            offset: i64,
1336            data: &[u8],
1337            _write_flags: u32,
1338            _flags: i32,
1339            _lock_owner: Option<u64>,
1340            reply: ReplyWrite,
1341        ) {
1342            let offset = if offset < 0 { 0 } else { offset as u64 };
1343            match self.write_file(ino, offset, data) {
1344                Ok(written) => reply.written(written),
1345                Err(err) => reply.error(err.errno()),
1346            }
1347        }
1348
1349        fn create(
1350            &mut self,
1351            _req: &Request<'_>,
1352            parent: u64,
1353            name: &OsStr,
1354            _mode: u32,
1355            _umask: u32,
1356            _flags: i32,
1357            reply: ReplyCreate,
1358        ) {
1359            let name = match name.to_str() {
1360                Some(value) => value,
1361                None => {
1362                    reply.error(libc::EINVAL);
1363                    return;
1364                }
1365            };
1366
1367            match self.create_file(parent, name) {
1368                Ok(attr) => reply.created(&TTL, &self.file_attr(&attr), 0, 0, 0),
1369                Err(err) => reply.error(err.errno()),
1370            }
1371        }
1372
1373        fn mkdir(
1374            &mut self,
1375            _req: &Request<'_>,
1376            parent: u64,
1377            name: &OsStr,
1378            _mode: u32,
1379            _umask: u32,
1380            reply: ReplyEntry,
1381        ) {
1382            let name = match name.to_str() {
1383                Some(value) => value,
1384                None => {
1385                    reply.error(libc::EINVAL);
1386                    return;
1387                }
1388            };
1389
1390            match HashtreeFuse::mkdir(self, parent, name) {
1391                Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
1392                Err(err) => reply.error(err.errno()),
1393            }
1394        }
1395
1396        fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
1397            let name = match name.to_str() {
1398                Some(value) => value,
1399                None => {
1400                    reply.error(libc::EINVAL);
1401                    return;
1402                }
1403            };
1404
1405            match HashtreeFuse::unlink(self, parent, name) {
1406                Ok(()) => reply.ok(),
1407                Err(err) => reply.error(err.errno()),
1408            }
1409        }
1410
1411        fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
1412            let name = match name.to_str() {
1413                Some(value) => value,
1414                None => {
1415                    reply.error(libc::EINVAL);
1416                    return;
1417                }
1418            };
1419
1420            match HashtreeFuse::rmdir(self, parent, name) {
1421                Ok(()) => reply.ok(),
1422                Err(err) => reply.error(err.errno()),
1423            }
1424        }
1425
1426        fn rename(
1427            &mut self,
1428            _req: &Request<'_>,
1429            parent: u64,
1430            name: &OsStr,
1431            newparent: u64,
1432            newname: &OsStr,
1433            _flags: u32,
1434            reply: ReplyEmpty,
1435        ) {
1436            let name = match name.to_str() {
1437                Some(value) => value,
1438                None => {
1439                    reply.error(libc::EINVAL);
1440                    return;
1441                }
1442            };
1443            let newname = match newname.to_str() {
1444                Some(value) => value,
1445                None => {
1446                    reply.error(libc::EINVAL);
1447                    return;
1448                }
1449            };
1450
1451            match HashtreeFuse::rename(self, parent, name, newparent, newname) {
1452                Ok(()) => reply.ok(),
1453                Err(err) => reply.error(err.errno()),
1454            }
1455        }
1456
1457        fn setattr(
1458            &mut self,
1459            _req: &Request<'_>,
1460            ino: u64,
1461            _mode: Option<u32>,
1462            _uid: Option<u32>,
1463            _gid: Option<u32>,
1464            size: Option<u64>,
1465            _atime: Option<fuser::TimeOrNow>,
1466            _mtime: Option<fuser::TimeOrNow>,
1467            _ctime: Option<SystemTime>,
1468            _fh: Option<u64>,
1469            _crtime: Option<SystemTime>,
1470            _chgtime: Option<SystemTime>,
1471            _bkuptime: Option<SystemTime>,
1472            _flags: Option<u32>,
1473            reply: ReplyAttr,
1474        ) {
1475            if let Some(size) = size {
1476                match self.truncate_file(ino, size) {
1477                    Ok(()) => {
1478                        if let Ok(attr) = self.get_attr(ino) {
1479                            reply.attr(&TTL, &self.file_attr(&attr));
1480                        } else {
1481                            reply.error(libc::EIO);
1482                        }
1483                    }
1484                    Err(err) => reply.error(err.errno()),
1485                }
1486            } else {
1487                match self.get_attr(ino) {
1488                    Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
1489                    Err(err) => reply.error(err.errno()),
1490                }
1491            }
1492        }
1493
1494        fn readdir(
1495            &mut self,
1496            _req: &Request<'_>,
1497            ino: u64,
1498            _fh: u64,
1499            offset: i64,
1500            mut reply: ReplyDirectory,
1501        ) {
1502            let mut entries = Vec::new();
1503            entries.push((ino, EntryKind::Directory, ".".to_string()));
1504            let parent = self.parent_inode(ino);
1505            entries.push((parent, EntryKind::Directory, "..".to_string()));
1506
1507            match self.read_dir(ino) {
1508                Ok(children) => {
1509                    for entry in children {
1510                        entries.push((entry.inode, entry.kind, entry.name));
1511                    }
1512                }
1513                Err(err) => {
1514                    reply.error(err.errno());
1515                    return;
1516                }
1517            }
1518
1519            let start = if offset < 0 { 0 } else { offset as usize };
1520            for (index, (inode, kind, name)) in entries.into_iter().enumerate().skip(start) {
1521                let next_offset = (index + 1) as i64;
1522                let full = reply.add(inode, next_offset, Self::file_type(kind), name);
1523                if full {
1524                    break;
1525                }
1526            }
1527
1528            reply.ok();
1529        }
1530
1531        fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) {
1532            let stats = current_fs_stats();
1533            reply.statfs(
1534                stats.blocks,
1535                stats.bfree,
1536                stats.bavail,
1537                stats.files,
1538                stats.ffree,
1539                stats.bsize,
1540                stats.namelen,
1541                stats.frsize,
1542            );
1543        }
1544    }
1545}
1546
1547#[cfg(test)]
1548mod tests {
1549    use super::*;
1550    use hashtree_core::store::MemoryStore;
1551
1552    #[cfg(feature = "fuse")]
1553    use super::fuse_impl::{current_fs_stats, fallback_fs_stats};
1554
1555    struct RecordingPublisher {
1556        updates: Mutex<Vec<Cid>>,
1557    }
1558
1559    impl RecordingPublisher {
1560        fn new() -> Self {
1561            Self {
1562                updates: Mutex::new(Vec::new()),
1563            }
1564        }
1565
1566        fn updates(&self) -> Vec<Cid> {
1567            self.updates.lock().unwrap().clone()
1568        }
1569    }
1570
1571    impl RootPublisher for RecordingPublisher {
1572        fn publish(&self, cid: &Cid) -> Result<(), FsError> {
1573            self.updates.lock().unwrap().push(cid.clone());
1574            Ok(())
1575        }
1576    }
1577
1578    async fn empty_root(store: Arc<MemoryStore>) -> Cid {
1579        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1580        tree.put_directory(Vec::new()).await.unwrap()
1581    }
1582
1583    #[tokio::test]
1584    async fn test_create_write_read_file() {
1585        let store = Arc::new(MemoryStore::new());
1586        let root = empty_root(store.clone()).await;
1587        let fs = HashtreeFuse::new(store.clone(), root).unwrap();
1588
1589        let attr = fs.create_file(ROOT_INODE, "hello.txt").unwrap();
1590        assert_eq!(attr.kind, EntryKind::File);
1591
1592        fs.write_file(attr.inode, 0, b"hello").unwrap();
1593        let read = fs.read_file(attr.inode, 0, 5).unwrap();
1594        assert_eq!(read, b"hello");
1595
1596        let tree = HashTree::new(HashTreeConfig::new(store));
1597        let entries = tree.list_directory(&fs.current_root()).await.unwrap();
1598        let file = entries
1599            .iter()
1600            .find(|entry| entry.name == "hello.txt")
1601            .unwrap();
1602        assert_eq!(
1603            file.meta
1604                .as_ref()
1605                .and_then(|meta| meta.get("whole_file_hash"))
1606                .and_then(serde_json::Value::as_str),
1607            Some("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
1608        );
1609    }
1610
1611    #[tokio::test]
1612    async fn test_mkdir_and_rename() {
1613        let store = Arc::new(MemoryStore::new());
1614        let root = empty_root(store.clone()).await;
1615        let fs = HashtreeFuse::new(store.clone(), root).unwrap();
1616
1617        let dir = fs.mkdir(ROOT_INODE, "docs").unwrap();
1618        let file = fs.create_file(dir.inode, "draft.txt").unwrap();
1619        fs.write_file(file.inode, 0, b"data").unwrap();
1620
1621        fs.rename(dir.inode, "draft.txt", dir.inode, "final.txt")
1622            .unwrap();
1623        let entries = fs.read_dir(dir.inode).unwrap();
1624        let names: Vec<String> = entries.into_iter().map(|e| e.name).collect();
1625        assert!(names.contains(&"final.txt".to_string()));
1626        assert!(!names.contains(&"draft.txt".to_string()));
1627
1628        let tree = HashTree::new(HashTreeConfig::new(store));
1629        let docs = tree
1630            .resolve(&fs.current_root(), "docs")
1631            .await
1632            .unwrap()
1633            .unwrap();
1634        let entries = tree.list_directory(&docs).await.unwrap();
1635        let file = entries
1636            .iter()
1637            .find(|entry| entry.name == "final.txt")
1638            .unwrap();
1639        let expected_hash = to_hex(&sha256(b"data"));
1640        assert_eq!(
1641            file.meta
1642                .as_ref()
1643                .and_then(|meta| meta.get("whole_file_hash"))
1644                .and_then(serde_json::Value::as_str),
1645            Some(expected_hash.as_str())
1646        );
1647    }
1648
1649    #[tokio::test]
1650    async fn test_truncate_file() {
1651        let store = Arc::new(MemoryStore::new());
1652        let root = empty_root(store.clone()).await;
1653        let fs = HashtreeFuse::new(store, root).unwrap();
1654
1655        let file = fs.create_file(ROOT_INODE, "file.bin").unwrap();
1656        fs.write_file(file.inode, 0, b"abcdef").unwrap();
1657        fs.truncate_file(file.inode, 3).unwrap();
1658        let read = fs.read_file(file.inode, 0, 10).unwrap();
1659        assert_eq!(read, b"abc");
1660    }
1661
1662    #[tokio::test]
1663    async fn test_directory_refresh_sentinel_is_virtual() {
1664        let store = Arc::new(MemoryStore::new());
1665        let root = empty_root(store.clone()).await;
1666        let fs = HashtreeFuse::new(store, root.clone()).unwrap();
1667
1668        let entries = fs.read_dir(ROOT_INODE).unwrap();
1669        assert!(!entries
1670            .iter()
1671            .any(|entry| entry.name == DIRECTORY_REFRESH_SENTINEL_NAME));
1672        let sentinel = fs
1673            .lookup_child(ROOT_INODE, DIRECTORY_REFRESH_SENTINEL_NAME)
1674            .unwrap();
1675        assert_eq!(sentinel.kind, EntryKind::File);
1676        assert!(fs.read_file(sentinel.inode, 0, 10).unwrap().is_empty());
1677        assert!(fs
1678            .create_file(ROOT_INODE, DIRECTORY_REFRESH_SENTINEL_NAME)
1679            .is_err());
1680        fs.unlink(ROOT_INODE, DIRECTORY_REFRESH_SENTINEL_NAME)
1681            .unwrap();
1682        assert_eq!(fs.current_root(), root);
1683    }
1684
1685    #[test]
1686    fn test_directory_refresh_sentinel_is_not_hidden() {
1687        assert!(!DIRECTORY_REFRESH_SENTINEL_NAME.starts_with('.'));
1688    }
1689
1690    #[tokio::test]
1691    async fn test_publisher_invoked() {
1692        let store = Arc::new(MemoryStore::new());
1693        let root = empty_root(store.clone()).await;
1694        let publisher = Arc::new(RecordingPublisher::new());
1695        let fs = HashtreeFuse::new_with_publisher(store, root, Some(publisher.clone())).unwrap();
1696
1697        let file = fs.create_file(ROOT_INODE, "note.txt").unwrap();
1698        fs.write_file(file.inode, 0, b"note").unwrap();
1699
1700        let updates = publisher.updates();
1701        assert!(!updates.is_empty());
1702        assert_eq!(updates.last().unwrap(), &fs.current_root());
1703    }
1704
1705    #[tokio::test]
1706    async fn test_replace_root_refreshes_visible_tree_without_publishing() {
1707        let store = Arc::new(MemoryStore::new());
1708        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1709        let root = empty_root(store.clone()).await;
1710        let publisher = Arc::new(RecordingPublisher::new());
1711        let fs = HashtreeFuse::new_with_publisher(store, root, Some(publisher.clone())).unwrap();
1712
1713        let old_file = fs.create_file(ROOT_INODE, "old.txt").unwrap();
1714        fs.write_file(old_file.inode, 0, b"old").unwrap();
1715        assert!(!publisher.updates().is_empty());
1716        publisher.updates.lock().unwrap().clear();
1717
1718        let (new_blob, new_size) = tree.put(b"new").await.unwrap();
1719        let new_root = tree
1720            .put_directory(vec![hashtree_core::DirEntry::from_cid(
1721                "new.txt", &new_blob,
1722            )
1723            .with_size(new_size)
1724            .with_link_type(LinkType::Blob)])
1725            .await
1726            .unwrap();
1727
1728        let old_lookup = fs.lookup_child(ROOT_INODE, "old.txt").unwrap();
1729        fs.replace_root(new_root.clone()).unwrap();
1730
1731        assert_eq!(fs.current_root(), new_root);
1732        assert!(fs.lookup_child(ROOT_INODE, "old.txt").is_err());
1733        let new_lookup = fs.lookup_child(ROOT_INODE, "new.txt").unwrap();
1734        assert_ne!(old_lookup.inode, new_lookup.inode);
1735        assert_eq!(fs.read_file(new_lookup.inode, 0, 3).unwrap(), b"new");
1736        assert!(publisher.updates().is_empty());
1737    }
1738
1739    #[tokio::test]
1740    async fn test_replace_root_if_current_preserves_dirty_root() {
1741        let store = Arc::new(MemoryStore::new());
1742        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1743        let root = empty_root(store.clone()).await;
1744        let fs = HashtreeFuse::new(store, root.clone()).unwrap();
1745
1746        let local = fs.create_file(ROOT_INODE, "local.txt").unwrap();
1747        fs.write_file(local.inode, 0, b"local").unwrap();
1748        let dirty_root = fs.current_root();
1749
1750        let (remote_blob, remote_size) = tree.put(b"remote").await.unwrap();
1751        let remote_root = tree
1752            .put_directory(vec![hashtree_core::DirEntry::from_cid(
1753                "remote.txt",
1754                &remote_blob,
1755            )
1756            .with_size(remote_size)
1757            .with_link_type(LinkType::Blob)])
1758            .await
1759            .unwrap();
1760
1761        let replaced = fs
1762            .replace_root_if_current(&root, remote_root.clone())
1763            .unwrap();
1764
1765        assert!(!replaced);
1766        assert_eq!(fs.current_root(), dirty_root);
1767        assert!(fs.lookup_child(ROOT_INODE, "local.txt").is_ok());
1768        assert!(fs.lookup_child(ROOT_INODE, "remote.txt").is_err());
1769
1770        let replaced = fs
1771            .replace_root_if_current(&dirty_root, remote_root.clone())
1772            .unwrap();
1773        assert!(replaced);
1774        assert_eq!(fs.current_root(), remote_root);
1775    }
1776
1777    #[cfg(feature = "fuse")]
1778    #[tokio::test]
1779    async fn test_replace_root_invalidates_removed_entries_without_inode_notification() {
1780        let store = Arc::new(MemoryStore::new());
1781        let root = empty_root(store.clone()).await;
1782        let fs = HashtreeFuse::new(store.clone(), root).unwrap();
1783
1784        let old_file = fs.create_file(ROOT_INODE, "old.txt").unwrap();
1785        fs.write_file(old_file.inode, 0, b"old").unwrap();
1786        let new_root = empty_root(store).await;
1787
1788        let invalidations = fs.changed_known_entries_for_root(&new_root);
1789
1790        assert!(invalidations.contains(&FuseInvalidation::Entry {
1791            parent: ROOT_INODE,
1792            name: "old.txt".to_string(),
1793        }));
1794        assert_eq!(invalidations.len(), 2);
1795    }
1796
1797    #[cfg(feature = "fuse")]
1798    #[tokio::test]
1799    async fn test_replace_root_emits_delete_invalidation_for_removed_entries() {
1800        let store = Arc::new(MemoryStore::new());
1801        let root = empty_root(store.clone()).await;
1802        let fs = HashtreeFuse::new(store.clone(), root).unwrap();
1803
1804        let old_file = fs.create_file(ROOT_INODE, "old.txt").unwrap();
1805        fs.write_file(old_file.inode, 0, b"old").unwrap();
1806        let new_root = empty_root(store).await;
1807
1808        let invalidations = fs.changed_known_entries_for_root(&new_root);
1809
1810        assert!(invalidations.contains(&FuseInvalidation::Delete {
1811            parent: ROOT_INODE,
1812            child: old_file.inode,
1813            name: "old.txt".to_string(),
1814        }));
1815        assert_eq!(
1816            fs.removed_known_entry_paths_for_root(&new_root),
1817            vec![vec!["old.txt".to_string()]]
1818        );
1819        let delete_index = invalidations
1820            .iter()
1821            .position(|invalidation| {
1822                matches!(invalidation, FuseInvalidation::Delete { name, .. } if name == "old.txt")
1823            })
1824            .unwrap();
1825        let entry_index = invalidations
1826            .iter()
1827            .position(|invalidation| {
1828                matches!(invalidation, FuseInvalidation::Entry { name, .. } if name == "old.txt")
1829            })
1830            .unwrap();
1831        assert!(entry_index < delete_index);
1832    }
1833
1834    #[cfg(feature = "fuse")]
1835    #[tokio::test]
1836    async fn test_replace_root_invalidates_changed_known_entry_by_name_and_new_inode() {
1837        let store = Arc::new(MemoryStore::new());
1838        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1839        let (old_blob, old_size) = tree.put(b"old").await.unwrap();
1840        let root = tree
1841            .put_directory(vec![hashtree_core::DirEntry::from_cid(
1842                "note.txt", &old_blob,
1843            )
1844            .with_size(old_size)
1845            .with_link_type(LinkType::Blob)])
1846            .await
1847            .unwrap();
1848        let fs = HashtreeFuse::new(store, root).unwrap();
1849        let old_file = fs.lookup_child(ROOT_INODE, "note.txt").unwrap();
1850
1851        let (new_blob, new_size) = tree.put(b"new").await.unwrap();
1852        let new_root = tree
1853            .put_directory(vec![hashtree_core::DirEntry::from_cid(
1854                "note.txt", &new_blob,
1855            )
1856            .with_size(new_size)
1857            .with_link_type(LinkType::Blob)])
1858            .await
1859            .unwrap();
1860
1861        let invalidations = fs.changed_known_entries_for_root(&new_root);
1862
1863        assert!(invalidations.contains(&FuseInvalidation::Entry {
1864            parent: ROOT_INODE,
1865            name: "note.txt".to_string(),
1866        }));
1867        assert_eq!(invalidations.len(), 1);
1868
1869        fs.replace_root(new_root).unwrap();
1870        let new_file = fs.lookup_child(ROOT_INODE, "note.txt").unwrap();
1871        assert_ne!(old_file.inode, new_file.inode);
1872        assert_eq!(fs.read_file(new_file.inode, 0, 3).unwrap(), b"new");
1873    }
1874
1875    #[cfg(feature = "fuse")]
1876    #[tokio::test]
1877    async fn test_replace_root_invalidates_removed_name_without_known_child_inode() {
1878        let store = Arc::new(MemoryStore::new());
1879        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1880        let (old_blob, old_size) = tree.put(b"old").await.unwrap();
1881        let root = tree
1882            .put_directory(vec![hashtree_core::DirEntry::from_cid(
1883                "old.txt", &old_blob,
1884            )
1885            .with_size(old_size)
1886            .with_link_type(LinkType::Blob)])
1887            .await
1888            .unwrap();
1889        let fs = HashtreeFuse::new(store.clone(), root).unwrap();
1890        let new_root = empty_root(store).await;
1891
1892        let invalidations = fs.changed_known_entries_for_root(&new_root);
1893
1894        assert!(invalidations.contains(&FuseInvalidation::Entry {
1895            parent: ROOT_INODE,
1896            name: "old.txt".to_string(),
1897        }));
1898    }
1899
1900    #[tokio::test]
1901    async fn test_refresh_unlink_does_not_publish_or_change_root() {
1902        let store = Arc::new(MemoryStore::new());
1903        let root = empty_root(store.clone()).await;
1904        let publisher = Arc::new(RecordingPublisher::new());
1905        let fs = HashtreeFuse::new_with_publisher(store, root, Some(publisher.clone())).unwrap();
1906
1907        let old_file = fs.create_file(ROOT_INODE, "old.txt").unwrap();
1908        fs.write_file(old_file.inode, 0, b"old").unwrap();
1909        let old_root = fs.current_root();
1910        publisher.updates.lock().unwrap().clear();
1911
1912        fs.begin_refresh_unlinks(vec![vec!["old.txt".to_string()]]);
1913        fs.unlink(ROOT_INODE, "old.txt").unwrap();
1914
1915        assert_eq!(fs.current_root(), old_root);
1916        assert!(publisher.updates().is_empty());
1917        assert!(fs.lookup_child(ROOT_INODE, "old.txt").is_ok());
1918    }
1919
1920    #[cfg(feature = "fuse")]
1921    #[tokio::test]
1922    async fn test_replace_root_invalidates_added_entries_by_name() {
1923        let store = Arc::new(MemoryStore::new());
1924        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1925        let root = empty_root(store.clone()).await;
1926        let fs = HashtreeFuse::new(store, root).unwrap();
1927
1928        let (new_blob, new_size) = tree.put(b"new").await.unwrap();
1929        let new_root = tree
1930            .put_directory(vec![hashtree_core::DirEntry::from_cid(
1931                "new.txt", &new_blob,
1932            )
1933            .with_size(new_size)
1934            .with_link_type(LinkType::Blob)])
1935            .await
1936            .unwrap();
1937
1938        let invalidations = fs.changed_known_entries_for_root(&new_root);
1939
1940        assert!(invalidations.contains(&FuseInvalidation::Entry {
1941            parent: ROOT_INODE,
1942            name: "new.txt".to_string(),
1943        }));
1944    }
1945
1946    #[tokio::test]
1947    async fn test_replace_root_preserves_existing_directory_inodes() {
1948        let store = Arc::new(MemoryStore::new());
1949        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1950        let root = empty_root(store.clone()).await;
1951        let fs = HashtreeFuse::new(store, root).unwrap();
1952
1953        let docs = fs.mkdir(ROOT_INODE, "docs").unwrap();
1954        let old_file = fs.create_file(docs.inode, "old.txt").unwrap();
1955        fs.write_file(old_file.inode, 0, b"old").unwrap();
1956        let old_entries = fs.read_dir(docs.inode).unwrap();
1957        assert!(old_entries.iter().any(|entry| entry.name == "old.txt"));
1958
1959        let (new_blob, new_size) = tree.put(b"new").await.unwrap();
1960        let new_docs = tree
1961            .put_directory(vec![hashtree_core::DirEntry::from_cid(
1962                "new.txt", &new_blob,
1963            )
1964            .with_size(new_size)
1965            .with_link_type(LinkType::Blob)])
1966            .await
1967            .unwrap();
1968        let new_root = tree
1969            .put_directory(vec![
1970                hashtree_core::DirEntry::from_cid("docs", &new_docs).with_link_type(LinkType::Dir)
1971            ])
1972            .await
1973            .unwrap();
1974
1975        fs.replace_root(new_root).unwrap();
1976
1977        assert_eq!(
1978            fs.lookup_child(ROOT_INODE, "docs").unwrap().inode,
1979            docs.inode
1980        );
1981        assert_eq!(fs.get_attr(docs.inode).unwrap().kind, EntryKind::Directory);
1982        let new_entries = fs.read_dir(docs.inode).unwrap();
1983        let names: Vec<String> = new_entries.into_iter().map(|entry| entry.name).collect();
1984        assert!(names.contains(&"new.txt".to_string()));
1985        assert!(!names.contains(&"old.txt".to_string()));
1986    }
1987
1988    #[cfg(feature = "fuse")]
1989    #[test]
1990    fn test_fallback_fs_stats_reports_free_space() {
1991        let stats = fallback_fs_stats();
1992        assert!(stats.blocks > 0);
1993        assert!(stats.bfree > 0);
1994        assert!(stats.bavail > 0);
1995        assert!(stats.bsize > 0);
1996        assert_eq!(stats.bfree, stats.bavail);
1997    }
1998
1999    #[cfg(feature = "fuse")]
2000    #[test]
2001    fn test_current_fs_stats_reports_free_space() {
2002        let stats = current_fs_stats();
2003        assert!(stats.blocks > 0);
2004        assert!(stats.bfree > 0);
2005        assert!(stats.bavail > 0);
2006        assert!(stats.bsize > 0);
2007    }
2008}