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