1use anyfs_backend::*;
9use std::collections::HashMap;
10use std::ffi::OsStr;
11use std::io::{Read, Write};
12use std::path::{Path, PathBuf};
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::RwLock;
15use std::time::SystemTime;
16
17fn copy_file<B: Fs>(fs: &B, from: &Path, to: &Path) -> Result<(), FsError> {
25 let data = fs.read(from)?;
26 fs.write(to, &data)?;
27 Ok(())
28}
29
30fn count_files_recursive<B: Fs>(fs: &B, dir: &Path) -> Result<usize, FsError> {
32 let mut count = 0;
33 for entry in fs.read_dir(dir)? {
34 let entry = entry?;
35 match entry.file_type {
36 FileType::File => count += 1,
37 FileType::Directory => {
38 count += count_files_recursive(fs, &entry.path)?;
39 }
40 FileType::Symlink => {} }
42 }
43 Ok(count)
44}
45
46fn find_files<B: Fs>(
48 fs: &B,
49 dir: &Path,
50 predicate: &dyn Fn(&DirEntry) -> bool,
51) -> Result<Vec<PathBuf>, FsError> {
52 let mut results = Vec::new();
53 for entry in fs.read_dir(dir)? {
54 let entry = entry?;
55 if predicate(&entry) {
56 results.push(entry.path.clone());
57 }
58 if entry.file_type == FileType::Directory {
59 results.extend(find_files(fs, &entry.path, predicate)?);
60 }
61 }
62 Ok(results)
63}
64
65fn backup_with_link<B: Fs + FsLink>(fs: &B, path: &Path) -> Result<PathBuf, FsError> {
73 let backup_path = PathBuf::from(format!("{}.bak", path.display()));
74
75 let data = fs.read(path)?;
77 fs.write(&backup_path, &data)?;
78
79 let link_path = PathBuf::from(format!("{}.latest", path.display()));
81 let _ = fs.remove_file(&link_path);
83 fs.symlink(&backup_path, &link_path)?;
84
85 Ok(backup_path)
86}
87
88fn resolve_symlinks<B: Fs + FsLink>(fs: &B, path: &Path) -> Result<PathBuf, FsError> {
90 let mut current = path.to_path_buf();
91 let mut seen = std::collections::HashSet::new();
92
93 loop {
94 if !seen.insert(current.clone()) {
95 return Err(FsError::InvalidData {
97 path: current,
98 details: "symlink loop detected".into(),
99 });
100 }
101
102 match fs.symlink_metadata(¤t) {
103 Ok(meta) if meta.file_type == FileType::Symlink => {
104 let target = fs.read_link(¤t)?;
105 current = if target.is_absolute() {
106 target
107 } else {
108 current.parent().unwrap_or(Path::new("/")).join(target)
109 };
110 }
111 Ok(_) => return Ok(current),
112 Err(e) => return Err(e),
113 }
114 }
115}
116
117fn report_usage<B: FsFull>(fs: &B) -> Result<String, FsError> {
125 let stats = fs.statfs()?;
126 let used_percent = (stats.used_bytes as f64 / stats.total_bytes as f64) * 100.0;
127
128 Ok(format!(
129 "Disk usage: {:.1}% ({} / {} bytes)",
130 used_percent, stats.used_bytes, stats.total_bytes
131 ))
132}
133
134fn list_by_inode<B: FsFuse>(fs: &B, inode: u64) -> Result<Vec<(String, u64)>, FsError> {
138 let path = fs.inode_to_path(inode)?;
139 let mut entries = Vec::new();
140
141 for entry in fs.read_dir(&path)? {
142 let entry = entry?;
143 let child_inode = fs.path_to_inode(&entry.path)?;
144 entries.push((entry.name, child_inode));
145 }
146
147 Ok(entries)
148}
149
150fn atomic_write<B: FsPosix>(fs: &B, path: &Path, data: &[u8]) -> Result<(), FsError> {
154 let handle = fs.open(path, OpenFlags::WRITE)?;
156
157 fs.lock(handle, LockType::Exclusive)?;
159
160 fs.write_at(handle, data, 0)?;
162
163 fs.unlock(handle)?;
165 fs.close(handle)?;
166
167 Ok(())
168}
169
170fn process_with_trait_object(fs: &dyn Fs, files: &[&Path]) -> Result<u64, FsError> {
178 let mut total_size = 0;
179 for path in files {
180 let meta = fs.metadata(path)?;
181 total_size += meta.size;
182 }
183 Ok(total_size)
184}
185
186struct MultiBackend {
188 backends: Vec<Box<dyn Fs>>,
189}
190
191impl MultiBackend {
192 fn new() -> Self {
193 Self {
194 backends: Vec::new(),
195 }
196 }
197
198 fn add<B: Fs + 'static>(&mut self, backend: B) {
199 self.backends.push(Box::new(backend));
200 }
201
202 fn find_file(&self, name: &str) -> Option<(usize, PathBuf)> {
203 for (i, fs) in self.backends.iter().enumerate() {
204 if let Ok(entries) = fs.read_dir(Path::new("/")) {
205 for entry in entries.flatten() {
206 if entry.name == name {
207 return Some((i, entry.path));
208 }
209 }
210 }
211 }
212 None
213 }
214}
215
216struct DemoFs {
235 files: RwLock<HashMap<PathBuf, Vec<u8>>>,
236 dirs: RwLock<std::collections::HashSet<PathBuf>>,
237 symlinks: RwLock<HashMap<PathBuf, PathBuf>>,
238 inodes: RwLock<HashMap<PathBuf, u64>>,
239 inode_to_path: RwLock<HashMap<u64, PathBuf>>,
240 handles: RwLock<HashMap<u64, (PathBuf, OpenFlags)>>,
241 next_inode: AtomicU64,
242 next_handle: AtomicU64,
243}
244
245impl DemoFs {
246 fn new() -> Self {
247 let fs = Self {
248 files: RwLock::new(HashMap::new()),
249 dirs: RwLock::new(std::collections::HashSet::new()),
250 symlinks: RwLock::new(HashMap::new()),
251 inodes: RwLock::new(HashMap::new()),
252 inode_to_path: RwLock::new(HashMap::new()),
253 handles: RwLock::new(HashMap::new()),
254 next_inode: AtomicU64::new(2),
255 next_handle: AtomicU64::new(1),
256 };
257 fs.dirs.write().unwrap().insert(PathBuf::from("/"));
258 fs.assign_inode(Path::new("/"));
259 fs
260 }
261
262 fn assign_inode(&self, path: &Path) -> u64 {
263 let mut inodes = self.inodes.write().unwrap();
264 if let Some(&inode) = inodes.get(path) {
265 return inode;
266 }
267 let inode = if path == Path::new("/") {
268 ROOT_INODE
269 } else {
270 self.next_inode.fetch_add(1, Ordering::SeqCst)
271 };
272 inodes.insert(path.to_path_buf(), inode);
273 self.inode_to_path
274 .write()
275 .unwrap()
276 .insert(inode, path.to_path_buf());
277 inode
278 }
279
280 fn get_file_type(&self, path: &Path) -> Option<FileType> {
281 if self.symlinks.read().unwrap().contains_key(path) {
282 Some(FileType::Symlink)
283 } else if self.dirs.read().unwrap().contains(path) {
284 Some(FileType::Directory)
285 } else if self.files.read().unwrap().contains_key(path) {
286 Some(FileType::File)
287 } else {
288 None
289 }
290 }
291}
292
293impl FsRead for DemoFs {
295 fn read(&self, path: &Path) -> Result<Vec<u8>, FsError> {
296 self.files
297 .read()
298 .unwrap()
299 .get(path)
300 .cloned()
301 .ok_or_else(|| FsError::NotFound {
302 path: path.to_path_buf(),
303 })
304 }
305 fn read_to_string(&self, path: &Path) -> Result<String, FsError> {
306 String::from_utf8(self.read(path)?).map_err(|_| FsError::InvalidData {
307 path: path.to_path_buf(),
308 details: "not UTF-8".into(),
309 })
310 }
311 fn read_range(&self, path: &Path, offset: u64, len: usize) -> Result<Vec<u8>, FsError> {
312 let data = self.read(path)?;
313 let start = offset as usize;
314 Ok(if start >= data.len() {
315 vec![]
316 } else {
317 data[start..(start + len).min(data.len())].to_vec()
318 })
319 }
320 fn exists(&self, path: &Path) -> Result<bool, FsError> {
321 Ok(self.get_file_type(path).is_some())
322 }
323 fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
324 let ft = self.get_file_type(path).ok_or_else(|| FsError::NotFound {
325 path: path.to_path_buf(),
326 })?;
327 let size = if ft == FileType::File {
328 self.files
329 .read()
330 .unwrap()
331 .get(path)
332 .map(|d| d.len() as u64)
333 .unwrap_or(0)
334 } else {
335 0
336 };
337 Ok(Metadata {
338 file_type: ft,
339 size,
340 permissions: Permissions::default_file(),
341 created: SystemTime::UNIX_EPOCH,
342 modified: SystemTime::UNIX_EPOCH,
343 accessed: SystemTime::UNIX_EPOCH,
344 inode: self.inodes.read().unwrap().get(path).copied().unwrap_or(0),
345 nlink: 1,
346 })
347 }
348 fn open_read(&self, path: &Path) -> Result<Box<dyn Read + Send>, FsError> {
349 Ok(Box::new(std::io::Cursor::new(self.read(path)?)))
350 }
351}
352
353impl FsWrite for DemoFs {
354 fn write(&self, path: &Path, data: &[u8]) -> Result<(), FsError> {
355 self.assign_inode(path);
356 self.files
357 .write()
358 .unwrap()
359 .insert(path.to_path_buf(), data.to_vec());
360 Ok(())
361 }
362 fn append(&self, path: &Path, data: &[u8]) -> Result<(), FsError> {
363 self.files
364 .write()
365 .unwrap()
366 .entry(path.to_path_buf())
367 .or_default()
368 .extend_from_slice(data);
369 self.assign_inode(path);
370 Ok(())
371 }
372 fn remove_file(&self, path: &Path) -> Result<(), FsError> {
373 self.files
374 .write()
375 .unwrap()
376 .remove(path)
377 .map(|_| ())
378 .ok_or_else(|| FsError::NotFound {
379 path: path.to_path_buf(),
380 })
381 }
382 fn rename(&self, from: &Path, to: &Path) -> Result<(), FsError> {
383 let data = self
384 .files
385 .write()
386 .unwrap()
387 .remove(from)
388 .ok_or_else(|| FsError::NotFound {
389 path: from.to_path_buf(),
390 })?;
391 self.files.write().unwrap().insert(to.to_path_buf(), data);
392 self.assign_inode(to);
393 Ok(())
394 }
395 fn copy(&self, from: &Path, to: &Path) -> Result<(), FsError> {
396 let data = self.read(from)?;
397 self.write(to, &data)
398 }
399 fn truncate(&self, path: &Path, size: u64) -> Result<(), FsError> {
400 self.files
401 .write()
402 .unwrap()
403 .get_mut(path)
404 .ok_or_else(|| FsError::NotFound {
405 path: path.to_path_buf(),
406 })?
407 .resize(size as usize, 0);
408 Ok(())
409 }
410 fn open_write(&self, _path: &Path) -> Result<Box<dyn Write + Send>, FsError> {
411 Ok(Box::new(std::io::Cursor::new(Vec::new())))
412 }
413}
414
415impl FsDir for DemoFs {
416 fn read_dir(&self, path: &Path) -> Result<ReadDirIter, FsError> {
417 if !self.dirs.read().unwrap().contains(path) {
418 return Err(FsError::NotFound {
419 path: path.to_path_buf(),
420 });
421 }
422 let mut entries = Vec::new();
423 for (fp, data) in self.files.read().unwrap().iter() {
424 if fp.parent() == Some(path) {
425 if let Some(name) = fp.file_name() {
426 entries.push(Ok(DirEntry {
427 name: name.to_string_lossy().into(),
428 path: fp.clone(),
429 file_type: FileType::File,
430 size: data.len() as u64,
431 inode: 0,
432 }));
433 }
434 }
435 }
436 for dp in self.dirs.read().unwrap().iter() {
437 if dp.parent() == Some(path) && dp != path {
438 if let Some(name) = dp.file_name() {
439 entries.push(Ok(DirEntry {
440 name: name.to_string_lossy().into(),
441 path: dp.clone(),
442 file_type: FileType::Directory,
443 size: 0,
444 inode: 0,
445 }));
446 }
447 }
448 }
449 Ok(ReadDirIter::from_vec(entries))
450 }
451 fn create_dir(&self, path: &Path) -> Result<(), FsError> {
452 if self.dirs.read().unwrap().contains(path) {
453 return Err(FsError::AlreadyExists {
454 path: path.to_path_buf(),
455 operation: "create_dir",
456 });
457 }
458 self.dirs.write().unwrap().insert(path.to_path_buf());
459 self.assign_inode(path);
460 Ok(())
461 }
462 fn create_dir_all(&self, path: &Path) -> Result<(), FsError> {
463 let mut current = PathBuf::new();
464 for c in path.components() {
465 current.push(c);
466 self.dirs.write().unwrap().insert(current.clone());
467 self.assign_inode(¤t);
468 }
469 Ok(())
470 }
471 fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
472 if !self.dirs.write().unwrap().remove(path) {
473 return Err(FsError::NotFound {
474 path: path.to_path_buf(),
475 });
476 }
477 Ok(())
478 }
479 fn remove_dir_all(&self, path: &Path) -> Result<(), FsError> {
480 self.dirs.write().unwrap().remove(path);
481 self.files
482 .write()
483 .unwrap()
484 .retain(|p, _| !p.starts_with(path));
485 Ok(())
486 }
487}
488
489impl FsLink for DemoFs {
490 fn symlink(&self, target: &Path, link: &Path) -> Result<(), FsError> {
491 self.symlinks
492 .write()
493 .unwrap()
494 .insert(link.to_path_buf(), target.to_path_buf());
495 self.assign_inode(link);
496 Ok(())
497 }
498 fn hard_link(&self, original: &Path, link: &Path) -> Result<(), FsError> {
499 let data = self.read(original)?;
500 self.write(link, &data)
501 }
502 fn read_link(&self, path: &Path) -> Result<PathBuf, FsError> {
503 self.symlinks
504 .read()
505 .unwrap()
506 .get(path)
507 .cloned()
508 .ok_or_else(|| FsError::InvalidData {
509 path: path.to_path_buf(),
510 details: "not a symlink".into(),
511 })
512 }
513 fn symlink_metadata(&self, path: &Path) -> Result<Metadata, FsError> {
514 let ft = self.get_file_type(path).ok_or_else(|| FsError::NotFound {
515 path: path.to_path_buf(),
516 })?;
517 Ok(Metadata {
518 file_type: ft,
519 size: 0,
520 permissions: Permissions::default_file(),
521 created: SystemTime::UNIX_EPOCH,
522 modified: SystemTime::UNIX_EPOCH,
523 accessed: SystemTime::UNIX_EPOCH,
524 inode: 0,
525 nlink: 1,
526 })
527 }
528}
529
530impl FsPermissions for DemoFs {
531 fn set_permissions(&self, path: &Path, _perm: Permissions) -> Result<(), FsError> {
532 if self.get_file_type(path).is_none() {
533 return Err(FsError::NotFound {
534 path: path.to_path_buf(),
535 });
536 }
537 Ok(())
538 }
539}
540
541impl FsSync for DemoFs {
542 fn sync(&self) -> Result<(), FsError> {
543 Ok(())
544 }
545 fn fsync(&self, path: &Path) -> Result<(), FsError> {
546 if self.get_file_type(path).is_none() {
547 return Err(FsError::NotFound {
548 path: path.to_path_buf(),
549 });
550 }
551 Ok(())
552 }
553}
554
555impl FsStats for DemoFs {
556 fn statfs(&self) -> Result<StatFs, FsError> {
557 Ok(StatFs {
558 total_bytes: 100 * 1024 * 1024,
559 used_bytes: 50 * 1024 * 1024,
560 available_bytes: 50 * 1024 * 1024,
561 total_inodes: 10000,
562 used_inodes: 1000,
563 available_inodes: 9000,
564 block_size: 4096,
565 max_name_len: 255,
566 })
567 }
568}
569
570impl FsInode for DemoFs {
571 fn path_to_inode(&self, path: &Path) -> Result<u64, FsError> {
572 self.inodes
573 .read()
574 .unwrap()
575 .get(path)
576 .copied()
577 .ok_or_else(|| FsError::NotFound {
578 path: path.to_path_buf(),
579 })
580 }
581 fn inode_to_path(&self, inode: u64) -> Result<PathBuf, FsError> {
582 self.inode_to_path
583 .read()
584 .unwrap()
585 .get(&inode)
586 .cloned()
587 .ok_or(FsError::InodeNotFound { inode })
588 }
589 fn lookup(&self, parent: u64, name: &OsStr) -> Result<u64, FsError> {
590 let parent_path = self.inode_to_path(parent)?;
591 self.path_to_inode(&parent_path.join(name))
592 }
593 fn metadata_by_inode(&self, inode: u64) -> Result<Metadata, FsError> {
594 self.metadata(&self.inode_to_path(inode)?)
595 }
596}
597
598impl FsHandles for DemoFs {
599 fn open(&self, path: &Path, flags: OpenFlags) -> Result<Handle, FsError> {
600 if flags.create && !self.files.read().unwrap().contains_key(path) {
601 self.write(path, &[])?;
602 } else if !self.files.read().unwrap().contains_key(path) {
603 return Err(FsError::NotFound {
604 path: path.to_path_buf(),
605 });
606 }
607 let id = self.next_handle.fetch_add(1, Ordering::SeqCst);
608 self.handles
609 .write()
610 .unwrap()
611 .insert(id, (path.to_path_buf(), flags));
612 Ok(Handle(id))
613 }
614 fn read_at(&self, handle: Handle, buf: &mut [u8], offset: u64) -> Result<usize, FsError> {
615 let handles = self.handles.read().unwrap();
616 let (path, flags) = handles
617 .get(&handle.0)
618 .ok_or(FsError::InvalidHandle { handle })?;
619 if !flags.read {
620 return Err(FsError::PermissionDenied {
621 path: path.clone(),
622 operation: "read",
623 });
624 }
625 let data = self
626 .files
627 .read()
628 .unwrap()
629 .get(path)
630 .cloned()
631 .unwrap_or_default();
632 let start = offset as usize;
633 if start >= data.len() {
634 return Ok(0);
635 }
636 let n = buf.len().min(data.len() - start);
637 buf[..n].copy_from_slice(&data[start..start + n]);
638 Ok(n)
639 }
640 fn write_at(&self, handle: Handle, data: &[u8], offset: u64) -> Result<usize, FsError> {
641 let handles = self.handles.read().unwrap();
642 let (path, flags) = handles
643 .get(&handle.0)
644 .ok_or(FsError::InvalidHandle { handle })?;
645 if !flags.write {
646 return Err(FsError::PermissionDenied {
647 path: path.clone(),
648 operation: "write",
649 });
650 }
651 let path = path.clone();
652 drop(handles);
653 let mut files = self.files.write().unwrap();
654 let file_data = files.entry(path).or_default();
655 let start = offset as usize;
656 if start + data.len() > file_data.len() {
657 file_data.resize(start + data.len(), 0);
658 }
659 file_data[start..start + data.len()].copy_from_slice(data);
660 Ok(data.len())
661 }
662 fn close(&self, handle: Handle) -> Result<(), FsError> {
663 self.handles
664 .write()
665 .unwrap()
666 .remove(&handle.0)
667 .map(|_| ())
668 .ok_or(FsError::InvalidHandle { handle })
669 }
670}
671
672impl FsLock for DemoFs {
673 fn lock(&self, handle: Handle, _lock_type: LockType) -> Result<(), FsError> {
674 if !self.handles.read().unwrap().contains_key(&handle.0) {
675 return Err(FsError::InvalidHandle { handle });
676 }
677 Ok(())
678 }
679 fn try_lock(&self, handle: Handle, _lock_type: LockType) -> Result<bool, FsError> {
680 if !self.handles.read().unwrap().contains_key(&handle.0) {
681 return Err(FsError::InvalidHandle { handle });
682 }
683 Ok(true)
684 }
685 fn unlock(&self, handle: Handle) -> Result<(), FsError> {
686 if !self.handles.read().unwrap().contains_key(&handle.0) {
687 return Err(FsError::InvalidHandle { handle });
688 }
689 Ok(())
690 }
691}
692
693impl FsXattr for DemoFs {
694 fn get_xattr(&self, path: &Path, _name: &str) -> Result<Vec<u8>, FsError> {
695 Err(FsError::NotFound {
696 path: path.to_path_buf(),
697 })
698 }
699 fn set_xattr(&self, _path: &Path, _name: &str, _value: &[u8]) -> Result<(), FsError> {
700 Ok(())
701 }
702 fn remove_xattr(&self, path: &Path, name: &str) -> Result<(), FsError> {
703 Err(FsError::XattrNotFound {
704 path: path.to_path_buf(),
705 name: name.to_string(),
706 })
707 }
708 fn list_xattr(&self, _path: &Path) -> Result<Vec<String>, FsError> {
709 Ok(vec![])
710 }
711}
712
713fn main() {
718 println!("=== Generic Functions Example ===\n");
719
720 let fs = DemoFs::new();
721
722 fs.create_dir_all(Path::new("/project/src")).unwrap();
724 fs.write(Path::new("/project/README.md"), b"# My Project")
725 .unwrap();
726 fs.write(Path::new("/project/src/main.rs"), b"fn main() {}")
727 .unwrap();
728 fs.write(Path::new("/project/src/lib.rs"), b"pub fn hello() {}")
729 .unwrap();
730
731 println!("Pattern 1: Functions with Fs bound");
733 copy_file(
734 &fs,
735 Path::new("/project/README.md"),
736 Path::new("/project/README.bak"),
737 )
738 .unwrap();
739 println!(" Copied README.md to README.bak");
740
741 let count = count_files_recursive(&fs, Path::new("/project")).unwrap();
742 println!(" Total files in /project: {count}");
743
744 let rs_files = find_files(&fs, Path::new("/project"), &|e| e.name.ends_with(".rs")).unwrap();
745 println!(" Rust files found: {rs_files:?}");
746
747 println!("\nPattern 2: Functions with Fs + FsLink bounds");
749 let backup = backup_with_link(&fs, Path::new("/project/README.md")).unwrap();
750 println!(" Created backup at: {}", backup.display());
751
752 let resolved = resolve_symlinks(&fs, Path::new("/project/README.md.latest")).unwrap();
754 println!(" Resolved symlink to: {}", resolved.display());
755
756 println!("\nPattern 3: Functions with FsFull bound");
758 let usage = report_usage(&fs).unwrap();
759 println!(" {usage}");
760
761 println!("\nPattern 4: Functions with FsFuse bound");
763 let entries = list_by_inode(&fs, ROOT_INODE).unwrap();
764 println!(" Root directory contents by inode:");
765 for (name, inode) in entries {
766 println!(" {name}: inode {inode}");
767 }
768
769 println!("\nPattern 5: Functions with FsPosix bound");
771 atomic_write(&fs, Path::new("/project/config.txt"), b"key=value").unwrap();
772 println!(" Wrote config.txt atomically with locking");
773
774 println!("\nPattern 6: Trait objects for runtime polymorphism");
776 let files = [
777 Path::new("/project/README.md"),
778 Path::new("/project/src/main.rs"),
779 ];
780 let total = process_with_trait_object(&fs, &files).unwrap();
781 println!(" Total size of selected files: {total} bytes");
782
783 let mut multi = MultiBackend::new();
785 multi.add(DemoFs::new()); println!(" MultiBackend can store {} backends", multi.backends.len());
787 if let Some((idx, path)) = multi.find_file("README.md") {
788 println!(" Found file in backend {idx}: {}", path.display());
789 }
790
791 println!("\n=== All patterns demonstrated! ===");
796}