1use libc::{ENOENT, ENOSYS};
2use std::{
3 ffi::OsStr,
4 time::{Duration as StdDuration, SystemTime as StdSystemTime, UNIX_EPOCH as STD_UNIX_EPOCH},
5};
6use time::macros::datetime;
7
8use fuser::{
9 FileType, Filesystem, KernelConfig, ReplyAttr, ReplyBmap, ReplyCreate, ReplyData,
10 ReplyDirectory, ReplyDirectoryPlus, ReplyEmpty, ReplyEntry, ReplyIoctl, ReplyLock, ReplyLseek,
11 ReplyOpen, ReplyStatfs, ReplyWrite, ReplyXattr, Request, TimeOrNow,
12};
13use mkdosfs::{DirEntryStatus, Fs, FsError};
14
15use tracing::instrument;
16
17const ED_UNIX_TIME: u64 = 286405200;
18
19pub fn from_direntry_status(status: DirEntryStatus) -> FileType {
20 use DirEntryStatus::*;
21
22 match status {
23 Normal | Protected | LogicalDisk => FileType::RegularFile,
24 Directory => FileType::Directory,
25 BadFile => FileType::RegularFile,
26 Deleted => FileType::RegularFile,
27 }
28}
29
30fn systime_from_secs(secs: u64) -> StdSystemTime {
31 STD_UNIX_EPOCH + StdDuration::from_secs(secs)
32}
33
34const ROOT_DIR_ATTR: fuser::FileAttr = fuser::FileAttr {
35 ino: 1,
36 size: 0,
37 blocks: 0,
38 atime: STD_UNIX_EPOCH, mtime: STD_UNIX_EPOCH,
40 ctime: STD_UNIX_EPOCH,
41 crtime: STD_UNIX_EPOCH,
42 kind: FileType::Directory,
43 perm: 0o755,
44 nlink: 2,
45 uid: 1000,
46 gid: 1000,
47 rdev: 0,
48 flags: 0,
49 blksize: 512,
50};
51
52#[derive(Debug)]
53#[allow(dead_code)]
54pub struct FuseFs {
55 file_path: String,
57 demonize: bool,
59 read_only: bool,
61 show_bad: bool,
63 show_deleted: bool,
65 inverted: bool,
67 offset: u64,
69 size: u64,
71 fs: Fs,
73 _tracing_span: tracing::Span,
74}
75
76impl Default for FuseFs {
77 fn default() -> Self {
78 Self {
79 file_path: String::default(),
80 _tracing_span: tracing::span!(tracing::Level::TRACE, "FuseFs"),
81 demonize: false,
82 read_only: true,
83 show_bad: false,
84 show_deleted: false,
85 inverted: false,
86 offset: 0,
87 size: 0,
88 fs: Fs::default(),
89 }
90 }
91}
92
93impl FuseFs {
94 pub fn new(fname: &str) -> Self {
95 let fs = Fs::new(fname);
96 Self {
97 file_path: fname.into(),
98 demonize: false,
99 fs,
100 ..Default::default()
101 }
102 }
103
104 pub fn try_open(&mut self) -> Result<(), FsError> {
105 self.fs.try_open()
106 }
107
108 pub fn show_bad(&mut self, arg: bool) {
109 self.show_bad = arg;
110 }
111
112 pub fn show_deleted(&mut self, arg: bool) {
113 self.show_deleted = arg;
114 }
115
116 pub fn set_inverted(&mut self, inverted: bool) {
118 self.inverted = inverted;
119 self.fs.set_inverted(inverted);
120 }
121
122 pub fn set_offset(&mut self, offset: u64) {
124 self.offset = offset;
125 self.fs.set_offset_blocks(offset);
126 }
127
128 pub fn set_size(&mut self, size: u64) {
130 self.size = size;
131 self.fs.set_size_blocks(size);
132 }
133}
134
135impl Filesystem for FuseFs {
136 #[instrument(level = "trace")]
137 fn init(
138 &mut self,
139 _req: &Request<'_>,
140 _config: &mut KernelConfig,
141 ) -> std::result::Result<(), i32> {
142 Ok(())
143 }
144
145 fn destroy(&mut self) {}
146
147 #[instrument(level = "trace", skip(self, _req, reply))]
148 fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
149 use fuser::FileAttr;
150
151 let last_modified = self.fs.last_modified();
153 if let Some(entry) = self.fs.find_entrie(name.to_str().unwrap(), parent) {
155 let fattr = FileAttr {
156 ino: entry.inode,
157 size: entry.size as u64,
158 blocks: entry.blocks,
159 atime: last_modified, mtime: last_modified, ctime: last_modified, crtime: last_modified, kind: from_direntry_status(entry.status),
164 perm: entry.mode,
165 nlink: 1,
166 uid: 1000,
167 gid: 1000,
168 rdev: 0,
169 blksize: self.fs.block_size() as u32,
170 flags: 0,
171 };
172 reply.entry(&StdDuration::from_secs(10), &fattr, 0);
173 } else {
174 reply.error(ENOENT);
175 }
176 }
177
178 fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) {}
179
180 #[instrument(level = "trace", skip(self, _req, reply))]
181 fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
182 use fuser::FileAttr;
183 let last_modified = self.fs.last_modified();
185 if ino == 1 {
186 let mut dattr = ROOT_DIR_ATTR;
187 dattr.atime = datetime!(1979-01-29 03:00 UTC).into(); dattr.ctime = systime_from_secs(ED_UNIX_TIME);
189 dattr.mtime = systime_from_secs(ED_UNIX_TIME);
190 dattr.crtime = systime_from_secs(ED_UNIX_TIME);
191 reply.attr(&StdDuration::from_secs(10), &dattr);
192 }
193 else if let Some(entry) = self.fs.entrie_by_inode(ino) {
195 let fattr = FileAttr {
196 ino,
197 size: entry.size as u64,
198 blocks: entry.blocks,
199 atime: last_modified, mtime: last_modified, ctime: last_modified, crtime: last_modified, kind: from_direntry_status(entry.status),
204 perm: entry.mode,
205 nlink: 1,
206 uid: 1000,
207 gid: 1000,
208 rdev: 0,
209 blksize: self.fs.block_size() as u32,
210 flags: 0,
211 };
212 reply.attr(&StdDuration::from_secs(10), &fattr)
213 } else {
214 reply.error(ENOENT);
215 }
216 }
218
219 fn setattr(
220 &mut self,
221 _req: &Request<'_>,
222 _ino: u64,
223 _mode: Option<u32>,
224 _uid: Option<u32>,
225 _gid: Option<u32>,
226 _size: Option<u64>,
227 _atime: Option<TimeOrNow>,
228 _mtime: Option<TimeOrNow>,
229 _ctime: Option<StdSystemTime>,
230 _fh: Option<u64>,
231 _crtime: Option<StdSystemTime>,
232 _chgtime: Option<StdSystemTime>,
233 _bkuptime: Option<StdSystemTime>,
234 _flags: Option<u32>,
235 reply: ReplyAttr,
236 ) {
237 reply.error(ENOSYS);
238 }
239
240 fn readlink(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyData) {
241 reply.error(ENOSYS);
242 }
243
244 fn mknod(
245 &mut self,
246 _req: &Request<'_>,
247 _parent: u64,
248 _name: &OsStr,
249 _mode: u32,
250 _umask: u32,
251 _rdev: u32,
252 reply: ReplyEntry,
253 ) {
254 reply.error(ENOSYS);
255 }
256
257 fn mkdir(
258 &mut self,
259 _req: &Request<'_>,
260 _parent: u64,
261 _name: &OsStr,
262 _mode: u32,
263 _umask: u32,
264 reply: ReplyEntry,
265 ) {
266 reply.error(ENOSYS);
267 }
268
269 fn unlink(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, reply: ReplyEmpty) {
270 reply.error(ENOSYS);
271 }
272
273 fn rmdir(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, reply: ReplyEmpty) {
274 reply.error(ENOSYS);
275 }
276
277 fn symlink(
278 &mut self,
279 _req: &Request<'_>,
280 _parent: u64,
281 _name: &OsStr,
282 _link: &std::path::Path,
283 reply: ReplyEntry,
284 ) {
285 reply.error(ENOSYS);
286 }
287
288 fn rename(
289 &mut self,
290 _req: &Request<'_>,
291 _parent: u64,
292 _name: &OsStr,
293 _newparent: u64,
294 _newname: &OsStr,
295 _flags: u32,
296 reply: ReplyEmpty,
297 ) {
298 reply.error(ENOSYS);
299 }
300
301 fn link(
302 &mut self,
303 _req: &Request<'_>,
304 _ino: u64,
305 _newparent: u64,
306 _newname: &OsStr,
307 reply: ReplyEntry,
308 ) {
309 reply.error(ENOSYS);
310 }
311
312 #[instrument(level = "trace", skip(self, _req, reply))]
313 fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) {
314 let access_mask = match flags & libc::O_ACCMODE {
316 libc::O_RDONLY => {
317 if flags & libc::O_TRUNC != 0 {
319 reply.error(libc::EACCES);
320 return;
321 }
322 libc::R_OK
323 }
324 libc::O_WRONLY => {
325 if self.read_only {
326 reply.error(libc::EACCES);
327 return;
328 }
329 libc::W_OK
330 }
331 libc::O_RDWR => {
332 if self.read_only {
333 reply.error(libc::EACCES);
334 return;
335 }
336 libc::R_OK | libc::W_OK
337 }
338 _ => {
340 reply.error(libc::EINVAL);
341 return;
342 }
343 };
344
345 reply.opened(0, access_mask as u32);
346 }
348
349 #[instrument(level = "trace", skip(self, _req, reply))]
350 fn read(
351 &mut self,
352 _req: &Request<'_>,
353 ino: u64,
354 fh: u64,
355 offset: i64,
356 size: u32,
357 flags: i32,
358 _lock_owner: Option<u64>,
359 reply: ReplyData,
360 ) {
361 if let Some(entry) = self.fs.entrie_by_inode(ino) {
364 let file_size = entry.size as u64;
365 let read_size = std::cmp::min(size, file_size.saturating_sub(offset as u64) as u32);
367 let real_offset = offset as u64 + entry.start_block * self.fs.block_size();
369 let mut buf = vec![0; read_size as usize];
370 if self.fs.read_exact_at(&mut buf, real_offset).is_ok() {
372 reply.data(&buf);
373 } else {
374 reply.error(libc::EIO);
375 }
376 } else {
377 reply.error(ENOENT);
378 }
379 }
380
381 fn write(
382 &mut self,
383 _req: &Request<'_>,
384 _ino: u64,
385 _fh: u64,
386 _offset: i64,
387 _data: &[u8],
388 _write_flags: u32,
389 _flags: i32,
390 _lock_owner: Option<u64>,
391 reply: ReplyWrite,
392 ) {
393 reply.error(ENOSYS);
394 }
395
396 fn flush(
397 &mut self,
398 _req: &Request<'_>,
399 _ino: u64,
400 _fh: u64,
401 _lock_owner: u64,
402 reply: ReplyEmpty,
403 ) {
404 reply.error(ENOSYS);
405 }
406
407 #[instrument(level = "trace", skip(self, _req, reply))]
408 fn release(
409 &mut self,
410 _req: &Request<'_>,
411 _ino: u64,
412 _fh: u64,
413 _flags: i32,
414 _lock_owner: Option<u64>,
415 _flush: bool,
416 reply: ReplyEmpty,
417 ) {
418 reply.ok();
420 }
421
422 fn fsync(
423 &mut self,
424 _req: &Request<'_>,
425 _ino: u64,
426 _fh: u64,
427 _datasync: bool,
428 reply: ReplyEmpty,
429 ) {
430 reply.error(ENOSYS);
431 }
432
433 #[instrument(level = "trace", skip(self, _req, reply))]
434 fn opendir(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) {
435 let access_mask = match flags & libc::O_ACCMODE {
436 libc::O_RDONLY => {
437 if flags & libc::O_TRUNC != 0 {
439 reply.error(libc::EACCES);
440 return;
441 }
442 libc::R_OK
443 }
444 libc::O_WRONLY => {
445 if self.read_only {
446 reply.error(libc::EACCES);
447 return;
448 }
449 libc::W_OK
450 }
451 libc::O_RDWR => {
452 if self.read_only {
453 reply.error(libc::EACCES);
454 return;
455 }
456 libc::R_OK | libc::W_OK
457 }
458 _ => {
460 reply.error(libc::EINVAL);
461 return;
462 }
463 };
464
465 reply.opened(0, access_mask as u32);
466 }
468
469 #[instrument(level = "trace", skip(self, _req, reply))]
470 fn readdir(
471 &mut self,
472 _req: &Request<'_>,
473 ino: u64,
474 _fh: u64,
475 mut offset: i64,
476 mut reply: ReplyDirectory,
477 ) {
478 if offset == 0 || offset == 1 {
481 if ino == 1 {
482 if offset == 0 {
483 offset += 1;
484 if reply.add(1, offset, FileType::Directory, ".") {
485 return;
486 }
487 }
488 if offset == 1 {
489 offset += 1;
490 if reply.add(1, offset, FileType::Directory, "..") {
491 return;
492 }
493 }
494 } else {
495 if offset == 0 {
496 offset += 1;
497 if reply.add(ino, offset, FileType::Directory, ".") {
498 return;
499 }
500 }
501 if offset == 1 {
502 let entry = self.fs.entrie_by_inode(ino);
503 assert!(entry.is_some());
504 offset += 1;
505 if reply.add(
506 entry.unwrap().parent_inode,
507 offset,
508 FileType::Directory,
509 "..",
510 ) {
511 return;
512 }
513 }
514 }
515 }
516
517 for (i, entry) in self
519 .fs
520 .entries_by_parent_inode(ino)
521 .iter()
522 .filter(|&e| (!e.is_deleted || self.show_deleted) && (!e.is_bad || self.show_bad))
523 .skip((offset - 2) as usize)
524 .enumerate()
525 {
526 if reply.add(
527 entry.inode,
528 offset + 1 + i as i64,
530 from_direntry_status(entry.status),
531 &entry.name,
532 ) {
533 break;
534 }
535 }
536
537 reply.ok();
538 }
540
541 fn readdirplus(
542 &mut self,
543 _req: &Request<'_>,
544 _ino: u64,
545 _fh: u64,
546 _offset: i64,
547 reply: ReplyDirectoryPlus,
548 ) {
549 reply.error(ENOSYS);
550 }
551
552 fn releasedir(
553 &mut self,
554 _req: &Request<'_>,
555 _ino: u64,
556 _fh: u64,
557 _flags: i32,
558 reply: ReplyEmpty,
559 ) {
560 reply.ok();
561 }
562
563 fn fsyncdir(
564 &mut self,
565 _req: &Request<'_>,
566 _ino: u64,
567 _fh: u64,
568 _datasync: bool,
569 reply: ReplyEmpty,
570 ) {
571 reply.error(ENOSYS);
572 }
573
574 #[instrument(level = "trace", skip(self, _req, reply))]
576 fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) {
577 reply.statfs(
583 self.fs.disk_size(),
584 self.fs.disk_size() - self.fs.blocks(),
585 self.fs.disk_size() - self.fs.blocks() as u64,
586 self.fs.files(),
587 0,
588 self.fs.block_size() as u32,
589 14,
590 0,
591 );
592 }
593
594 fn setxattr(
595 &mut self,
596 _req: &Request<'_>,
597 _ino: u64,
598 _name: &OsStr,
599 _value: &[u8],
600 _flags: i32,
601 _position: u32,
602 reply: ReplyEmpty,
603 ) {
604 reply.error(ENOSYS);
605 }
606
607 fn getxattr(
608 &mut self,
609 _req: &Request<'_>,
610 _ino: u64,
611 _name: &OsStr,
612 _size: u32,
613 reply: ReplyXattr,
614 ) {
615 reply.error(ENOSYS);
616 }
617
618 fn listxattr(&mut self, _req: &Request<'_>, _ino: u64, _size: u32, reply: ReplyXattr) {
619 reply.error(ENOSYS);
620 }
621
622 fn removexattr(&mut self, _req: &Request<'_>, _ino: u64, _name: &OsStr, reply: ReplyEmpty) {
623 reply.error(ENOSYS);
624 }
625
626 fn access(&mut self, _req: &Request<'_>, _ino: u64, _mask: i32, reply: ReplyEmpty) {
627 reply.error(ENOSYS);
628 }
629
630 fn create(
631 &mut self,
632 _req: &Request<'_>,
633 _parent: u64,
634 _name: &OsStr,
635 _mode: u32,
636 _umask: u32,
637 _flags: i32,
638 reply: ReplyCreate,
639 ) {
640 reply.error(ENOSYS);
641 }
642
643 fn getlk(
644 &mut self,
645 _req: &Request<'_>,
646 _ino: u64,
647 _fh: u64,
648 _lock_owner: u64,
649 _start: u64,
650 _end: u64,
651 _typ: i32,
652 _pid: u32,
653 reply: ReplyLock,
654 ) {
655 reply.error(ENOSYS);
656 }
657
658 fn setlk(
659 &mut self,
660 _req: &Request<'_>,
661 _ino: u64,
662 _fh: u64,
663 _lock_owner: u64,
664 _start: u64,
665 _end: u64,
666 _typ: i32,
667 _pid: u32,
668 _sleep: bool,
669 reply: ReplyEmpty,
670 ) {
671 reply.error(ENOSYS);
672 }
673
674 fn bmap(
675 &mut self,
676 _req: &Request<'_>,
677 _ino: u64,
678 _blocksize: u32,
679 _idx: u64,
680 reply: ReplyBmap,
681 ) {
682 reply.error(ENOSYS);
683 }
684
685 fn ioctl(
686 &mut self,
687 _req: &Request<'_>,
688 _ino: u64,
689 _fh: u64,
690 _flags: u32,
691 _cmd: u32,
692 _in_data: &[u8],
693 _out_size: u32,
694 reply: ReplyIoctl,
695 ) {
696 reply.error(ENOSYS);
697 }
698
699 fn fallocate(
700 &mut self,
701 _req: &Request<'_>,
702 _ino: u64,
703 _fh: u64,
704 _offset: i64,
705 _length: i64,
706 _mode: i32,
707 reply: ReplyEmpty,
708 ) {
709 reply.error(ENOSYS);
710 }
711
712 fn lseek(
713 &mut self,
714 _req: &Request<'_>,
715 _ino: u64,
716 _fh: u64,
717 _offset: i64,
718 _whence: i32,
719 reply: ReplyLseek,
720 ) {
721 reply.error(ENOSYS);
722 }
723
724 fn copy_file_range(
725 &mut self,
726 _req: &Request<'_>,
727 _ino_in: u64,
728 _fh_in: u64,
729 _offset_in: i64,
730 _ino_out: u64,
731 _fh_out: u64,
732 _offset_out: i64,
733 _len: u64,
734 _flags: u32,
735 reply: ReplyWrite,
736 ) {
737 reply.error(ENOSYS);
738 }
739}