Skip to main content

hashtree_fuse/
lib.rs

1use std::collections::HashMap;
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::{Cid, HashTree, HashTreeConfig, HashTreeError, LinkType, Store};
8use thiserror::Error;
9
10pub const ROOT_INODE: u64 = 1;
11
12#[derive(Debug, Error)]
13pub enum FsError {
14    #[error("root hash is not a directory")]
15    InvalidRoot,
16    #[error("entry not found")]
17    NotFound,
18    #[error("not a directory")]
19    NotDir,
20    #[error("is a directory")]
21    IsDir,
22    #[error("entry already exists")]
23    AlreadyExists,
24    #[error("directory not empty")]
25    NotEmpty,
26    #[error("invalid entry name")]
27    InvalidName,
28    #[error("tree error: {0}")]
29    Tree(String),
30    #[error("publish error: {0}")]
31    Publish(String),
32}
33
34impl From<HashTreeError> for FsError {
35    fn from(err: HashTreeError) -> Self {
36        FsError::Tree(err.to_string())
37    }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum EntryKind {
42    File,
43    Directory,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct EntryAttr {
48    pub inode: u64,
49    pub size: u64,
50    pub kind: EntryKind,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct DirEntry {
55    pub inode: u64,
56    pub name: String,
57    pub kind: EntryKind,
58}
59
60pub trait RootPublisher: Send + Sync {
61    fn publish(&self, cid: &Cid) -> Result<(), FsError>;
62}
63
64#[derive(Debug, Clone, Eq)]
65struct ChildKey {
66    parent: u64,
67    name: String,
68}
69
70impl PartialEq for ChildKey {
71    fn eq(&self, other: &Self) -> bool {
72        self.parent == other.parent && self.name == other.name
73    }
74}
75
76impl StdHash for ChildKey {
77    fn hash<H: Hasher>(&self, state: &mut H) {
78        self.parent.hash(state);
79        self.name.hash(state);
80    }
81}
82
83struct ResolvedEntry {
84    cid: Cid,
85    link_type: LinkType,
86    size: u64,
87}
88
89pub struct HashtreeFuse<S: Store> {
90    tree: HashTree<S>,
91    root: RwLock<Cid>,
92    paths: RwLock<HashMap<u64, Vec<String>>>,
93    children: RwLock<HashMap<ChildKey, u64>>,
94    parents: RwLock<HashMap<u64, u64>>,
95    next_inode: AtomicU64,
96    publisher: Option<Arc<dyn RootPublisher>>,
97    modify_lock: Mutex<()>,
98}
99
100impl<S: Store> HashtreeFuse<S> {
101    pub fn new(store: Arc<S>, root: Cid) -> Result<Self, FsError> {
102        Self::new_with_publisher(store, root, None)
103    }
104
105    pub fn new_with_publisher(
106        store: Arc<S>,
107        root: Cid,
108        publisher: Option<Arc<dyn RootPublisher>>,
109    ) -> Result<Self, FsError> {
110        let mut config = HashTreeConfig::new(store);
111        if root.key.is_none() {
112            config = config.public();
113        }
114        let tree = HashTree::new(config);
115
116        let is_dir = block_on(tree.get_directory_node(&root))?.is_some();
117        if !is_dir {
118            return Err(FsError::InvalidRoot);
119        }
120
121        let mut paths = HashMap::new();
122        paths.insert(ROOT_INODE, Vec::new());
123        let mut parents = HashMap::new();
124        parents.insert(ROOT_INODE, ROOT_INODE);
125
126        Ok(Self {
127            tree,
128            root: RwLock::new(root),
129            paths: RwLock::new(paths),
130            children: RwLock::new(HashMap::new()),
131            parents: RwLock::new(parents),
132            next_inode: AtomicU64::new(ROOT_INODE + 1),
133            publisher,
134            modify_lock: Mutex::new(()),
135        })
136    }
137
138    pub fn current_root(&self) -> Cid {
139        self.root.read().unwrap().clone()
140    }
141
142    pub fn lookup_child(&self, parent: u64, name: &str) -> Result<EntryAttr, FsError> {
143        if name.is_empty() {
144            return Err(FsError::NotFound);
145        }
146        if name == "." {
147            return self.get_attr(parent);
148        }
149        if name == ".." {
150            let parent_inode = self.parent_inode(parent);
151            return self.get_attr(parent_inode);
152        }
153
154        let child_inode = self.get_or_create_child_inode(parent, name)?;
155        let path = self.path_for_inode(child_inode)?;
156
157        match self.resolve_entry(&path) {
158            Ok(entry) => self.entry_attr_from_resolved(child_inode, entry),
159            Err(FsError::NotFound) => {
160                self.drop_inode(child_inode);
161                Err(FsError::NotFound)
162            }
163            Err(err) => Err(err),
164        }
165    }
166
167    pub fn get_attr(&self, inode: u64) -> Result<EntryAttr, FsError> {
168        if inode == ROOT_INODE {
169            return Ok(EntryAttr {
170                inode,
171                size: 0,
172                kind: EntryKind::Directory,
173            });
174        }
175
176        let path = self.path_for_inode(inode)?;
177        let entry = self.resolve_entry(&path)?;
178        self.entry_attr_from_resolved(inode, entry)
179    }
180
181    pub fn read_file(&self, inode: u64, offset: u64, size: u32) -> Result<Vec<u8>, FsError> {
182        let path = self.path_for_inode(inode)?;
183        let entry = self.resolve_entry(&path)?;
184        if entry.link_type == LinkType::Dir {
185            return Err(FsError::IsDir);
186        }
187
188        let file_size = self.entry_size(&entry)?;
189        if offset >= file_size {
190            return Ok(vec![]);
191        }
192        let read_len = (size as u64).min(file_size - offset);
193        if read_len == 0 {
194            return Ok(vec![]);
195        }
196
197        if entry.cid.key.is_some() {
198            let data = block_on(self.tree.get(&entry.cid, None))?.ok_or(FsError::NotFound)?;
199            let start = usize::try_from(offset).unwrap_or(usize::MAX);
200            if start >= data.len() {
201                return Ok(vec![]);
202            }
203            let end_u64 = offset.saturating_add(read_len);
204            let mut end = usize::try_from(end_u64).unwrap_or(data.len());
205            if end > data.len() {
206                end = data.len();
207            }
208            return Ok(data[start..end].to_vec());
209        }
210
211        let end = offset.saturating_add(read_len);
212        let data = block_on(
213            self.tree
214                .read_file_range(&entry.cid.hash, offset, Some(end)),
215        )?
216        .ok_or(FsError::NotFound)?;
217        Ok(data)
218    }
219
220    pub fn read_dir(&self, inode: u64) -> Result<Vec<DirEntry>, FsError> {
221        let path = self.path_for_inode(inode)?;
222        let dir_cid = self.resolve_dir_cid(&path)?;
223
224        let entries = block_on(self.tree.list_directory(&dir_cid))?;
225        let mut out = Vec::with_capacity(entries.len());
226
227        for entry in entries {
228            let child_inode = self.get_or_create_child_inode(inode, &entry.name)?;
229            out.push(DirEntry {
230                inode: child_inode,
231                name: entry.name,
232                kind: Self::kind_from_link(entry.link_type),
233            });
234        }
235
236        Ok(out)
237    }
238
239    pub fn create_file(&self, parent: u64, name: &str) -> Result<EntryAttr, FsError> {
240        self.ensure_valid_name(name)?;
241        let _guard = self.modify_lock.lock().unwrap();
242
243        let parent_path = self.path_for_inode(parent)?;
244        let mut child_path = parent_path.clone();
245        child_path.push(name.to_string());
246
247        if self.resolve_entry(&child_path).is_ok() {
248            return Err(FsError::AlreadyExists);
249        }
250
251        let (cid, size) = block_on(self.tree.put(&[]))?;
252        let link_type = self.link_type_for_size(size);
253        let new_root = block_on(self.tree.set_entry(
254            &self.current_root(),
255            &self.path_refs(&parent_path),
256            name,
257            &cid,
258            size,
259            link_type,
260        ))?;
261
262        self.apply_root_update(new_root)?;
263
264        let inode = self.insert_path(parent, name.to_string(), child_path);
265        Ok(EntryAttr {
266            inode,
267            size,
268            kind: EntryKind::File,
269        })
270    }
271
272    pub fn mkdir(&self, parent: u64, name: &str) -> Result<EntryAttr, FsError> {
273        self.ensure_valid_name(name)?;
274        let _guard = self.modify_lock.lock().unwrap();
275
276        let parent_path = self.path_for_inode(parent)?;
277        let mut child_path = parent_path.clone();
278        child_path.push(name.to_string());
279
280        if self.resolve_entry(&child_path).is_ok() {
281            return Err(FsError::AlreadyExists);
282        }
283
284        let dir_cid = block_on(self.tree.put_directory(Vec::new()))?;
285        let new_root = block_on(self.tree.set_entry(
286            &self.current_root(),
287            &self.path_refs(&parent_path),
288            name,
289            &dir_cid,
290            0,
291            LinkType::Dir,
292        ))?;
293
294        self.apply_root_update(new_root)?;
295
296        let inode = self.insert_path(parent, name.to_string(), child_path);
297        Ok(EntryAttr {
298            inode,
299            size: 0,
300            kind: EntryKind::Directory,
301        })
302    }
303
304    pub fn write_file(&self, inode: u64, offset: u64, data: &[u8]) -> Result<u32, FsError> {
305        let _guard = self.modify_lock.lock().unwrap();
306        let path = self.path_for_inode(inode)?;
307        let existing = self.read_file_full(&path)?;
308        let new_data = Self::apply_write(existing, offset, data);
309        self.update_file_at_path(&path, new_data)?;
310        Ok(data.len() as u32)
311    }
312
313    pub fn truncate_file(&self, inode: u64, size: u64) -> Result<(), FsError> {
314        let _guard = self.modify_lock.lock().unwrap();
315        let path = self.path_for_inode(inode)?;
316        let existing = self.read_file_full(&path)?;
317        let new_data = Self::apply_truncate(existing, size);
318        self.update_file_at_path(&path, new_data)?;
319        Ok(())
320    }
321
322    pub fn unlink(&self, parent: u64, name: &str) -> Result<(), FsError> {
323        self.ensure_valid_name(name)?;
324        let _guard = self.modify_lock.lock().unwrap();
325
326        let parent_path = self.path_for_inode(parent)?;
327        let mut child_path = parent_path.clone();
328        child_path.push(name.to_string());
329        let entry = self.resolve_entry(&child_path)?;
330        if entry.link_type == LinkType::Dir {
331            return Err(FsError::IsDir);
332        }
333
334        let new_root = block_on(self.tree.remove_entry(
335            &self.current_root(),
336            &self.path_refs(&parent_path),
337            name,
338        ))?;
339
340        self.apply_root_update(new_root)?;
341        self.remove_paths_prefix(&child_path);
342        self.children.write().unwrap().remove(&ChildKey {
343            parent,
344            name: name.to_string(),
345        });
346
347        Ok(())
348    }
349
350    pub fn rmdir(&self, parent: u64, name: &str) -> Result<(), FsError> {
351        self.ensure_valid_name(name)?;
352        let _guard = self.modify_lock.lock().unwrap();
353
354        let parent_path = self.path_for_inode(parent)?;
355        let mut child_path = parent_path.clone();
356        child_path.push(name.to_string());
357        let entry = self.resolve_entry(&child_path)?;
358        if entry.link_type != LinkType::Dir {
359            return Err(FsError::NotDir);
360        }
361
362        let dir_entries = block_on(self.tree.list_directory(&entry.cid))?;
363        if !dir_entries.is_empty() {
364            return Err(FsError::NotEmpty);
365        }
366
367        let new_root = block_on(self.tree.remove_entry(
368            &self.current_root(),
369            &self.path_refs(&parent_path),
370            name,
371        ))?;
372
373        self.apply_root_update(new_root)?;
374        self.remove_paths_prefix(&child_path);
375        self.children.write().unwrap().remove(&ChildKey {
376            parent,
377            name: name.to_string(),
378        });
379
380        Ok(())
381    }
382
383    pub fn rename(
384        &self,
385        parent: u64,
386        name: &str,
387        new_parent: u64,
388        new_name: &str,
389    ) -> Result<(), FsError> {
390        self.ensure_valid_name(name)?;
391        self.ensure_valid_name(new_name)?;
392        let _guard = self.modify_lock.lock().unwrap();
393
394        if parent == new_parent && name == new_name {
395            return Ok(());
396        }
397
398        let parent_path = self.path_for_inode(parent)?;
399        let new_parent_path = self.path_for_inode(new_parent)?;
400
401        let mut old_path = parent_path.clone();
402        old_path.push(name.to_string());
403        let entry = self.resolve_entry(&old_path)?;
404
405        let new_root = block_on(self.tree.set_entry(
406            &self.current_root(),
407            &self.path_refs(&new_parent_path),
408            new_name,
409            &entry.cid,
410            entry.size,
411            entry.link_type,
412        ))?;
413        let new_root = block_on(self.tree.remove_entry(
414            &new_root,
415            &self.path_refs(&parent_path),
416            name,
417        ))?;
418
419        self.apply_root_update(new_root)?;
420
421        let inode = self.get_or_create_child_inode(parent, name)?;
422        let mut new_path = new_parent_path.clone();
423        new_path.push(new_name.to_string());
424
425        self.children.write().unwrap().remove(&ChildKey {
426            parent,
427            name: name.to_string(),
428        });
429        self.children.write().unwrap().insert(
430            ChildKey {
431                parent: new_parent,
432                name: new_name.to_string(),
433            },
434            inode,
435        );
436
437        self.parents.write().unwrap().insert(inode, new_parent);
438        self.update_paths_prefix(&old_path, &new_path);
439
440        Ok(())
441    }
442
443    fn ensure_valid_name(&self, name: &str) -> Result<(), FsError> {
444        if name.is_empty() || name.contains('/') {
445            return Err(FsError::InvalidName);
446        }
447        Ok(())
448    }
449
450    fn path_for_inode(&self, inode: u64) -> Result<Vec<String>, FsError> {
451        self.paths
452            .read()
453            .unwrap()
454            .get(&inode)
455            .cloned()
456            .ok_or(FsError::NotFound)
457    }
458
459    fn resolve_entry(&self, path: &[String]) -> Result<ResolvedEntry, FsError> {
460        if path.is_empty() {
461            return Ok(ResolvedEntry {
462                cid: self.current_root(),
463                link_type: LinkType::Dir,
464                size: 0,
465            });
466        }
467
468        let (parent_path, name) = path.split_at(path.len() - 1);
469        let parent_cid = self.resolve_dir_cid(parent_path)?;
470        let entries = block_on(self.tree.list_directory(&parent_cid))?;
471        let entry = entries
472            .into_iter()
473            .find(|e| e.name == name[0])
474            .ok_or(FsError::NotFound)?;
475
476        Ok(ResolvedEntry {
477            cid: Cid {
478                hash: entry.hash,
479                key: entry.key,
480            },
481            link_type: entry.link_type,
482            size: entry.size,
483        })
484    }
485
486    fn resolve_dir_cid(&self, path: &[String]) -> Result<Cid, FsError> {
487        if path.is_empty() {
488            return Ok(self.current_root());
489        }
490
491        let root = self.current_root();
492        let path_str = path.join("/");
493        let cid = block_on(self.tree.resolve(&root, &path_str))?.ok_or(FsError::NotFound)?;
494
495        let is_dir = block_on(self.tree.is_dir(&cid))?;
496        if !is_dir {
497            return Err(FsError::NotDir);
498        }
499
500        Ok(cid)
501    }
502
503    fn entry_attr_from_resolved(
504        &self,
505        inode: u64,
506        entry: ResolvedEntry,
507    ) -> Result<EntryAttr, FsError> {
508        let kind = Self::kind_from_link(entry.link_type);
509        let size = if kind == EntryKind::Directory {
510            0
511        } else {
512            self.entry_size(&entry)?
513        };
514
515        Ok(EntryAttr { inode, size, kind })
516    }
517
518    fn entry_size(&self, entry: &ResolvedEntry) -> Result<u64, FsError> {
519        if entry.link_type == LinkType::Dir {
520            return Ok(0);
521        }
522        if entry.size > 0 {
523            return Ok(entry.size);
524        }
525
526        let data = block_on(self.tree.get(&entry.cid, None))?.ok_or(FsError::NotFound)?;
527        Ok(data.len() as u64)
528    }
529
530    fn read_file_full(&self, path: &[String]) -> Result<Vec<u8>, FsError> {
531        let entry = self.resolve_entry(path)?;
532        if entry.link_type == LinkType::Dir {
533            return Err(FsError::IsDir);
534        }
535        let data = block_on(self.tree.get(&entry.cid, None))?.ok_or(FsError::NotFound)?;
536        Ok(data)
537    }
538
539    fn update_file_at_path(&self, path: &[String], data: Vec<u8>) -> Result<(), FsError> {
540        let (parent_path, name) = path.split_at(path.len() - 1);
541        let (cid, size) = block_on(self.tree.put(&data))?;
542        let link_type = self.link_type_for_size(size);
543
544        let new_root = block_on(self.tree.set_entry(
545            &self.current_root(),
546            &self.path_refs(parent_path),
547            name[0].as_str(),
548            &cid,
549            size,
550            link_type,
551        ))?;
552
553        self.apply_root_update(new_root)
554    }
555
556    fn apply_root_update(&self, new_root: Cid) -> Result<(), FsError> {
557        if let Some(publisher) = &self.publisher {
558            publisher.publish(&new_root)?;
559        }
560        *self.root.write().unwrap() = new_root;
561        Ok(())
562    }
563
564    fn link_type_for_size(&self, size: u64) -> LinkType {
565        if size as usize > self.tree.chunk_size() {
566            LinkType::File
567        } else {
568            LinkType::Blob
569        }
570    }
571
572    fn get_or_create_child_inode(&self, parent: u64, name: &str) -> Result<u64, FsError> {
573        let key = ChildKey {
574            parent,
575            name: name.to_string(),
576        };
577        if let Some(inode) = self.children.read().unwrap().get(&key).copied() {
578            return Ok(inode);
579        }
580
581        let parent_path = self.path_for_inode(parent)?;
582        let mut child_path = parent_path.clone();
583        child_path.push(name.to_string());
584
585        if let Some(existing) = self.find_inode_by_path(&child_path) {
586            self.children.write().unwrap().insert(key, existing);
587            return Ok(existing);
588        }
589
590        Ok(self.insert_path(parent, name.to_string(), child_path))
591    }
592
593    fn insert_path(&self, parent: u64, name: String, path: Vec<String>) -> u64 {
594        let inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
595        self.paths.write().unwrap().insert(inode, path);
596        self.parents.write().unwrap().insert(inode, parent);
597        self.children
598            .write()
599            .unwrap()
600            .insert(ChildKey { parent, name }, inode);
601        inode
602    }
603
604    fn find_inode_by_path(&self, path: &[String]) -> Option<u64> {
605        self.paths
606            .read()
607            .unwrap()
608            .iter()
609            .find_map(|(inode, inode_path)| {
610                if inode_path == path {
611                    Some(*inode)
612                } else {
613                    None
614                }
615            })
616    }
617
618    fn update_paths_prefix(&self, old_prefix: &[String], new_prefix: &[String]) {
619        let mut paths = self.paths.write().unwrap();
620        for path in paths.values_mut() {
621            if Self::path_has_prefix(path, old_prefix) {
622                let mut updated = new_prefix.to_vec();
623                updated.extend_from_slice(&path[old_prefix.len()..]);
624                *path = updated;
625            }
626        }
627    }
628
629    fn remove_paths_prefix(&self, prefix: &[String]) {
630        let mut to_remove = Vec::new();
631        {
632            let paths = self.paths.read().unwrap();
633            for (inode, path) in paths.iter() {
634                if *inode == ROOT_INODE {
635                    continue;
636                }
637                if Self::path_has_prefix(path, prefix) {
638                    to_remove.push(*inode);
639                }
640            }
641        }
642
643        if to_remove.is_empty() {
644            return;
645        }
646
647        let remove_set: std::collections::HashSet<u64> = to_remove.into_iter().collect();
648        self.paths
649            .write()
650            .unwrap()
651            .retain(|inode, _| !remove_set.contains(inode));
652        self.parents
653            .write()
654            .unwrap()
655            .retain(|inode, _| !remove_set.contains(inode));
656        self.children
657            .write()
658            .unwrap()
659            .retain(|_, inode| !remove_set.contains(inode));
660    }
661
662    fn drop_inode(&self, inode: u64) {
663        if inode == ROOT_INODE {
664            return;
665        }
666        let mut paths = self.paths.write().unwrap();
667        let removed_path = paths.remove(&inode);
668        drop(paths);
669        self.parents.write().unwrap().remove(&inode);
670        if let Some(path) = removed_path {
671            if let Some((name, parent_path)) = path.split_last() {
672                if let Some(parent_inode) = self.find_inode_by_path(parent_path) {
673                    self.children.write().unwrap().remove(&ChildKey {
674                        parent: parent_inode,
675                        name: name.to_string(),
676                    });
677                }
678            }
679        }
680    }
681
682    fn parent_inode(&self, inode: u64) -> u64 {
683        self.parents
684            .read()
685            .unwrap()
686            .get(&inode)
687            .copied()
688            .unwrap_or(ROOT_INODE)
689    }
690
691    fn kind_from_link(link_type: LinkType) -> EntryKind {
692        match link_type {
693            LinkType::Dir => EntryKind::Directory,
694            LinkType::Blob | LinkType::File => EntryKind::File,
695        }
696    }
697
698    fn apply_write(mut existing: Vec<u8>, offset: u64, data: &[u8]) -> Vec<u8> {
699        let offset_usize = offset as usize;
700        if existing.len() < offset_usize {
701            existing.resize(offset_usize, 0);
702        }
703        if existing.len() < offset_usize + data.len() {
704            existing.resize(offset_usize + data.len(), 0);
705        }
706        existing[offset_usize..offset_usize + data.len()].copy_from_slice(data);
707        existing
708    }
709
710    fn apply_truncate(mut existing: Vec<u8>, size: u64) -> Vec<u8> {
711        let size = size as usize;
712        if existing.len() > size {
713            existing.truncate(size);
714        } else if existing.len() < size {
715            existing.resize(size, 0);
716        }
717        existing
718    }
719
720    fn path_refs<'a>(&self, path: &'a [String]) -> Vec<&'a str> {
721        path.iter().map(|p| p.as_str()).collect()
722    }
723
724    fn path_has_prefix(path: &[String], prefix: &[String]) -> bool {
725        if prefix.len() > path.len() {
726            return false;
727        }
728        path.iter().zip(prefix.iter()).all(|(a, b)| a == b)
729    }
730}
731
732#[cfg(feature = "fuse")]
733mod fuse_impl {
734    use super::*;
735    use fuser::{
736        FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyCreate, ReplyData,
737        ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyStatfs, ReplyWrite, Request,
738    };
739    use std::ffi::CString;
740    use std::ffi::OsStr;
741    use std::path::Path;
742    use std::time::{Duration, SystemTime};
743
744    const TTL: Duration = Duration::from_secs(1);
745    const FALLBACK_BLOCK_SIZE: u32 = 4096;
746    const FALLBACK_TOTAL_BYTES: u64 = 1 << 40;
747    const FALLBACK_FREE_BYTES: u64 = 1 << 39;
748    const FALLBACK_TOTAL_FILES: u64 = 1_000_000;
749    const FALLBACK_FREE_FILES: u64 = 900_000;
750
751    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
752    pub(crate) struct FsStats {
753        pub(crate) blocks: u64,
754        pub(crate) bfree: u64,
755        pub(crate) bavail: u64,
756        pub(crate) files: u64,
757        pub(crate) ffree: u64,
758        pub(crate) bsize: u32,
759        pub(crate) namelen: u32,
760        pub(crate) frsize: u32,
761    }
762
763    impl FsError {
764        fn errno(&self) -> i32 {
765            match self {
766                FsError::InvalidRoot | FsError::InvalidName => libc::EINVAL,
767                FsError::NotFound => libc::ENOENT,
768                FsError::NotDir => libc::ENOTDIR,
769                FsError::IsDir => libc::EISDIR,
770                FsError::AlreadyExists => libc::EEXIST,
771                FsError::NotEmpty => libc::ENOTEMPTY,
772                FsError::Tree(_) | FsError::Publish(_) => libc::EIO,
773            }
774        }
775    }
776
777    pub(crate) fn fallback_fs_stats() -> FsStats {
778        let blocks = FALLBACK_TOTAL_BYTES / u64::from(FALLBACK_BLOCK_SIZE);
779        let free_blocks = FALLBACK_FREE_BYTES / u64::from(FALLBACK_BLOCK_SIZE);
780        FsStats {
781            blocks,
782            bfree: free_blocks,
783            bavail: free_blocks,
784            files: FALLBACK_TOTAL_FILES,
785            ffree: FALLBACK_FREE_FILES,
786            bsize: FALLBACK_BLOCK_SIZE,
787            namelen: 255,
788            frsize: FALLBACK_BLOCK_SIZE,
789        }
790    }
791
792    fn host_fs_stats(path: &Path) -> Option<FsStats> {
793        let path = CString::new(path.as_os_str().as_encoded_bytes()).ok()?;
794        let mut stat = std::mem::MaybeUninit::<libc::statfs>::uninit();
795        let result = unsafe { libc::statfs(path.as_ptr(), stat.as_mut_ptr()) };
796        if result != 0 {
797            return None;
798        }
799
800        let stat = unsafe { stat.assume_init() };
801        let bsize = u32::try_from(stat.f_bsize).ok()?;
802        Some(FsStats {
803            blocks: stat.f_blocks,
804            bfree: stat.f_bfree,
805            bavail: stat.f_bavail,
806            files: stat.f_files,
807            ffree: stat.f_ffree,
808            bsize,
809            namelen: 255,
810            frsize: bsize,
811        })
812    }
813
814    pub(crate) fn current_fs_stats() -> FsStats {
815        host_fs_stats(Path::new("/")).unwrap_or_else(fallback_fs_stats)
816    }
817
818    impl<S: Store + Send + Sync + 'static> HashtreeFuse<S> {
819        pub fn mount(
820            self,
821            mountpoint: impl AsRef<Path>,
822            options: &[MountOption],
823        ) -> std::io::Result<()> {
824            fuser::mount2(self, mountpoint, options)
825        }
826
827        fn file_attr(&self, attr: &EntryAttr) -> FileAttr {
828            let (kind, perm, nlink) = match attr.kind {
829                EntryKind::Directory => (FileType::Directory, 0o755, 2),
830                EntryKind::File => (FileType::RegularFile, 0o644, 1),
831            };
832            let uid = unsafe { libc::geteuid() };
833            let gid = unsafe { libc::getegid() };
834            let blocks = attr.size.div_ceil(512);
835
836            FileAttr {
837                ino: attr.inode,
838                size: attr.size,
839                blocks,
840                atime: SystemTime::UNIX_EPOCH,
841                mtime: SystemTime::UNIX_EPOCH,
842                ctime: SystemTime::UNIX_EPOCH,
843                crtime: SystemTime::UNIX_EPOCH,
844                kind,
845                perm,
846                nlink,
847                uid,
848                gid,
849                rdev: 0,
850                blksize: 512,
851                flags: 0,
852            }
853        }
854
855        fn file_type(kind: EntryKind) -> FileType {
856            match kind {
857                EntryKind::Directory => FileType::Directory,
858                EntryKind::File => FileType::RegularFile,
859            }
860        }
861    }
862
863    impl<S: Store + Send + Sync + 'static> Filesystem for HashtreeFuse<S> {
864        fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
865            let name = match name.to_str() {
866                Some(value) => value,
867                None => {
868                    reply.error(libc::ENOENT);
869                    return;
870                }
871            };
872
873            match self.lookup_child(parent, name) {
874                Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
875                Err(err) => reply.error(err.errno()),
876            }
877        }
878
879        fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
880            match self.get_attr(ino) {
881                Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
882                Err(err) => reply.error(err.errno()),
883            }
884        }
885
886        fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen) {
887            reply.opened(0, 0);
888        }
889
890        fn read(
891            &mut self,
892            _req: &Request<'_>,
893            ino: u64,
894            _fh: u64,
895            offset: i64,
896            size: u32,
897            _flags: i32,
898            _lock_owner: Option<u64>,
899            reply: ReplyData,
900        ) {
901            let offset = if offset < 0 { 0 } else { offset as u64 };
902            match self.read_file(ino, offset, size) {
903                Ok(data) => reply.data(&data),
904                Err(err) => reply.error(err.errno()),
905            }
906        }
907
908        fn write(
909            &mut self,
910            _req: &Request<'_>,
911            ino: u64,
912            _fh: u64,
913            offset: i64,
914            data: &[u8],
915            _write_flags: u32,
916            _flags: i32,
917            _lock_owner: Option<u64>,
918            reply: ReplyWrite,
919        ) {
920            let offset = if offset < 0 { 0 } else { offset as u64 };
921            match self.write_file(ino, offset, data) {
922                Ok(written) => reply.written(written),
923                Err(err) => reply.error(err.errno()),
924            }
925        }
926
927        fn create(
928            &mut self,
929            _req: &Request<'_>,
930            parent: u64,
931            name: &OsStr,
932            _mode: u32,
933            _umask: u32,
934            _flags: i32,
935            reply: ReplyCreate,
936        ) {
937            let name = match name.to_str() {
938                Some(value) => value,
939                None => {
940                    reply.error(libc::EINVAL);
941                    return;
942                }
943            };
944
945            match self.create_file(parent, name) {
946                Ok(attr) => reply.created(&TTL, &self.file_attr(&attr), 0, 0, 0),
947                Err(err) => reply.error(err.errno()),
948            }
949        }
950
951        fn mkdir(
952            &mut self,
953            _req: &Request<'_>,
954            parent: u64,
955            name: &OsStr,
956            _mode: u32,
957            _umask: u32,
958            reply: ReplyEntry,
959        ) {
960            let name = match name.to_str() {
961                Some(value) => value,
962                None => {
963                    reply.error(libc::EINVAL);
964                    return;
965                }
966            };
967
968            match HashtreeFuse::mkdir(self, parent, name) {
969                Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
970                Err(err) => reply.error(err.errno()),
971            }
972        }
973
974        fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
975            let name = match name.to_str() {
976                Some(value) => value,
977                None => {
978                    reply.error(libc::EINVAL);
979                    return;
980                }
981            };
982
983            match HashtreeFuse::unlink(self, parent, name) {
984                Ok(()) => reply.ok(),
985                Err(err) => reply.error(err.errno()),
986            }
987        }
988
989        fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
990            let name = match name.to_str() {
991                Some(value) => value,
992                None => {
993                    reply.error(libc::EINVAL);
994                    return;
995                }
996            };
997
998            match HashtreeFuse::rmdir(self, parent, name) {
999                Ok(()) => reply.ok(),
1000                Err(err) => reply.error(err.errno()),
1001            }
1002        }
1003
1004        fn rename(
1005            &mut self,
1006            _req: &Request<'_>,
1007            parent: u64,
1008            name: &OsStr,
1009            newparent: u64,
1010            newname: &OsStr,
1011            _flags: u32,
1012            reply: ReplyEmpty,
1013        ) {
1014            let name = match name.to_str() {
1015                Some(value) => value,
1016                None => {
1017                    reply.error(libc::EINVAL);
1018                    return;
1019                }
1020            };
1021            let newname = match newname.to_str() {
1022                Some(value) => value,
1023                None => {
1024                    reply.error(libc::EINVAL);
1025                    return;
1026                }
1027            };
1028
1029            match HashtreeFuse::rename(self, parent, name, newparent, newname) {
1030                Ok(()) => reply.ok(),
1031                Err(err) => reply.error(err.errno()),
1032            }
1033        }
1034
1035        fn setattr(
1036            &mut self,
1037            _req: &Request<'_>,
1038            ino: u64,
1039            _mode: Option<u32>,
1040            _uid: Option<u32>,
1041            _gid: Option<u32>,
1042            size: Option<u64>,
1043            _atime: Option<fuser::TimeOrNow>,
1044            _mtime: Option<fuser::TimeOrNow>,
1045            _ctime: Option<SystemTime>,
1046            _fh: Option<u64>,
1047            _crtime: Option<SystemTime>,
1048            _chgtime: Option<SystemTime>,
1049            _bkuptime: Option<SystemTime>,
1050            _flags: Option<u32>,
1051            reply: ReplyAttr,
1052        ) {
1053            if let Some(size) = size {
1054                match self.truncate_file(ino, size) {
1055                    Ok(()) => {
1056                        if let Ok(attr) = self.get_attr(ino) {
1057                            reply.attr(&TTL, &self.file_attr(&attr));
1058                        } else {
1059                            reply.error(libc::EIO);
1060                        }
1061                    }
1062                    Err(err) => reply.error(err.errno()),
1063                }
1064            } else {
1065                match self.get_attr(ino) {
1066                    Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
1067                    Err(err) => reply.error(err.errno()),
1068                }
1069            }
1070        }
1071
1072        fn readdir(
1073            &mut self,
1074            _req: &Request<'_>,
1075            ino: u64,
1076            _fh: u64,
1077            offset: i64,
1078            mut reply: ReplyDirectory,
1079        ) {
1080            let mut entries = Vec::new();
1081            entries.push((ino, EntryKind::Directory, ".".to_string()));
1082            let parent = self.parent_inode(ino);
1083            entries.push((parent, EntryKind::Directory, "..".to_string()));
1084
1085            match self.read_dir(ino) {
1086                Ok(children) => {
1087                    for entry in children {
1088                        entries.push((entry.inode, entry.kind, entry.name));
1089                    }
1090                }
1091                Err(err) => {
1092                    reply.error(err.errno());
1093                    return;
1094                }
1095            }
1096
1097            let start = if offset < 0 { 0 } else { offset as usize };
1098            for (index, (inode, kind, name)) in entries.into_iter().enumerate().skip(start) {
1099                let next_offset = (index + 1) as i64;
1100                let full = reply.add(inode, next_offset, Self::file_type(kind), name);
1101                if full {
1102                    break;
1103                }
1104            }
1105
1106            reply.ok();
1107        }
1108
1109        fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) {
1110            let stats = current_fs_stats();
1111            reply.statfs(
1112                stats.blocks,
1113                stats.bfree,
1114                stats.bavail,
1115                stats.files,
1116                stats.ffree,
1117                stats.bsize,
1118                stats.namelen,
1119                stats.frsize,
1120            );
1121        }
1122    }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127    use super::*;
1128    use hashtree_core::store::MemoryStore;
1129
1130    #[cfg(feature = "fuse")]
1131    use super::fuse_impl::{current_fs_stats, fallback_fs_stats};
1132
1133    struct RecordingPublisher {
1134        updates: Mutex<Vec<Cid>>,
1135    }
1136
1137    impl RecordingPublisher {
1138        fn new() -> Self {
1139            Self {
1140                updates: Mutex::new(Vec::new()),
1141            }
1142        }
1143
1144        fn updates(&self) -> Vec<Cid> {
1145            self.updates.lock().unwrap().clone()
1146        }
1147    }
1148
1149    impl RootPublisher for RecordingPublisher {
1150        fn publish(&self, cid: &Cid) -> Result<(), FsError> {
1151            self.updates.lock().unwrap().push(cid.clone());
1152            Ok(())
1153        }
1154    }
1155
1156    async fn empty_root(store: Arc<MemoryStore>) -> Cid {
1157        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1158        tree.put_directory(Vec::new()).await.unwrap()
1159    }
1160
1161    #[tokio::test]
1162    async fn test_create_write_read_file() {
1163        let store = Arc::new(MemoryStore::new());
1164        let root = empty_root(store.clone()).await;
1165        let fs = HashtreeFuse::new(store, root).unwrap();
1166
1167        let attr = fs.create_file(ROOT_INODE, "hello.txt").unwrap();
1168        assert_eq!(attr.kind, EntryKind::File);
1169
1170        fs.write_file(attr.inode, 0, b"hello").unwrap();
1171        let read = fs.read_file(attr.inode, 0, 5).unwrap();
1172        assert_eq!(read, b"hello");
1173    }
1174
1175    #[tokio::test]
1176    async fn test_mkdir_and_rename() {
1177        let store = Arc::new(MemoryStore::new());
1178        let root = empty_root(store.clone()).await;
1179        let fs = HashtreeFuse::new(store, root).unwrap();
1180
1181        let dir = fs.mkdir(ROOT_INODE, "docs").unwrap();
1182        let file = fs.create_file(dir.inode, "draft.txt").unwrap();
1183        fs.write_file(file.inode, 0, b"data").unwrap();
1184
1185        fs.rename(dir.inode, "draft.txt", dir.inode, "final.txt")
1186            .unwrap();
1187        let entries = fs.read_dir(dir.inode).unwrap();
1188        let names: Vec<String> = entries.into_iter().map(|e| e.name).collect();
1189        assert!(names.contains(&"final.txt".to_string()));
1190        assert!(!names.contains(&"draft.txt".to_string()));
1191    }
1192
1193    #[tokio::test]
1194    async fn test_truncate_file() {
1195        let store = Arc::new(MemoryStore::new());
1196        let root = empty_root(store.clone()).await;
1197        let fs = HashtreeFuse::new(store, root).unwrap();
1198
1199        let file = fs.create_file(ROOT_INODE, "file.bin").unwrap();
1200        fs.write_file(file.inode, 0, b"abcdef").unwrap();
1201        fs.truncate_file(file.inode, 3).unwrap();
1202        let read = fs.read_file(file.inode, 0, 10).unwrap();
1203        assert_eq!(read, b"abc");
1204    }
1205
1206    #[tokio::test]
1207    async fn test_publisher_invoked() {
1208        let store = Arc::new(MemoryStore::new());
1209        let root = empty_root(store.clone()).await;
1210        let publisher = Arc::new(RecordingPublisher::new());
1211        let fs = HashtreeFuse::new_with_publisher(store, root, Some(publisher.clone())).unwrap();
1212
1213        let file = fs.create_file(ROOT_INODE, "note.txt").unwrap();
1214        fs.write_file(file.inode, 0, b"note").unwrap();
1215
1216        let updates = publisher.updates();
1217        assert!(!updates.is_empty());
1218        assert_eq!(updates.last().unwrap(), &fs.current_root());
1219    }
1220
1221    #[cfg(feature = "fuse")]
1222    #[test]
1223    fn test_fallback_fs_stats_reports_free_space() {
1224        let stats = fallback_fs_stats();
1225        assert!(stats.blocks > 0);
1226        assert!(stats.bfree > 0);
1227        assert!(stats.bavail > 0);
1228        assert!(stats.bsize > 0);
1229        assert_eq!(stats.bfree, stats.bavail);
1230    }
1231
1232    #[cfg(feature = "fuse")]
1233    #[test]
1234    fn test_current_fs_stats_reports_free_space() {
1235        let stats = current_fs_stats();
1236        assert!(stats.blocks > 0);
1237        assert!(stats.bfree > 0);
1238        assert!(stats.bavail > 0);
1239        assert!(stats.bsize > 0);
1240    }
1241}