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