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::OsStr;
740    use std::path::Path;
741    use std::time::{Duration, SystemTime};
742
743    const TTL: Duration = Duration::from_secs(1);
744
745    impl FsError {
746        fn errno(&self) -> i32 {
747            match self {
748                FsError::InvalidRoot | FsError::InvalidName => libc::EINVAL,
749                FsError::NotFound => libc::ENOENT,
750                FsError::NotDir => libc::ENOTDIR,
751                FsError::IsDir => libc::EISDIR,
752                FsError::AlreadyExists => libc::EEXIST,
753                FsError::NotEmpty => libc::ENOTEMPTY,
754                FsError::Tree(_) | FsError::Publish(_) => libc::EIO,
755            }
756        }
757    }
758
759    impl<S: Store + Send + Sync + 'static> HashtreeFuse<S> {
760        pub fn mount(
761            self,
762            mountpoint: impl AsRef<Path>,
763            options: &[MountOption],
764        ) -> std::io::Result<()> {
765            fuser::mount2(self, mountpoint, options)
766        }
767
768        fn file_attr(&self, attr: &EntryAttr) -> FileAttr {
769            let (kind, perm, nlink) = match attr.kind {
770                EntryKind::Directory => (FileType::Directory, 0o755, 2),
771                EntryKind::File => (FileType::RegularFile, 0o644, 1),
772            };
773            let uid = unsafe { libc::geteuid() };
774            let gid = unsafe { libc::getegid() };
775            let blocks = (attr.size + 511) / 512;
776
777            FileAttr {
778                ino: attr.inode,
779                size: attr.size,
780                blocks,
781                atime: SystemTime::UNIX_EPOCH,
782                mtime: SystemTime::UNIX_EPOCH,
783                ctime: SystemTime::UNIX_EPOCH,
784                crtime: SystemTime::UNIX_EPOCH,
785                kind,
786                perm,
787                nlink,
788                uid,
789                gid,
790                rdev: 0,
791                blksize: 512,
792                flags: 0,
793            }
794        }
795
796        fn file_type(kind: EntryKind) -> FileType {
797            match kind {
798                EntryKind::Directory => FileType::Directory,
799                EntryKind::File => FileType::RegularFile,
800            }
801        }
802    }
803
804    impl<S: Store + Send + Sync + 'static> Filesystem for HashtreeFuse<S> {
805        fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
806            let name = match name.to_str() {
807                Some(value) => value,
808                None => {
809                    reply.error(libc::ENOENT);
810                    return;
811                }
812            };
813
814            match self.lookup_child(parent, name) {
815                Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
816                Err(err) => reply.error(err.errno()),
817            }
818        }
819
820        fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
821            match self.get_attr(ino) {
822                Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
823                Err(err) => reply.error(err.errno()),
824            }
825        }
826
827        fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen) {
828            reply.opened(0, 0);
829        }
830
831        fn read(
832            &mut self,
833            _req: &Request<'_>,
834            ino: u64,
835            _fh: u64,
836            offset: i64,
837            size: u32,
838            _flags: i32,
839            _lock_owner: Option<u64>,
840            reply: ReplyData,
841        ) {
842            let offset = if offset < 0 { 0 } else { offset as u64 };
843            match self.read_file(ino, offset, size) {
844                Ok(data) => reply.data(&data),
845                Err(err) => reply.error(err.errno()),
846            }
847        }
848
849        fn write(
850            &mut self,
851            _req: &Request<'_>,
852            ino: u64,
853            _fh: u64,
854            offset: i64,
855            data: &[u8],
856            _write_flags: i32,
857            _flags: i32,
858            _lock_owner: Option<u64>,
859            reply: ReplyWrite,
860        ) {
861            let offset = if offset < 0 { 0 } else { offset as u64 };
862            match self.write_file(ino, offset, data) {
863                Ok(written) => reply.written(written),
864                Err(err) => reply.error(err.errno()),
865            }
866        }
867
868        fn create(
869            &mut self,
870            _req: &Request<'_>,
871            parent: u64,
872            name: &OsStr,
873            _mode: u32,
874            _umask: u32,
875            _flags: i32,
876            reply: ReplyCreate,
877        ) {
878            let name = match name.to_str() {
879                Some(value) => value,
880                None => {
881                    reply.error(libc::EINVAL);
882                    return;
883                }
884            };
885
886            match self.create_file(parent, name) {
887                Ok(attr) => reply.created(&TTL, &self.file_attr(&attr), 0, 0, 0),
888                Err(err) => reply.error(err.errno()),
889            }
890        }
891
892        fn mkdir(
893            &mut self,
894            _req: &Request<'_>,
895            parent: u64,
896            name: &OsStr,
897            _mode: u32,
898            reply: ReplyEntry,
899        ) {
900            let name = match name.to_str() {
901                Some(value) => value,
902                None => {
903                    reply.error(libc::EINVAL);
904                    return;
905                }
906            };
907
908            match self.mkdir(parent, name) {
909                Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
910                Err(err) => reply.error(err.errno()),
911            }
912        }
913
914        fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
915            let name = match name.to_str() {
916                Some(value) => value,
917                None => {
918                    reply.error(libc::EINVAL);
919                    return;
920                }
921            };
922
923            match self.unlink(parent, name) {
924                Ok(()) => reply.ok(),
925                Err(err) => reply.error(err.errno()),
926            }
927        }
928
929        fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
930            let name = match name.to_str() {
931                Some(value) => value,
932                None => {
933                    reply.error(libc::EINVAL);
934                    return;
935                }
936            };
937
938            match self.rmdir(parent, name) {
939                Ok(()) => reply.ok(),
940                Err(err) => reply.error(err.errno()),
941            }
942        }
943
944        fn rename(
945            &mut self,
946            _req: &Request<'_>,
947            parent: u64,
948            name: &OsStr,
949            newparent: u64,
950            newname: &OsStr,
951            _flags: u32,
952            reply: ReplyEmpty,
953        ) {
954            let name = match name.to_str() {
955                Some(value) => value,
956                None => {
957                    reply.error(libc::EINVAL);
958                    return;
959                }
960            };
961            let newname = match newname.to_str() {
962                Some(value) => value,
963                None => {
964                    reply.error(libc::EINVAL);
965                    return;
966                }
967            };
968
969            match self.rename(parent, name, newparent, newname) {
970                Ok(()) => reply.ok(),
971                Err(err) => reply.error(err.errno()),
972            }
973        }
974
975        fn setattr(
976            &mut self,
977            _req: &Request<'_>,
978            ino: u64,
979            _mode: Option<u32>,
980            _uid: Option<u32>,
981            _gid: Option<u32>,
982            size: Option<u64>,
983            _atime: Option<fuser::TimeOrNow>,
984            _mtime: Option<fuser::TimeOrNow>,
985            _ctime: Option<SystemTime>,
986            _fh: Option<u64>,
987            _crtime: Option<SystemTime>,
988            _chgtime: Option<SystemTime>,
989            _bkuptime: Option<SystemTime>,
990            _flags: Option<u32>,
991            reply: ReplyAttr,
992        ) {
993            if let Some(size) = size {
994                match self.truncate_file(ino, size) {
995                    Ok(()) => {
996                        if let Ok(attr) = self.get_attr(ino) {
997                            reply.attr(&TTL, &self.file_attr(&attr));
998                        } else {
999                            reply.error(libc::EIO);
1000                        }
1001                    }
1002                    Err(err) => reply.error(err.errno()),
1003                }
1004            } else {
1005                match self.get_attr(ino) {
1006                    Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
1007                    Err(err) => reply.error(err.errno()),
1008                }
1009            }
1010        }
1011
1012        fn readdir(
1013            &mut self,
1014            _req: &Request<'_>,
1015            ino: u64,
1016            _fh: u64,
1017            offset: i64,
1018            mut reply: ReplyDirectory,
1019        ) {
1020            let mut entries = Vec::new();
1021            entries.push((ino, EntryKind::Directory, ".".to_string()));
1022            let parent = self.parent_inode(ino);
1023            entries.push((parent, EntryKind::Directory, "..".to_string()));
1024
1025            match self.read_dir(ino) {
1026                Ok(children) => {
1027                    for entry in children {
1028                        entries.push((entry.inode, entry.kind, entry.name));
1029                    }
1030                }
1031                Err(err) => {
1032                    reply.error(err.errno());
1033                    return;
1034                }
1035            }
1036
1037            let start = if offset < 0 { 0 } else { offset as usize };
1038            for (index, (inode, kind, name)) in entries.into_iter().enumerate().skip(start) {
1039                let next_offset = (index + 1) as i64;
1040                let full = reply.add(inode, next_offset, Self::file_type(kind), name);
1041                if full {
1042                    break;
1043                }
1044            }
1045
1046            reply.ok();
1047        }
1048
1049        fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) {
1050            reply.statfs(0, 0, 0, 0, 0, 512, 255, 0);
1051        }
1052    }
1053}
1054
1055#[cfg(test)]
1056mod tests {
1057    use super::*;
1058    use hashtree_core::store::MemoryStore;
1059
1060    struct RecordingPublisher {
1061        updates: Mutex<Vec<Cid>>,
1062    }
1063
1064    impl RecordingPublisher {
1065        fn new() -> Self {
1066            Self {
1067                updates: Mutex::new(Vec::new()),
1068            }
1069        }
1070
1071        fn updates(&self) -> Vec<Cid> {
1072            self.updates.lock().unwrap().clone()
1073        }
1074    }
1075
1076    impl RootPublisher for RecordingPublisher {
1077        fn publish(&self, cid: &Cid) -> Result<(), FsError> {
1078            self.updates.lock().unwrap().push(cid.clone());
1079            Ok(())
1080        }
1081    }
1082
1083    async fn empty_root(store: Arc<MemoryStore>) -> Cid {
1084        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1085        tree.put_directory(Vec::new()).await.unwrap()
1086    }
1087
1088    #[tokio::test]
1089    async fn test_create_write_read_file() {
1090        let store = Arc::new(MemoryStore::new());
1091        let root = empty_root(store.clone()).await;
1092        let fs = HashtreeFuse::new(store, root).unwrap();
1093
1094        let attr = fs.create_file(ROOT_INODE, "hello.txt").unwrap();
1095        assert_eq!(attr.kind, EntryKind::File);
1096
1097        fs.write_file(attr.inode, 0, b"hello").unwrap();
1098        let read = fs.read_file(attr.inode, 0, 5).unwrap();
1099        assert_eq!(read, b"hello");
1100    }
1101
1102    #[tokio::test]
1103    async fn test_mkdir_and_rename() {
1104        let store = Arc::new(MemoryStore::new());
1105        let root = empty_root(store.clone()).await;
1106        let fs = HashtreeFuse::new(store, root).unwrap();
1107
1108        let dir = fs.mkdir(ROOT_INODE, "docs").unwrap();
1109        let file = fs.create_file(dir.inode, "draft.txt").unwrap();
1110        fs.write_file(file.inode, 0, b"data").unwrap();
1111
1112        fs.rename(dir.inode, "draft.txt", dir.inode, "final.txt")
1113            .unwrap();
1114        let entries = fs.read_dir(dir.inode).unwrap();
1115        let names: Vec<String> = entries.into_iter().map(|e| e.name).collect();
1116        assert!(names.contains(&"final.txt".to_string()));
1117        assert!(!names.contains(&"draft.txt".to_string()));
1118    }
1119
1120    #[tokio::test]
1121    async fn test_truncate_file() {
1122        let store = Arc::new(MemoryStore::new());
1123        let root = empty_root(store.clone()).await;
1124        let fs = HashtreeFuse::new(store, root).unwrap();
1125
1126        let file = fs.create_file(ROOT_INODE, "file.bin").unwrap();
1127        fs.write_file(file.inode, 0, b"abcdef").unwrap();
1128        fs.truncate_file(file.inode, 3).unwrap();
1129        let read = fs.read_file(file.inode, 0, 10).unwrap();
1130        assert_eq!(read, b"abc");
1131    }
1132
1133    #[tokio::test]
1134    async fn test_publisher_invoked() {
1135        let store = Arc::new(MemoryStore::new());
1136        let root = empty_root(store.clone()).await;
1137        let publisher = Arc::new(RecordingPublisher::new());
1138        let fs = HashtreeFuse::new_with_publisher(store, root, Some(publisher.clone())).unwrap();
1139
1140        let file = fs.create_file(ROOT_INODE, "note.txt").unwrap();
1141        fs.write_file(file.inode, 0, b"note").unwrap();
1142
1143        let updates = publisher.updates();
1144        assert!(!updates.is_empty());
1145        assert_eq!(updates.last().unwrap(), &fs.current_root());
1146    }
1147}