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