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.div_ceil(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: u32,
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 _umask: u32,
899 reply: ReplyEntry,
900 ) {
901 let name = match name.to_str() {
902 Some(value) => value,
903 None => {
904 reply.error(libc::EINVAL);
905 return;
906 }
907 };
908
909 match HashtreeFuse::mkdir(self, parent, name) {
910 Ok(attr) => reply.entry(&TTL, &self.file_attr(&attr), 0),
911 Err(err) => reply.error(err.errno()),
912 }
913 }
914
915 fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
916 let name = match name.to_str() {
917 Some(value) => value,
918 None => {
919 reply.error(libc::EINVAL);
920 return;
921 }
922 };
923
924 match HashtreeFuse::unlink(self, parent, name) {
925 Ok(()) => reply.ok(),
926 Err(err) => reply.error(err.errno()),
927 }
928 }
929
930 fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) {
931 let name = match name.to_str() {
932 Some(value) => value,
933 None => {
934 reply.error(libc::EINVAL);
935 return;
936 }
937 };
938
939 match HashtreeFuse::rmdir(self, parent, name) {
940 Ok(()) => reply.ok(),
941 Err(err) => reply.error(err.errno()),
942 }
943 }
944
945 fn rename(
946 &mut self,
947 _req: &Request<'_>,
948 parent: u64,
949 name: &OsStr,
950 newparent: u64,
951 newname: &OsStr,
952 _flags: u32,
953 reply: ReplyEmpty,
954 ) {
955 let name = match name.to_str() {
956 Some(value) => value,
957 None => {
958 reply.error(libc::EINVAL);
959 return;
960 }
961 };
962 let newname = match newname.to_str() {
963 Some(value) => value,
964 None => {
965 reply.error(libc::EINVAL);
966 return;
967 }
968 };
969
970 match HashtreeFuse::rename(self, parent, name, newparent, newname) {
971 Ok(()) => reply.ok(),
972 Err(err) => reply.error(err.errno()),
973 }
974 }
975
976 fn setattr(
977 &mut self,
978 _req: &Request<'_>,
979 ino: u64,
980 _mode: Option<u32>,
981 _uid: Option<u32>,
982 _gid: Option<u32>,
983 size: Option<u64>,
984 _atime: Option<fuser::TimeOrNow>,
985 _mtime: Option<fuser::TimeOrNow>,
986 _ctime: Option<SystemTime>,
987 _fh: Option<u64>,
988 _crtime: Option<SystemTime>,
989 _chgtime: Option<SystemTime>,
990 _bkuptime: Option<SystemTime>,
991 _flags: Option<u32>,
992 reply: ReplyAttr,
993 ) {
994 if let Some(size) = size {
995 match self.truncate_file(ino, size) {
996 Ok(()) => {
997 if let Ok(attr) = self.get_attr(ino) {
998 reply.attr(&TTL, &self.file_attr(&attr));
999 } else {
1000 reply.error(libc::EIO);
1001 }
1002 }
1003 Err(err) => reply.error(err.errno()),
1004 }
1005 } else {
1006 match self.get_attr(ino) {
1007 Ok(attr) => reply.attr(&TTL, &self.file_attr(&attr)),
1008 Err(err) => reply.error(err.errno()),
1009 }
1010 }
1011 }
1012
1013 fn readdir(
1014 &mut self,
1015 _req: &Request<'_>,
1016 ino: u64,
1017 _fh: u64,
1018 offset: i64,
1019 mut reply: ReplyDirectory,
1020 ) {
1021 let mut entries = Vec::new();
1022 entries.push((ino, EntryKind::Directory, ".".to_string()));
1023 let parent = self.parent_inode(ino);
1024 entries.push((parent, EntryKind::Directory, "..".to_string()));
1025
1026 match self.read_dir(ino) {
1027 Ok(children) => {
1028 for entry in children {
1029 entries.push((entry.inode, entry.kind, entry.name));
1030 }
1031 }
1032 Err(err) => {
1033 reply.error(err.errno());
1034 return;
1035 }
1036 }
1037
1038 let start = if offset < 0 { 0 } else { offset as usize };
1039 for (index, (inode, kind, name)) in entries.into_iter().enumerate().skip(start) {
1040 let next_offset = (index + 1) as i64;
1041 let full = reply.add(inode, next_offset, Self::file_type(kind), name);
1042 if full {
1043 break;
1044 }
1045 }
1046
1047 reply.ok();
1048 }
1049
1050 fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) {
1051 reply.statfs(0, 0, 0, 0, 0, 512, 255, 0);
1052 }
1053 }
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058 use super::*;
1059 use hashtree_core::store::MemoryStore;
1060
1061 struct RecordingPublisher {
1062 updates: Mutex<Vec<Cid>>,
1063 }
1064
1065 impl RecordingPublisher {
1066 fn new() -> Self {
1067 Self {
1068 updates: Mutex::new(Vec::new()),
1069 }
1070 }
1071
1072 fn updates(&self) -> Vec<Cid> {
1073 self.updates.lock().unwrap().clone()
1074 }
1075 }
1076
1077 impl RootPublisher for RecordingPublisher {
1078 fn publish(&self, cid: &Cid) -> Result<(), FsError> {
1079 self.updates.lock().unwrap().push(cid.clone());
1080 Ok(())
1081 }
1082 }
1083
1084 async fn empty_root(store: Arc<MemoryStore>) -> Cid {
1085 let tree = HashTree::new(HashTreeConfig::new(store.clone()));
1086 tree.put_directory(Vec::new()).await.unwrap()
1087 }
1088
1089 #[tokio::test]
1090 async fn test_create_write_read_file() {
1091 let store = Arc::new(MemoryStore::new());
1092 let root = empty_root(store.clone()).await;
1093 let fs = HashtreeFuse::new(store, root).unwrap();
1094
1095 let attr = fs.create_file(ROOT_INODE, "hello.txt").unwrap();
1096 assert_eq!(attr.kind, EntryKind::File);
1097
1098 fs.write_file(attr.inode, 0, b"hello").unwrap();
1099 let read = fs.read_file(attr.inode, 0, 5).unwrap();
1100 assert_eq!(read, b"hello");
1101 }
1102
1103 #[tokio::test]
1104 async fn test_mkdir_and_rename() {
1105 let store = Arc::new(MemoryStore::new());
1106 let root = empty_root(store.clone()).await;
1107 let fs = HashtreeFuse::new(store, root).unwrap();
1108
1109 let dir = fs.mkdir(ROOT_INODE, "docs").unwrap();
1110 let file = fs.create_file(dir.inode, "draft.txt").unwrap();
1111 fs.write_file(file.inode, 0, b"data").unwrap();
1112
1113 fs.rename(dir.inode, "draft.txt", dir.inode, "final.txt")
1114 .unwrap();
1115 let entries = fs.read_dir(dir.inode).unwrap();
1116 let names: Vec<String> = entries.into_iter().map(|e| e.name).collect();
1117 assert!(names.contains(&"final.txt".to_string()));
1118 assert!(!names.contains(&"draft.txt".to_string()));
1119 }
1120
1121 #[tokio::test]
1122 async fn test_truncate_file() {
1123 let store = Arc::new(MemoryStore::new());
1124 let root = empty_root(store.clone()).await;
1125 let fs = HashtreeFuse::new(store, root).unwrap();
1126
1127 let file = fs.create_file(ROOT_INODE, "file.bin").unwrap();
1128 fs.write_file(file.inode, 0, b"abcdef").unwrap();
1129 fs.truncate_file(file.inode, 3).unwrap();
1130 let read = fs.read_file(file.inode, 0, 10).unwrap();
1131 assert_eq!(read, b"abc");
1132 }
1133
1134 #[tokio::test]
1135 async fn test_publisher_invoked() {
1136 let store = Arc::new(MemoryStore::new());
1137 let root = empty_root(store.clone()).await;
1138 let publisher = Arc::new(RecordingPublisher::new());
1139 let fs = HashtreeFuse::new_with_publisher(store, root, Some(publisher.clone())).unwrap();
1140
1141 let file = fs.create_file(ROOT_INODE, "note.txt").unwrap();
1142 fs.write_file(file.inode, 0, b"note").unwrap();
1143
1144 let updates = publisher.updates();
1145 assert!(!updates.is_empty());
1146 assert_eq!(updates.last().unwrap(), &fs.current_root());
1147 }
1148}