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