fuse_mkdosfs/
lib.rs

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, // 1970-01-01 00:00:00
39    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    /// path to image
56    file_path: String,
57    /// need demonize
58    demonize: bool,
59    /// read only mode
60    read_only: bool,
61    /// Enable show bad files
62    show_bad: bool,
63    /// Enable show deleted
64    show_deleted: bool,
65    /// Use binary inverted reader
66    inverted: bool,
67    /// Offset from start of image in blocks
68    offset: u64,
69    /// Size of image in blocks
70    size: u64,
71    ///
72    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    /// Set the fuse fs's inverted.
117    pub fn set_inverted(&mut self, inverted: bool) {
118        self.inverted = inverted;
119        self.fs.set_inverted(inverted);
120    }
121
122    /// Set the fuse fs's offset.
123    pub fn set_offset(&mut self, offset: u64) {
124        self.offset = offset;
125        self.fs.set_offset_blocks(offset);
126    }
127
128    /// Set the fuse fs's offset.
129    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        // dbg!("LOOKUP: ", parent, name);
152        let last_modified = self.fs.last_modified();
153        // dbg!("LOOKUP: ", &last_modified);
154        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,  // datetime!(1979-01-29 03:00 UTC).into(),
160                mtime: last_modified,  // datetime!(1979-01-29 03:00 UTC).into(),
161                ctime: last_modified,  // datetime!(1979-01-29 03:00 UTC).into(),
162                crtime: last_modified, // datetime!(1979-01-29 03:00 UTC).into(),
163                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        // 1 => _
184        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(); //systime_from_secs(ED_UNIX_TIME);
188            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        // 2 => _
194        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,  // datetime!(1979-01-29 03:00 UTC).into(),
200                mtime: last_modified,  // datetime!(1979-01-29 03:00 UTC).into(),
201                ctime: last_modified,  // datetime!(1979-01-29 03:00 UTC).into(),
202                crtime: last_modified, // datetime!(1979-01-29 03:00 UTC).into(),
203                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        // reply.error(ENOENT);
217    }
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        // dbg!(ino, flags);
315        let access_mask = match flags & libc::O_ACCMODE {
316            libc::O_RDONLY => {
317                // Behavior is undefined, but most filesystems return EACCES
318                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            // Exactly one access mode flag must be specified
339            _ => {
340                reply.error(libc::EINVAL);
341                return;
342            }
343        };
344
345        reply.opened(0, access_mask as u32);
346        // reply.opened(0, 0);
347    }
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        // dbg!(ino, fh, offset, size, flags);
362
363        if let Some(entry) = self.fs.entrie_by_inode(ino) {
364            let file_size = entry.size as u64;
365            // Could underflow if file length is less than local_start
366            let read_size = std::cmp::min(size, file_size.saturating_sub(offset as u64) as u32);
367            // Move this to mkfdosfs::Fs
368            let real_offset = offset as u64 + entry.start_block * self.fs.block_size();
369            let mut buf = vec![0; read_size as usize];
370            // ^
371            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        // dbg!(&_ino, &_fh);
419        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                // Behavior is undefined, but most filesystems return EACCES
438                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            // Exactly one access mode flag must be specified
459            _ => {
460                reply.error(libc::EINVAL);
461                return;
462            }
463        };
464
465        reply.opened(0, access_mask as u32);
466        // reply.opened(0, 0);
467    }
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        // dbg!("Readdir", ino, offset);
479        // эту порнуху надо исправить
480        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        // фильтр надо перести в mkdosfs
518        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                // i + 1 means the index of the next entry
529                offset + 1 + i as i64,
530                from_direntry_status(entry.status),
531                &entry.name,
532            ) {
533                break;
534            }
535        }
536
537        reply.ok();
538        // reply.error(ENOSYS);
539    }
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    /// Returns image fs info
575    #[instrument(level = "trace", skip(self, _req, reply))]
576    fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) {
577        // let _ffree = (self.meta.start_block as u64 * BLOCK_SIZE as u64
578        //     - MetaOffset::DirEntriesStart as u64)
579        //     / DIR_ENTRY_SIZE as u64
580        //     - self.meta.files as u64;
581        // dbg!(_ffree);
582        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}