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))?
199                .ok_or(FsError::NotFound)?;
200            let start = usize::try_from(offset).unwrap_or(usize::MAX);
201            if start >= data.len() {
202                return Ok(vec![]);
203            }
204            let end_u64 = offset.saturating_add(read_len);
205            let mut end = usize::try_from(end_u64).unwrap_or(data.len());
206            if end > data.len() {
207                end = data.len();
208            }
209            return Ok(data[start..end].to_vec());
210        }
211
212        let end = offset.saturating_add(read_len);
213        let data = block_on(self.tree.read_file_range(&entry.cid.hash, offset, Some(end)))?
214            .ok_or(FsError::NotFound)?;
215        Ok(data)
216    }
217
218    pub fn read_dir(&self, inode: u64) -> Result<Vec<DirEntry>, FsError> {
219        let path = self.path_for_inode(inode)?;
220        let dir_cid = self.resolve_dir_cid(&path)?;
221
222        let entries = block_on(self.tree.list_directory(&dir_cid))?;
223        let mut out = Vec::with_capacity(entries.len());
224
225        for entry in entries {
226            let child_inode = self.get_or_create_child_inode(inode, &entry.name)?;
227            out.push(DirEntry {
228                inode: child_inode,
229                name: entry.name,
230                kind: Self::kind_from_link(entry.link_type),
231            });
232        }
233
234        Ok(out)
235    }
236
237    pub fn create_file(&self, parent: u64, name: &str) -> Result<EntryAttr, FsError> {
238        self.ensure_valid_name(name)?;
239        let _guard = self.modify_lock.lock().unwrap();
240
241        let parent_path = self.path_for_inode(parent)?;
242        let mut child_path = parent_path.clone();
243        child_path.push(name.to_string());
244
245        if self.resolve_entry(&child_path).is_ok() {
246            return Err(FsError::AlreadyExists);
247        }
248
249        let (cid, size) = block_on(self.tree.put(&[]))?;
250        let link_type = self.link_type_for_size(size);
251        let new_root = block_on(self.tree.set_entry(
252            &self.current_root(),
253            &self.path_refs(&parent_path),
254            name,
255            &cid,
256            size,
257            link_type,
258        ))?;
259
260        self.apply_root_update(new_root)?;
261
262        let inode = self.insert_path(parent, name.to_string(), child_path);
263        Ok(EntryAttr {
264            inode,
265            size,
266            kind: EntryKind::File,
267        })
268    }
269
270    pub fn mkdir(&self, parent: u64, name: &str) -> Result<EntryAttr, FsError> {
271        self.ensure_valid_name(name)?;
272        let _guard = self.modify_lock.lock().unwrap();
273
274        let parent_path = self.path_for_inode(parent)?;
275        let mut child_path = parent_path.clone();
276        child_path.push(name.to_string());
277
278        if self.resolve_entry(&child_path).is_ok() {
279            return Err(FsError::AlreadyExists);
280        }
281
282        let dir_cid = block_on(self.tree.put_directory(Vec::new()))?;
283        let new_root = block_on(self.tree.set_entry(
284            &self.current_root(),
285            &self.path_refs(&parent_path),
286            name,
287            &dir_cid,
288            0,
289            LinkType::Dir,
290        ))?;
291
292        self.apply_root_update(new_root)?;
293
294        let inode = self.insert_path(parent, name.to_string(), child_path);
295        Ok(EntryAttr {
296            inode,
297            size: 0,
298            kind: EntryKind::Directory,
299        })
300    }
301
302    pub fn write_file(&self, inode: u64, offset: u64, data: &[u8]) -> Result<u32, FsError> {
303        let _guard = self.modify_lock.lock().unwrap();
304        let path = self.path_for_inode(inode)?;
305        let existing = self.read_file_full(&path)?;
306        let new_data = Self::apply_write(existing, offset, data);
307        self.update_file_at_path(&path, new_data)?;
308        Ok(data.len() as u32)
309    }
310
311    pub fn truncate_file(&self, inode: u64, size: u64) -> Result<(), FsError> {
312        let _guard = self.modify_lock.lock().unwrap();
313        let path = self.path_for_inode(inode)?;
314        let existing = self.read_file_full(&path)?;
315        let new_data = Self::apply_truncate(existing, size);
316        self.update_file_at_path(&path, new_data)?;
317        Ok(())
318    }
319
320    pub fn unlink(&self, parent: u64, name: &str) -> Result<(), FsError> {
321        self.ensure_valid_name(name)?;
322        let _guard = self.modify_lock.lock().unwrap();
323
324        let parent_path = self.path_for_inode(parent)?;
325        let mut child_path = parent_path.clone();
326        child_path.push(name.to_string());
327        let entry = self.resolve_entry(&child_path)?;
328        if entry.link_type == LinkType::Dir {
329            return Err(FsError::IsDir);
330        }
331
332        let new_root = block_on(self.tree.remove_entry(
333            &self.current_root(),
334            &self.path_refs(&parent_path),
335            name,
336        ))?;
337
338        self.apply_root_update(new_root)?;
339        self.remove_paths_prefix(&child_path);
340        self.children.write().unwrap().remove(&ChildKey {
341            parent,
342            name: name.to_string(),
343        });
344
345        Ok(())
346    }
347
348    pub fn rmdir(&self, parent: u64, name: &str) -> Result<(), FsError> {
349        self.ensure_valid_name(name)?;
350        let _guard = self.modify_lock.lock().unwrap();
351
352        let parent_path = self.path_for_inode(parent)?;
353        let mut child_path = parent_path.clone();
354        child_path.push(name.to_string());
355        let entry = self.resolve_entry(&child_path)?;
356        if entry.link_type != LinkType::Dir {
357            return Err(FsError::NotDir);
358        }
359
360        let dir_entries = block_on(self.tree.list_directory(&entry.cid))?;
361        if !dir_entries.is_empty() {
362            return Err(FsError::NotEmpty);
363        }
364
365        let new_root = block_on(self.tree.remove_entry(
366            &self.current_root(),
367            &self.path_refs(&parent_path),
368            name,
369        ))?;
370
371        self.apply_root_update(new_root)?;
372        self.remove_paths_prefix(&child_path);
373        self.children.write().unwrap().remove(&ChildKey {
374            parent,
375            name: name.to_string(),
376        });
377
378        Ok(())
379    }
380
381    pub fn rename(&self, parent: u64, name: &str, new_parent: u64, new_name: &str) -> Result<(), FsError> {
382        self.ensure_valid_name(name)?;
383        self.ensure_valid_name(new_name)?;
384        let _guard = self.modify_lock.lock().unwrap();
385
386        if parent == new_parent && name == new_name {
387            return Ok(());
388        }
389
390        let parent_path = self.path_for_inode(parent)?;
391        let new_parent_path = self.path_for_inode(new_parent)?;
392
393        let mut old_path = parent_path.clone();
394        old_path.push(name.to_string());
395        let entry = self.resolve_entry(&old_path)?;
396
397        let new_root = block_on(self.tree.set_entry(
398            &self.current_root(),
399            &self.path_refs(&new_parent_path),
400            new_name,
401            &entry.cid,
402            entry.size,
403            entry.link_type,
404        ))?;
405        let new_root = block_on(self.tree.remove_entry(
406            &new_root,
407            &self.path_refs(&parent_path),
408            name,
409        ))?;
410
411        self.apply_root_update(new_root)?;
412
413        let inode = self.get_or_create_child_inode(parent, name)?;
414        let mut new_path = new_parent_path.clone();
415        new_path.push(new_name.to_string());
416
417        self.children.write().unwrap().remove(&ChildKey {
418            parent,
419            name: name.to_string(),
420        });
421        self.children.write().unwrap().insert(
422            ChildKey {
423                parent: new_parent,
424                name: new_name.to_string(),
425            },
426            inode,
427        );
428
429        self.parents.write().unwrap().insert(inode, new_parent);
430        self.update_paths_prefix(&old_path, &new_path);
431
432        Ok(())
433    }
434
435    fn ensure_valid_name(&self, name: &str) -> Result<(), FsError> {
436        if name.is_empty() || name.contains('/') {
437            return Err(FsError::InvalidName);
438        }
439        Ok(())
440    }
441
442    fn path_for_inode(&self, inode: u64) -> Result<Vec<String>, FsError> {
443        self.paths
444            .read()
445            .unwrap()
446            .get(&inode)
447            .cloned()
448            .ok_or(FsError::NotFound)
449    }
450
451    fn resolve_entry(&self, path: &[String]) -> Result<ResolvedEntry, FsError> {
452        if path.is_empty() {
453            return Ok(ResolvedEntry {
454                cid: self.current_root(),
455                link_type: LinkType::Dir,
456                size: 0,
457            });
458        }
459
460        let (parent_path, name) = path.split_at(path.len() - 1);
461        let parent_cid = self.resolve_dir_cid(parent_path)?;
462        let entries = block_on(self.tree.list_directory(&parent_cid))?;
463        let entry = entries
464            .into_iter()
465            .find(|e| e.name == name[0])
466            .ok_or(FsError::NotFound)?;
467
468        Ok(ResolvedEntry {
469            cid: Cid {
470                hash: entry.hash,
471                key: entry.key,
472            },
473            link_type: entry.link_type,
474            size: entry.size,
475        })
476    }
477
478    fn resolve_dir_cid(&self, path: &[String]) -> Result<Cid, FsError> {
479        if path.is_empty() {
480            return Ok(self.current_root());
481        }
482
483        let root = self.current_root();
484        let path_str = path.join("/");
485        let cid = block_on(self.tree.resolve(&root, &path_str))?
486            .ok_or(FsError::NotFound)?;
487
488        let is_dir = block_on(self.tree.is_dir(&cid))?;
489        if !is_dir {
490            return Err(FsError::NotDir);
491        }
492
493        Ok(cid)
494    }
495
496    fn entry_attr_from_resolved(&self, inode: u64, entry: ResolvedEntry) -> Result<EntryAttr, FsError> {
497        let kind = Self::kind_from_link(entry.link_type);
498        let size = if kind == EntryKind::Directory {
499            0
500        } else {
501            self.entry_size(&entry)?
502        };
503
504        Ok(EntryAttr { inode, size, kind })
505    }
506
507    fn entry_size(&self, entry: &ResolvedEntry) -> Result<u64, FsError> {
508        if entry.link_type == LinkType::Dir {
509            return Ok(0);
510        }
511        if entry.size > 0 {
512            return Ok(entry.size);
513        }
514
515        let data = block_on(self.tree.get(&entry.cid))?
516            .ok_or(FsError::NotFound)?;
517        Ok(data.len() as u64)
518    }
519
520    fn read_file_full(&self, path: &[String]) -> Result<Vec<u8>, FsError> {
521        let entry = self.resolve_entry(path)?;
522        if entry.link_type == LinkType::Dir {
523            return Err(FsError::IsDir);
524        }
525        let data = block_on(self.tree.get(&entry.cid))?
526            .ok_or(FsError::NotFound)?;
527        Ok(data)
528    }
529
530    fn update_file_at_path(&self, path: &[String], data: Vec<u8>) -> Result<(), FsError> {
531        let (parent_path, name) = path.split_at(path.len() - 1);
532        let (cid, size) = block_on(self.tree.put(&data))?;
533        let link_type = self.link_type_for_size(size);
534
535        let new_root = block_on(self.tree.set_entry(
536            &self.current_root(),
537            &self.path_refs(parent_path),
538            name[0].as_str(),
539            &cid,
540            size,
541            link_type,
542        ))?;
543
544        self.apply_root_update(new_root)
545    }
546
547    fn apply_root_update(&self, new_root: Cid) -> Result<(), FsError> {
548        if let Some(publisher) = &self.publisher {
549            publisher.publish(&new_root)?;
550        }
551        *self.root.write().unwrap() = new_root;
552        Ok(())
553    }
554
555    fn link_type_for_size(&self, size: u64) -> LinkType {
556        if size as usize > self.tree.chunk_size() {
557            LinkType::File
558        } else {
559            LinkType::Blob
560        }
561    }
562
563    fn get_or_create_child_inode(&self, parent: u64, name: &str) -> Result<u64, FsError> {
564        let key = ChildKey {
565            parent,
566            name: name.to_string(),
567        };
568        if let Some(inode) = self.children.read().unwrap().get(&key).copied() {
569            return Ok(inode);
570        }
571
572        let parent_path = self.path_for_inode(parent)?;
573        let mut child_path = parent_path.clone();
574        child_path.push(name.to_string());
575
576        if let Some(existing) = self.find_inode_by_path(&child_path) {
577            self.children.write().unwrap().insert(key, existing);
578            return Ok(existing);
579        }
580
581        Ok(self.insert_path(parent, name.to_string(), child_path))
582    }
583
584    fn insert_path(&self, parent: u64, name: String, path: Vec<String>) -> u64 {
585        let inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
586        self.paths.write().unwrap().insert(inode, path);
587        self.parents.write().unwrap().insert(inode, parent);
588        self.children.write().unwrap().insert(
589            ChildKey { parent, name },
590            inode,
591        );
592        inode
593    }
594
595    fn find_inode_by_path(&self, path: &[String]) -> Option<u64> {
596        self.paths
597            .read()
598            .unwrap()
599            .iter()
600            .find_map(|(inode, inode_path)| if inode_path == path { Some(*inode) } else { None })
601    }
602
603    fn update_paths_prefix(&self, old_prefix: &[String], new_prefix: &[String]) {
604        let mut paths = self.paths.write().unwrap();
605        for path in paths.values_mut() {
606            if Self::path_has_prefix(path, old_prefix) {
607                let mut updated = new_prefix.to_vec();
608                updated.extend_from_slice(&path[old_prefix.len()..]);
609                *path = updated;
610            }
611        }
612    }
613
614    fn remove_paths_prefix(&self, prefix: &[String]) {
615        let mut to_remove = Vec::new();
616        {
617            let paths = self.paths.read().unwrap();
618            for (inode, path) in paths.iter() {
619                if *inode == ROOT_INODE {
620                    continue;
621                }
622                if Self::path_has_prefix(path, prefix) {
623                    to_remove.push(*inode);
624                }
625            }
626        }
627
628        if to_remove.is_empty() {
629            return;
630        }
631
632        let remove_set: std::collections::HashSet<u64> = to_remove.into_iter().collect();
633        self.paths.write().unwrap().retain(|inode, _| !remove_set.contains(inode));
634        self.parents.write().unwrap().retain(|inode, _| !remove_set.contains(inode));
635        self.children.write().unwrap().retain(|_, inode| !remove_set.contains(inode));
636    }
637
638    fn drop_inode(&self, inode: u64) {
639        if inode == ROOT_INODE {
640            return;
641        }
642        let mut paths = self.paths.write().unwrap();
643        let removed_path = paths.remove(&inode);
644        drop(paths);
645        self.parents.write().unwrap().remove(&inode);
646        if let Some(path) = removed_path {
647            if let Some((name, parent_path)) = path.split_last() {
648                if let Some(parent_inode) = self.find_inode_by_path(parent_path) {
649                    self.children.write().unwrap().remove(&ChildKey {
650                        parent: parent_inode,
651                        name: name.to_string(),
652                    });
653                }
654            }
655        }
656    }
657
658    fn parent_inode(&self, inode: u64) -> u64 {
659        self.parents
660            .read()
661            .unwrap()
662            .get(&inode)
663            .copied()
664            .unwrap_or(ROOT_INODE)
665    }
666
667    fn kind_from_link(link_type: LinkType) -> EntryKind {
668        match link_type {
669            LinkType::Dir => EntryKind::Directory,
670            LinkType::Blob | LinkType::File => EntryKind::File,
671        }
672    }
673
674    fn apply_write(mut existing: Vec<u8>, offset: u64, data: &[u8]) -> Vec<u8> {
675        let offset_usize = offset as usize;
676        if existing.len() < offset_usize {
677            existing.resize(offset_usize, 0);
678        }
679        if existing.len() < offset_usize + data.len() {
680            existing.resize(offset_usize + data.len(), 0);
681        }
682        existing[offset_usize..offset_usize + data.len()].copy_from_slice(data);
683        existing
684    }
685
686    fn apply_truncate(mut existing: Vec<u8>, size: u64) -> Vec<u8> {
687        let size = size as usize;
688        if existing.len() > size {
689            existing.truncate(size);
690        } else if existing.len() < size {
691            existing.resize(size, 0);
692        }
693        existing
694    }
695
696    fn path_refs<'a>(&self, path: &'a [String]) -> Vec<&'a str> {
697        path.iter().map(|p| p.as_str()).collect()
698    }
699
700    fn path_has_prefix(path: &[String], prefix: &[String]) -> bool {
701        if prefix.len() > path.len() {
702            return false;
703        }
704        path.iter().zip(prefix.iter()).all(|(a, b)| a == b)
705    }
706}
707
708#[cfg(feature = "fuse")]
709mod fuse_impl {
710    use super::*;
711    use fuser::{FileAttr, FileType, Filesystem, MountOption, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyStatfs, ReplyWrite, Request};
712    use std::ffi::OsStr;
713    use std::path::Path;
714    use std::time::{Duration, SystemTime};
715
716    const TTL: Duration = Duration::from_secs(1);
717
718    impl FsError {
719        fn errno(&self) -> i32 {
720            match self {
721                FsError::InvalidRoot | FsError::InvalidName => libc::EINVAL,
722                FsError::NotFound => libc::ENOENT,
723                FsError::NotDir => libc::ENOTDIR,
724                FsError::IsDir => libc::EISDIR,
725                FsError::AlreadyExists => libc::EEXIST,
726                FsError::NotEmpty => libc::ENOTEMPTY,
727                FsError::Tree(_) | FsError::Publish(_) => libc::EIO,
728            }
729        }
730    }
731
732    impl<S: Store + Send + Sync + 'static> HashtreeFuse<S> {
733        pub fn mount(self, mountpoint: impl AsRef<Path>, options: &[MountOption]) -> std::io::Result<()> {
734            fuser::mount2(self, mountpoint, options)
735        }
736
737        fn file_attr(&self, attr: &EntryAttr) -> FileAttr {
738            let (kind, perm, nlink) = match attr.kind {
739                EntryKind::Directory => (FileType::Directory, 0o755, 2),
740                EntryKind::File => (FileType::RegularFile, 0o644, 1),
741            };
742            let uid = unsafe { libc::geteuid() };
743            let gid = unsafe { libc::getegid() };
744            let blocks = (attr.size + 511) / 512;
745
746            FileAttr {
747                ino: attr.inode,
748                size: attr.size,
749                blocks,
750                atime: SystemTime::UNIX_EPOCH,
751                mtime: SystemTime::UNIX_EPOCH,
752                ctime: SystemTime::UNIX_EPOCH,
753                crtime: SystemTime::UNIX_EPOCH,
754                kind,
755                perm,
756                nlink,
757                uid,
758                gid,
759                rdev: 0,
760                blksize: 512,
761                flags: 0,
762            }
763        }
764
765        fn file_type(kind: EntryKind) -> FileType {
766            match kind {
767                EntryKind::Directory => FileType::Directory,
768                EntryKind::File => FileType::RegularFile,
769            }
770        }
771    }
772
773    impl<S: Store + Send + Sync + 'static> Filesystem for HashtreeFuse<S> {
774        fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
775            let name = match name.to_str() {
776                Some(value) => value,
777                None => {
778                    reply.error(libc::ENOENT);
779                    return;
780                }
781            };
782
783            match self.lookup_child(parent, name) {
784                Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
785                Err(err) => reply.error(err.errno()),
786            }
787        }
788
789        fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
790            match self.get_attr(ino) {
791                Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
792                Err(err) => reply.error(err.errno()),
793            }
794        }
795
796        fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: i32, reply: fuser::ReplyOpen) {
797            reply.opened(0, 0);
798        }
799
800        fn read(
801            &mut self,
802            _req: &Request<'_>,
803            ino: u64,
804            _fh: u64,
805            offset: i64,
806            size: u32,
807            _flags: i32,
808            _lock_owner: Option<u64>,
809            reply: ReplyData,
810        ) {
811            let offset = if offset < 0 { 0 } else { offset as u64 };
812            match self.read_file(ino, offset, size) {
813                Ok(data) => reply.data(&data),
814                Err(err) => reply.error(err.errno()),
815            }
816        }
817
818        fn write(
819            &mut self,
820            _req: &Request<'_>,
821            ino: u64,
822            _fh: u64,
823            offset: i64,
824            data: &[u8],
825            _write_flags: i32,
826            _flags: i32,
827            _lock_owner: Option<u64>,
828            reply: ReplyWrite,
829        ) {
830            let offset = if offset < 0 { 0 } else { offset as u64 };
831            match self.write_file(ino, offset, data) {
832                Ok(written) => reply.written(written),
833                Err(err) => reply.error(err.errno()),
834            }
835        }
836
837        fn create(
838            &mut self,
839            _req: &Request<'_>,
840            parent: u64,
841            name: &OsStr,
842            _mode: u32,
843            _umask: u32,
844            _flags: i32,
845            reply: ReplyCreate,
846        ) {
847            let name = match name.to_str() {
848                Some(value) => value,
849                None => {
850                    reply.error(libc::EINVAL);
851                    return;
852                }
853            };
854
855            match self.create_file(parent, name) {
856                Ok(attr) => reply.created(&TTL, &self.file_attr(&attr), 0, 0, 0),
857                Err(err) => reply.error(err.errno()),
858            }
859        }
860
861        fn mkdir(
862            &mut self,
863            _req: &Request<'_>,
864            parent: u64,
865            name: &OsStr,
866            _mode: u32,
867            reply: ReplyEntry,
868        ) {
869            let name = match name.to_str() {
870                Some(value) => value,
871                None => {
872                    reply.error(libc::EINVAL);
873                    return;
874                }
875            };
876
877            match self.mkdir(parent, name) {
878                Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
879                Err(err) => reply.error(err.errno()),
880            }
881        }
882
883        fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
884            let name = match name.to_str() {
885                Some(value) => value,
886                None => {
887                    reply.error(libc::EINVAL);
888                    return;
889                }
890            };
891
892            match self.unlink(parent, name) {
893                Ok(()) => reply.ok(),
894                Err(err) => reply.error(err.errno()),
895            }
896        }
897
898        fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
899            let name = match name.to_str() {
900                Some(value) => value,
901                None => {
902                    reply.error(libc::EINVAL);
903                    return;
904                }
905            };
906
907            match self.rmdir(parent, name) {
908                Ok(()) => reply.ok(),
909                Err(err) => reply.error(err.errno()),
910            }
911        }
912
913        fn rename(
914            &mut self,
915            _req: &Request<'_>,
916            parent: u64,
917            name: &OsStr,
918            newparent: u64,
919            newname: &OsStr,
920            _flags: u32,
921            reply: ReplyEmpty,
922        ) {
923            let name = match name.to_str() {
924                Some(value) => value,
925                None => {
926                    reply.error(libc::EINVAL);
927                    return;
928                }
929            };
930            let newname = match newname.to_str() {
931                Some(value) => value,
932                None => {
933                    reply.error(libc::EINVAL);
934                    return;
935                }
936            };
937
938            match self.rename(parent, name, newparent, newname) {
939                Ok(()) => reply.ok(),
940                Err(err) => reply.error(err.errno()),
941            }
942        }
943
944        fn setattr(
945            &mut self,
946            _req: &Request<'_>,
947            ino: u64,
948            _mode: Option<u32>,
949            _uid: Option<u32>,
950            _gid: Option<u32>,
951            size: Option<u64>,
952            _atime: Option<fuser::TimeOrNow>,
953            _mtime: Option<fuser::TimeOrNow>,
954            _ctime: Option<SystemTime>,
955            _fh: Option<u64>,
956            _crtime: Option<SystemTime>,
957            _chgtime: Option<SystemTime>,
958            _bkuptime: Option<SystemTime>,
959            _flags: Option<u32>,
960            reply: ReplyAttr,
961        ) {
962            if let Some(size) = size {
963                match self.truncate_file(ino, size) {
964                    Ok(()) => {
965                        if let Ok(attr) = self.get_attr(ino) {
966                            reply.attr(&TTL, &self.file_attr(&attr));
967                        } else {
968                            reply.error(libc::EIO);
969                        }
970                    }
971                    Err(err) => reply.error(err.errno()),
972                }
973            } else {
974                match self.get_attr(ino) {
975                    Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
976                    Err(err) => reply.error(err.errno()),
977                }
978            }
979        }
980
981        fn readdir(
982            &mut self,
983            _req: &Request<'_>,
984            ino: u64,
985            _fh: u64,
986            offset: i64,
987            mut reply: ReplyDirectory,
988        ) {
989            let mut entries = Vec::new();
990            entries.push((ino, EntryKind::Directory, ".".to_string()));
991            let parent = self.parent_inode(ino);
992            entries.push((parent, EntryKind::Directory, "..".to_string()));
993
994            match self.read_dir(ino) {
995                Ok(children) => {
996                    for entry in children {
997                        entries.push((entry.inode, entry.kind, entry.name));
998                    }
999                }
1000                Err(err) => {
1001                    reply.error(err.errno());
1002                    return;
1003                }
1004            }
1005
1006            let start = if offset < 0 { 0 } else { offset as usize };
1007            for (index, (inode, kind, name)) in entries.into_iter().enumerate().skip(start) {
1008                let next_offset = (index + 1) as i64;
1009                let full = reply.add(inode, next_offset, Self::file_type(kind), name);
1010                if full {
1011                    break;
1012                }
1013            }
1014
1015            reply.ok();
1016        }
1017
1018        fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) {
1019            reply.statfs(0, 0, 0, 0, 0, 512, 255, 0);
1020        }
1021    }
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026    use super::*;
1027    use hashtree_core::store::MemoryStore;
1028
1029    struct RecordingPublisher {
1030        updates: Mutex<Vec<Cid>>,
1031    }
1032
1033    impl RecordingPublisher {
1034        fn new() -> Self {
1035            Self {
1036                updates: Mutex::new(Vec::new()),
1037            }
1038        }
1039
1040        fn updates(&self) -> Vec<Cid> {
1041            self.updates.lock().unwrap().clone()
1042        }
1043    }
1044
1045    impl RootPublisher for RecordingPublisher {
1046        fn publish(&self, cid: &Cid) -> Result<(), FsError> {
1047            self.updates.lock().unwrap().push(cid.clone());
1048            Ok(())
1049        }
1050    }
1051
1052    async fn empty_root(store: Arc<MemoryStore>) -> Cid {
1053        let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1054        tree.put_directory(Vec::new()).await.unwrap()
1055    }
1056
1057    #[tokio::test]
1058    async fn test_create_write_read_file() {
1059        let store = Arc::new(MemoryStore::new());
1060        let root = empty_root(store.clone()).await;
1061        let fs = HashtreeFuse::new(store, root).unwrap();
1062
1063        let attr = fs.create_file(ROOT_INODE, "hello.txt").unwrap();
1064        assert_eq!(attr.kind, EntryKind::File);
1065
1066        fs.write_file(attr.inode, 0, b"hello").unwrap();
1067        let read = fs.read_file(attr.inode, 0, 5).unwrap();
1068        assert_eq!(read, b"hello");
1069    }
1070
1071    #[tokio::test]
1072    async fn test_mkdir_and_rename() {
1073        let store = Arc::new(MemoryStore::new());
1074        let root = empty_root(store.clone()).await;
1075        let fs = HashtreeFuse::new(store, root).unwrap();
1076
1077        let dir = fs.mkdir(ROOT_INODE, "docs").unwrap();
1078        let file = fs.create_file(dir.inode, "draft.txt").unwrap();
1079        fs.write_file(file.inode, 0, b"data").unwrap();
1080
1081        fs.rename(dir.inode, "draft.txt", dir.inode, "final.txt").unwrap();
1082        let entries = fs.read_dir(dir.inode).unwrap();
1083        let names: Vec<String> = entries.into_iter().map(|e| e.name).collect();
1084        assert!(names.contains(&"final.txt".to_string()));
1085        assert!(!names.contains(&"draft.txt".to_string()));
1086    }
1087
1088    #[tokio::test]
1089    async fn test_truncate_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 file = fs.create_file(ROOT_INODE, "file.bin").unwrap();
1095        fs.write_file(file.inode, 0, b"abcdef").unwrap();
1096        fs.truncate_file(file.inode, 3).unwrap();
1097        let read = fs.read_file(file.inode, 0, 10).unwrap();
1098        assert_eq!(read, b"abc");
1099    }
1100
1101    #[tokio::test]
1102    async fn test_publisher_invoked() {
1103        let store = Arc::new(MemoryStore::new());
1104        let root = empty_root(store.clone()).await;
1105        let publisher = Arc::new(RecordingPublisher::new());
1106        let fs = HashtreeFuse::new_with_publisher(store, root, Some(publisher.clone())).unwrap();
1107
1108        let file = fs.create_file(ROOT_INODE, "note.txt").unwrap();
1109        fs.write_file(file.inode, 0, b"note").unwrap();
1110
1111        let updates = publisher.updates();
1112        assert!(!updates.is_empty());
1113        assert_eq!(updates.last().unwrap(), &fs.current_root());
1114    }
1115}