Skip to main content

fuser_ng/
fuserng.rs

1// FuserNG :: A wrapper around FUSE that presents paths instead of inodes.
2//
3// Copyright (c) 2016-2022 by William R. Fraser, 2026 by François NT
4//
5
6use std::ffi::{OsStr, OsString};
7use std::path::Path;
8use std::sync::{Arc, RwLock};
9use std::time::SystemTime;
10
11use fuser::{
12    AccessFlags, Errno, FileHandle, FopenFlags, Generation, INodeNo, LockOwner, OpenFlags,
13    RenameFlags, TimeOrNow, WriteFlags,
14};
15
16use crate::FileType;
17use crate::directory_cache::*;
18use crate::inode_table::{InodeTable, InodeToPath};
19use crate::types::*;
20
21trait IntoRequestInfo {
22    fn info(&self) -> RequestInfo;
23}
24
25impl IntoRequestInfo for fuser::Request {
26    fn info(&self) -> RequestInfo {
27        RequestInfo {
28            unique: self.unique().0,
29            uid: self.uid(),
30            gid: self.gid(),
31            pid: self.pid(),
32        }
33    }
34}
35
36fn fuse_fileattr(attr: FileAttr, ino: INodeNo) -> fuser::FileAttr {
37    fuser::FileAttr {
38        ino,
39        size: attr.size,
40        blocks: attr.blocks,
41        atime: attr.atime,
42        mtime: attr.mtime,
43        ctime: attr.ctime,
44        crtime: attr.crtime,
45        kind: attr.kind,
46        perm: attr.perm,
47        nlink: attr.nlink,
48        uid: attr.uid,
49        gid: attr.gid,
50        rdev: attr.rdev,
51        blksize: attr.blksize,
52        flags: attr.flags,
53    }
54}
55
56trait TimeOrNowExt {
57    fn time(self) -> SystemTime;
58}
59
60impl TimeOrNowExt for TimeOrNow {
61    fn time(self) -> SystemTime {
62        match self {
63            TimeOrNow::SpecificTime(t) => t,
64            TimeOrNow::Now => SystemTime::now(),
65        }
66    }
67}
68
69/// Path-oriented wrapper around a user filesystem implementation.
70///
71/// ```no_run
72/// struct EmptyFs;
73///
74/// impl fuser_ng::Filesystem for EmptyFs {}
75///
76/// # fn main() -> std::io::Result<()> {
77/// let options = [fuser_ng::MountOption::FSName("emptyfs".into())];
78///
79/// fuser_ng::mount(
80///     fuser_ng::FuserNG::new(EmptyFs),
81///     "/tmp/emptyfs",
82///     &options,
83///     fuser_ng::ThreadCount::Default,
84/// )?;
85/// # Ok(())
86/// # }
87/// ```
88#[derive(Debug)]
89pub struct FuserNG<T> {
90    target: Arc<T>,
91    table: InodeTable,
92    directory_cache: RwLock<DirectoryCache>,
93}
94
95impl<T: Filesystem + Sync + Send + 'static> FuserNG<T> {
96    /// Creates a wrapper with a fresh inode table and directory cache.
97    pub fn new(target_fs: T) -> FuserNG<T> {
98        FuserNG {
99            target: Arc::new(target_fs),
100            table: InodeTable::new(),
101            directory_cache: DirectoryCache::new().into(),
102        }
103    }
104    fn get_path(&self, ino: INodeNo) -> Option<EntryName> {
105        self.table.get_path(ino.0)
106    }
107    fn add_or_get_dir(&self, parent: INodeNo, name: &OsStr) -> Option<(u64, u64)> {
108        self.table.add_or_get_dir(parent.0, name)
109    }
110    fn add_or_get_leaf(&self, parent: INodeNo, name: &OsStr) -> Option<(u64, u64)> {
111        self.table.add_or_get_leaf(parent.0, name)
112    }
113    fn lookup(&self, ino: u64) {
114        self.table.lookup(ino);
115    }
116    fn forget(&self, ino: INodeNo, n: u64) -> u64 {
117        self.table.forget(ino.0, n)
118    }
119    fn add_leaf(&self, parent: INodeNo, name: &OsStr) -> Option<(u64, u64)> {
120        self.table.add_leaf(parent.0, name)
121    }
122    fn add_dir(&self, parent: INodeNo, name: &OsStr) -> Option<(u64, u64)> {
123        self.table.add_dir(parent.0, name)
124    }
125    fn inode_unlink(&self, parent: INodeNo, name: &OsStr) {
126        self.table.unlink(parent.0, name)
127    }
128    fn get_parent_inode(&self, ino: INodeNo) -> Option<u64> {
129        self.table.get_parent_inode(ino.0)
130    }
131    fn inode_rename(
132        &self,
133        oldparent: INodeNo,
134        oldname: &OsStr,
135        newparent: INodeNo,
136        newname: &OsStr,
137    ) -> Option<()> {
138        self.table
139            .rename(oldparent.0, oldname, newparent.0, newname)
140    }
141}
142
143macro_rules! get_entry_name {
144    ($s:expr, $ino:expr, $reply:expr) => {
145        if let Some(path) = $s.get_path($ino) {
146            path
147        } else {
148            $reply.error(Errno::EINVAL);
149            return;
150        }
151    };
152}
153
154macro_rules! resolve_from_parent {
155    ($s:expr, $ino:expr, $name:expr, $reply:expr) => {
156        if let Some(path) = $s.table.resolve_from_parent($ino.0, $name.into()) {
157            path
158        } else {
159            $reply.error(Errno::EINVAL);
160            return;
161        }
162    };
163}
164
165macro_rules! get_resolved_path {
166    ($s:expr, $ino:expr, $reply:expr) => {{ get_entry_name!($s, $ino, $reply).with($ino.0) }};
167}
168
169impl<T: Filesystem + Sync + Send + 'static> fuser::Filesystem for FuserNG<T> {
170    fn init(
171        &mut self,
172        req: &fuser::Request,
173        config: &mut fuser::KernelConfig,
174    ) -> Result<(), std::io::Error> {
175        debug!("init");
176        self.target.init(req.info(), config)
177    }
178
179    fn destroy(&mut self) {
180        debug!("destroy");
181        self.target.destroy();
182    }
183
184    fn lookup(
185        &self,
186        req: &fuser::Request,
187        parent: INodeNo,
188        name: &OsStr,
189        reply: fuser::ReplyEntry,
190    ) {
191        let path = resolve_from_parent!(self, parent, name, reply);
192        debug!("lookup: {:?}", path);
193        //let parent_path = get_folder_path!(self, parent, reply);
194        //debug!("lookup: {:?}, {:?}", parent_path, name);
195        //let path = Arc::new((*parent_path).clone().join(name));
196        match self.target.getattr(req.info(), &path, None) {
197            Ok((ttl, attr)) => {
198                let value = if attr.kind == FileType::Directory {
199                    self.add_or_get_dir(parent, name)
200                } else {
201                    self.add_or_get_leaf(parent, name)
202                };
203                if let Some((ino, generation)) = value {
204                    self.lookup(ino);
205                    reply.entry(
206                        &ttl,
207                        &fuse_fileattr(attr, INodeNo(ino)),
208                        Generation(generation),
209                    );
210                } else {
211                    reply.error(Errno::EINVAL)
212                }
213            }
214            Err(e) => reply.error(e.into()),
215        }
216    }
217
218    fn forget(&self, _req: &fuser::Request, ino: INodeNo, nlookup: u64) {
219        let lookups = self.forget(ino, nlookup);
220        let path = self
221            .get_path(ino)
222            .unwrap_or_else(|| EntryName::new(OsStr::new("").into(), OsString::from("[unknown]")));
223        debug!(
224            "forget: inode {} ({:?}) now at {} lookups",
225            ino, path, lookups
226        );
227    }
228
229    fn getattr(
230        &self,
231        req: &fuser::Request,
232        ino: INodeNo,
233        fh: Option<fuser::FileHandle>,
234        reply: fuser::ReplyAttr,
235    ) {
236        let path = get_entry_name!(self, ino, reply);
237        debug!("getattr: {:?}", path);
238        match self.target.getattr(req.info(), &path, fh.map(|fh| fh.0)) {
239            Ok((ttl, attr)) => reply.attr(&ttl, &fuse_fileattr(attr, ino)),
240            Err(e) => reply.error(e.into()),
241        }
242    }
243
244    fn setattr(
245        &self,
246        req: &fuser::Request,               // passed to all
247        ino: INodeNo,                       // translated to path; passed to all
248        mode: Option<u32>,                  // chmod
249        uid: Option<u32>,                   // chown
250        gid: Option<u32>,                   // chown
251        size: Option<u64>,                  // truncate
252        atime: Option<TimeOrNow>,           // utimens
253        mtime: Option<TimeOrNow>,           // utimens
254        _ctime: Option<SystemTime>,         // ? TODO
255        fh: Option<fuser::FileHandle>,      // passed to all
256        crtime: Option<SystemTime>,         // utimens_osx  (OS X only)
257        chgtime: Option<SystemTime>,        // utimens_osx  (OS X only)
258        bkuptime: Option<SystemTime>,       // utimens_osx  (OS X only)
259        flags: Option<fuser::BsdFileFlags>, // utimens_osx  (OS X only)
260        reply: fuser::ReplyAttr,
261    ) {
262        let path = get_resolved_path!(self, ino, reply);
263        debug!("setattr: {:?}", path);
264
265        debug!("\tino:\t{:?}", ino);
266        debug!("\tmode:\t{:?}", mode);
267        debug!("\tuid:\t{:?}", uid);
268        debug!("\tgid:\t{:?}", gid);
269        debug!("\tsize:\t{:?}", size);
270        debug!("\tatime:\t{:?}", atime);
271        debug!("\tmtime:\t{:?}", mtime);
272        debug!("\tfh:\t{:?}", fh);
273
274        // TODO: figure out what C FUSE does when only some of these are implemented.
275
276        if let Some(mode) = mode
277            && let Err(e) = self
278                .target
279                .chmod(req.info(), &path, fh.map(|fh| fh.0), mode)
280        {
281            reply.error(e.into());
282            return;
283        }
284
285        if (uid.is_some() || gid.is_some())
286            && let Err(e) = self
287                .target
288                .chown(req.info(), &path, fh.map(|fh| fh.0), uid, gid)
289        {
290            reply.error(e.into());
291            return;
292        }
293
294        if let Some(size) = size
295            && let Err(e) = self
296                .target
297                .truncate(req.info(), &path, fh.map(|fh| fh.0), size)
298        {
299            reply.error(e.into());
300            return;
301        }
302
303        if atime.is_some() || mtime.is_some() {
304            let atime = atime.map(TimeOrNowExt::time);
305            let mtime = mtime.map(TimeOrNowExt::time);
306            if let Err(e) = self
307                .target
308                .utimens(req.info(), &path, fh.map(|fh| fh.0), atime, mtime)
309            {
310                reply.error(e.into());
311                return;
312            }
313        }
314
315        if (crtime.is_some() || chgtime.is_some() || bkuptime.is_some() || flags.is_some())
316            && let Err(e) = self.target.utimens_macos(
317                req.info(),
318                &path,
319                fh.map(|fh| fh.0),
320                crtime,
321                chgtime,
322                bkuptime,
323                flags.map(|flags| flags.bits()),
324            )
325        {
326            reply.error(e.into());
327            return;
328        }
329
330        match self
331            .target
332            .getattr(req.info(), &path.entry_name(), fh.map(|fh| fh.0))
333        {
334            Ok((ttl, attr)) => reply.attr(&ttl, &fuse_fileattr(attr, ino)),
335            Err(e) => reply.error(e.into()),
336        }
337    }
338
339    fn readlink(&self, req: &fuser::Request, ino: INodeNo, reply: fuser::ReplyData) {
340        let path = get_resolved_path!(self, ino, reply);
341        debug!("readlink: {:?}", path);
342        match self.target.readlink(req.info(), &path) {
343            Ok(data) => reply.data(&data),
344            Err(e) => reply.error(e.into()),
345        }
346    }
347
348    fn mknod(
349        &self,
350        req: &fuser::Request,
351        parent: INodeNo,
352        name: &OsStr,
353        mode: u32,
354        _umask: u32, // TODO
355        rdev: u32,
356        reply: fuser::ReplyEntry,
357    ) {
358        let entry = resolve_from_parent!(self, parent, name, reply);
359        debug!("mknod: {:?}", entry);
360        match self.target.mknod(req.info(), &entry, mode, rdev) {
361            Ok((ttl, attr)) => {
362                if let Some((ino, generation)) = self.add_leaf(parent, name) {
363                    reply.entry(
364                        &ttl,
365                        &fuse_fileattr(attr, INodeNo(ino)),
366                        Generation(generation),
367                    )
368                } else {
369                    reply.error(Errno::from_i32(libc::EINVAL))
370                }
371            }
372            Err(e) => reply.error(e.into()),
373        }
374    }
375
376    fn mkdir(
377        &self,
378        req: &fuser::Request,
379        parent: INodeNo,
380        name: &OsStr,
381        mode: u32,
382        _umask: u32, // TODO
383        reply: fuser::ReplyEntry,
384    ) {
385        let entry = resolve_from_parent!(self, parent, name, reply);
386        debug!("mkdir: {:?} (mode={:#o})", entry, mode);
387        match self.target.mkdir(req.info(), &entry, mode) {
388            Ok((ttl, attr)) => {
389                if let Some((ino, generation)) = self.add_dir(parent, name) {
390                    reply.entry(
391                        &ttl,
392                        &fuse_fileattr(attr, INodeNo(ino)),
393                        Generation(generation),
394                    )
395                } else {
396                    reply.error(Errno::from_i32(libc::EINVAL))
397                }
398            }
399            Err(e) => reply.error(e.into()),
400        }
401    }
402
403    fn unlink(
404        &self,
405        req: &fuser::Request,
406        parent: INodeNo,
407        name: &OsStr,
408        reply: fuser::ReplyEmpty,
409    ) {
410        let entry = resolve_from_parent!(self, parent, name, reply);
411        debug!("unlink: {:?}", entry);
412        match self.target.unlink(req.info(), &entry) {
413            Ok(()) => {
414                self.inode_unlink(parent, name);
415                reply.ok()
416            }
417            Err(e) => reply.error(e.into()),
418        }
419    }
420
421    fn rmdir(&self, req: &fuser::Request, parent: INodeNo, name: &OsStr, reply: fuser::ReplyEmpty) {
422        let entry = resolve_from_parent!(self, parent, name, reply);
423        debug!("rmdir: {:?}", entry);
424        match self.target.rmdir(req.info(), &entry) {
425            Ok(()) => {
426                self.inode_unlink(parent, name);
427                reply.ok()
428            }
429            Err(e) => reply.error(e.into()),
430        }
431    }
432
433    fn symlink(
434        &self,
435        req: &fuser::Request,
436        parent: INodeNo,
437        name: &OsStr,
438        link: &Path,
439        reply: fuser::ReplyEntry,
440    ) {
441        let entry = resolve_from_parent!(self, parent, name, reply);
442        debug!("symlink: {:?} -> {:?}", entry, link);
443        match self.target.symlink(req.info(), &entry, link) {
444            Ok((ttl, attr)) => {
445                if let Some((ino, generation)) = self.add_leaf(parent, name) {
446                    reply.entry(
447                        &ttl,
448                        &fuse_fileattr(attr, INodeNo(ino)),
449                        Generation(generation),
450                    )
451                } else {
452                    reply.error(Errno::EINVAL)
453                }
454            }
455            Err(e) => reply.error(e.into()),
456        }
457    }
458
459    fn rename(
460        &self,
461        req: &fuser::Request,
462        parent: INodeNo,
463        name: &OsStr,
464        newparent: INodeNo,
465        newname: &OsStr,
466        _flags: RenameFlags, // TODO
467        reply: fuser::ReplyEmpty,
468    ) {
469        let entry = resolve_from_parent!(self, parent, name, reply);
470        let new_entry = resolve_from_parent!(self, newparent, newname, reply);
471        debug!("rename: {:?} -> {:?}", entry, new_entry);
472        match self.target.rename(req.info(), &entry, &new_entry) {
473            Ok(()) => {
474                self.inode_rename(parent, name, newparent, newname);
475                reply.ok()
476            }
477            Err(e) => reply.error(e.into()),
478        }
479    }
480
481    fn link(
482        &self,
483        req: &fuser::Request,
484        ino: INodeNo,
485        newparent: INodeNo,
486        newname: &OsStr,
487        reply: fuser::ReplyEntry,
488    ) {
489        let path = get_resolved_path!(self, ino, reply);
490        let new_entry = resolve_from_parent!(self, newparent, newname, reply);
491        debug!("link: {:?} -> {:?}", path, new_entry);
492        match self.target.link(req.info(), &path, &new_entry) {
493            Ok((ttl, attr)) => {
494                // NOTE: this results in the new link having a different inode from the original.
495                // This is needed because our inode table is a 1:1 map between paths and inodes.
496                if let Some((new_ino, generation)) = self.add_leaf(newparent, newname) {
497                    reply.entry(
498                        &ttl,
499                        &fuse_fileattr(attr, INodeNo(new_ino)),
500                        Generation(generation),
501                    );
502                } else {
503                    reply.error(Errno::EINVAL);
504                }
505            }
506            Err(e) => reply.error(e.into()),
507        }
508    }
509
510    fn open(&self, req: &fuser::Request, ino: INodeNo, flags: OpenFlags, reply: fuser::ReplyOpen) {
511        let path = get_resolved_path!(self, ino, reply);
512        debug!("open: {:?}", path);
513        match self.target.open(req.info(), &path, flags.0 as u32) {
514            // TODO: change flags to i32
515            Ok((fh, flags)) => reply.opened(FileHandle(fh), FopenFlags::from_bits_retain(flags)),
516            Err(e) => reply.error(e.into()),
517        }
518    }
519
520    fn read(
521        &self,
522        req: &fuser::Request,
523        ino: INodeNo,
524        fh: FileHandle,
525        offset: u64,
526        size: u32,
527        _flags: OpenFlags,              // TODO
528        _lock_owner: Option<LockOwner>, // TODO
529        reply: fuser::ReplyData,
530    ) {
531        let path = get_resolved_path!(self, ino, reply);
532        debug!("read: {:?} {:#x} @ {:#x}", path, size, offset);
533        self.target
534            .read(req.info(), &path, fh.0, offset, size, |result| {
535                match result {
536                    Ok(data) => reply.data(data),
537                    Err(e) => reply.error(e.into()),
538                }
539                CallbackResult {
540                    _private: std::marker::PhantomData {},
541                }
542            });
543    }
544
545    fn write(
546        &self,
547        req: &fuser::Request,
548        ino: INodeNo,
549        fh: FileHandle,
550        offset: u64,
551        data: &[u8],
552        _write_flags: WriteFlags, // TODO
553        flags: OpenFlags,
554        _lock_owner: Option<LockOwner>, // TODO
555        reply: fuser::ReplyWrite,
556    ) {
557        let path = get_resolved_path!(self, ino, reply);
558        debug!("write: {:?} {:#x} @ {:#x}", path, data.len(), offset);
559        // The target API owns the write buffer, while fuser gives us borrowed request data.
560        let data_buf = Vec::from(data);
561        match self
562            .target
563            .write(req.info(), &path, fh.0, offset, data_buf, flags.0 as u32)
564        {
565            Ok(written) => reply.written(written),
566            Err(e) => reply.error(e.into()),
567        }
568    }
569
570    fn flush(
571        &self,
572        req: &fuser::Request,
573        ino: INodeNo,
574        fh: FileHandle,
575        lock_owner: LockOwner,
576        reply: fuser::ReplyEmpty,
577    ) {
578        let path = get_resolved_path!(self, ino, reply);
579        debug!("flush: {:?}", path);
580        match self.target.flush(req.info(), &path, fh.0, lock_owner.0) {
581            Ok(()) => reply.ok(),
582            Err(e) => reply.error(e.into()),
583        }
584    }
585
586    fn release(
587        &self,
588        req: &fuser::Request,
589        ino: INodeNo,
590        fh: FileHandle,
591        flags: OpenFlags,
592        lock_owner: Option<LockOwner>,
593        flush: bool,
594        reply: fuser::ReplyEmpty,
595    ) {
596        let path = get_resolved_path!(self, ino, reply);
597        debug!("release: {:?}", path);
598        match self.target.release(
599            req.info(),
600            &path,
601            fh.0,
602            flags.0 as u32,
603            lock_owner.map(|owner| owner.0).unwrap_or(0), /* TODO */
604            flush,
605        ) {
606            Ok(()) => reply.ok(),
607            Err(e) => reply.error(e.into()),
608        }
609    }
610
611    fn fsync(
612        &self,
613        req: &fuser::Request,
614        ino: INodeNo,
615        fh: FileHandle,
616        datasync: bool,
617        reply: fuser::ReplyEmpty,
618    ) {
619        let path = get_resolved_path!(self, ino, reply);
620        debug!("fsync: {:?}", path);
621        match self.target.fsync(req.info(), &path, fh.0, datasync) {
622            Ok(()) => reply.ok(),
623            Err(e) => reply.error(e.into()),
624        }
625    }
626
627    fn opendir(
628        &self,
629        req: &fuser::Request,
630        ino: INodeNo,
631        flags: OpenFlags,
632        reply: fuser::ReplyOpen,
633    ) {
634        let path = get_resolved_path!(self, ino, reply);
635        debug!("opendir: {:?}", path);
636        match self.target.opendir(req.info(), &path, flags.0 as u32) {
637            Ok((fh, flags)) => {
638                let dcache_key = self.directory_cache.write().unwrap().new_entry(fh);
639                reply.opened(FileHandle(dcache_key), FopenFlags::from_bits_retain(flags));
640            }
641            Err(e) => reply.error(e.into()),
642        }
643    }
644
645    fn readdir(
646        &self,
647        req: &fuser::Request,
648        ino: INodeNo,
649        fh: FileHandle,
650        offset: u64,
651        mut reply: fuser::ReplyDirectory,
652    ) {
653        let path = get_resolved_path!(self, ino, reply);
654        debug!("readdir: {:?} @ {}", path, offset);
655
656        let parent_inode = if ino == INodeNo::ROOT {
657            ino
658        } else {
659            match self.get_parent_inode(ino) {
660                Some(inode) => INodeNo(inode),
661                None => {
662                    error!("readdir: unable to get parent inode for {:?}", &path);
663                    reply.error(Errno::EIO);
664                    return;
665                }
666            }
667        };
668
669        let cached_entries = {
670            self.directory_cache
671                .write()
672                .unwrap()
673                .get_mut(fh.0)
674                .entries
675                .clone()
676        };
677
678        let entries = match cached_entries {
679            Some(entries) => entries,
680            None => {
681                let real_fh = self.directory_cache.read().unwrap().real_fh(fh.0);
682                debug!("entries not yet fetched; requesting with fh {}", real_fh);
683                match self.target.readdir(req.info(), &path, real_fh) {
684                    Ok(entries) => {
685                        self.directory_cache.write().unwrap().get_mut(fh.0).entries =
686                            Some(entries.clone());
687                        entries
688                    }
689                    Err(e) => {
690                        reply.error(e.into());
691                        return;
692                    }
693                }
694            }
695        };
696
697        debug!("directory has {} entries", entries.len());
698
699        for (index, entry) in entries.iter().skip(offset as usize).enumerate() {
700            let entry_inode = if entry.name == Path::new(".") {
701                ino
702            } else if entry.name == Path::new("..") {
703                parent_inode
704            } else {
705                // Don't bother looking in the inode table for the entry; FUSE doesn't pre-
706                // populate its inode cache with this value, so subsequent access to these
707                // files is going to involve it issuing a LOOKUP operation anyway.
708                INodeNo(!1u64)
709            };
710
711            debug!(
712                "readdir: adding entry #{}, {:?}",
713                offset + index as u64,
714                entry.name
715            );
716
717            let buffer_full: bool = reply.add(
718                entry_inode,
719                offset + index as u64 + 1,
720                entry.kind,
721                entry.name.as_os_str(),
722            );
723
724            if buffer_full {
725                debug!("readdir: reply buffer is full");
726                break;
727            }
728        }
729
730        reply.ok();
731    }
732
733    fn releasedir(
734        &self,
735        req: &fuser::Request,
736        ino: INodeNo,
737        fh: FileHandle,
738        flags: OpenFlags,
739        reply: fuser::ReplyEmpty,
740    ) {
741        let path = get_resolved_path!(self, ino, reply);
742        debug!("releasedir: {:?}", path);
743        let real_fh = self.directory_cache.read().unwrap().real_fh(fh.0);
744        match self
745            .target
746            .releasedir(req.info(), &path, real_fh, flags.0 as u32)
747        {
748            Ok(()) => reply.ok(),
749            Err(e) => reply.error(e.into()),
750        }
751        self.directory_cache.write().unwrap().delete(fh.0);
752    }
753
754    fn fsyncdir(
755        &self,
756        req: &fuser::Request,
757        ino: INodeNo,
758        fh: FileHandle,
759        datasync: bool,
760        reply: fuser::ReplyEmpty,
761    ) {
762        let path = get_resolved_path!(self, ino, reply);
763        debug!("fsyncdir: {:?} (datasync: {:?})", path, datasync);
764        let real_fh = self.directory_cache.read().unwrap().real_fh(fh.0);
765        match self.target.fsyncdir(req.info(), &path, real_fh, datasync) {
766            Ok(()) => reply.ok(),
767            Err(e) => reply.error(e.into()),
768        }
769    }
770
771    fn statfs(&self, req: &fuser::Request, ino: INodeNo, reply: fuser::ReplyStatfs) {
772        let path = get_resolved_path!(self, ino, reply);
773        debug!("statfs: {:?}", path);
774        match self.target.statfs(req.info(), &path) {
775            Ok(statfs) => reply.statfs(
776                statfs.blocks,
777                statfs.bfree,
778                statfs.bavail,
779                statfs.files,
780                statfs.ffree,
781                statfs.bsize,
782                statfs.namelen,
783                statfs.frsize,
784            ),
785            Err(e) => reply.error(e.into()),
786        }
787    }
788
789    fn setxattr(
790        &self,
791        req: &fuser::Request,
792        ino: INodeNo,
793        name: &OsStr,
794        value: &[u8],
795        flags: i32,
796        position: u32,
797        reply: fuser::ReplyEmpty,
798    ) {
799        let path = get_resolved_path!(self, ino, reply);
800        debug!(
801            "setxattr: {:?} {:?} ({} bytes, flags={:#x}, pos={:#x}",
802            path,
803            name,
804            value.len(),
805            flags,
806            position
807        );
808        match self
809            .target
810            .setxattr(req.info(), &path, name, value, flags as u32, position)
811        {
812            Ok(()) => reply.ok(),
813            Err(e) => reply.error(e.into()),
814        }
815    }
816
817    fn getxattr(
818        &self,
819        req: &fuser::Request,
820        ino: INodeNo,
821        name: &OsStr,
822        size: u32,
823        reply: fuser::ReplyXattr,
824    ) {
825        let path = get_resolved_path!(self, ino, reply);
826        debug!("getxattr: {:?} {:?}", path, name);
827        match self.target.getxattr(req.info(), &path, name, size) {
828            Ok(Xattr::Size(size)) => {
829                debug!("getxattr: sending size {}", size);
830                reply.size(size)
831            }
832            Ok(Xattr::Data(vec)) => {
833                debug!("getxattr: sending {} bytes", vec.len());
834                reply.data(&vec)
835            }
836            Err(e) => {
837                debug!("getxattr: error {}", e);
838                reply.error(e.into())
839            }
840        }
841    }
842
843    fn listxattr(&self, req: &fuser::Request, ino: INodeNo, size: u32, reply: fuser::ReplyXattr) {
844        let path = get_resolved_path!(self, ino, reply);
845        debug!("listxattr: {:?}", path);
846        match self.target.listxattr(req.info(), &path, size) {
847            Ok(Xattr::Size(size)) => {
848                debug!("listxattr: sending size {}", size);
849                reply.size(size)
850            }
851            Ok(Xattr::Data(vec)) => {
852                debug!("listxattr: sending {} bytes", vec.len());
853                reply.data(&vec)
854            }
855            Err(e) => reply.error(e.into()),
856        }
857    }
858
859    fn removexattr(
860        &self,
861        req: &fuser::Request,
862        ino: INodeNo,
863        name: &OsStr,
864        reply: fuser::ReplyEmpty,
865    ) {
866        let path = get_resolved_path!(self, ino, reply);
867        debug!("removexattr: {:?}, {:?}", path, name);
868        match self.target.removexattr(req.info(), &path, name) {
869            Ok(()) => reply.ok(),
870            Err(e) => reply.error(e.into()),
871        }
872    }
873
874    fn access(
875        &self,
876        req: &fuser::Request,
877        ino: INodeNo,
878        mask: AccessFlags,
879        reply: fuser::ReplyEmpty,
880    ) {
881        let path = get_resolved_path!(self, ino, reply);
882        debug!("access: {:?}, mask={:#o}", path, mask.bits());
883        match self.target.access(req.info(), &path, mask.bits() as u32) {
884            Ok(()) => reply.ok(),
885            Err(e) => reply.error(e.into()),
886        }
887    }
888
889    fn create(
890        &self,
891        req: &fuser::Request,
892        parent: INodeNo,
893        name: &OsStr,
894        mode: u32,
895        _umask: u32, // TODO
896        flags: i32,
897        reply: fuser::ReplyCreate,
898    ) {
899        let entry = resolve_from_parent!(self, parent, name, reply);
900        debug!("create: {:?} (mode={:#o}, flags={:#x})", entry, mode, flags);
901        match self.target.create(req.info(), &entry, mode, flags as u32) {
902            Ok(create) => {
903                if let Some((ino, generation)) = self.add_leaf(parent, name) {
904                    let attr = fuse_fileattr(create.attr, INodeNo(ino));
905                    reply.created(
906                        &create.ttl,
907                        &attr,
908                        Generation(generation),
909                        FileHandle(create.fh),
910                        FopenFlags::from_bits_retain(create.flags),
911                    );
912                } else {
913                    reply.error(Errno::EINVAL);
914                }
915            }
916            Err(e) => reply.error(e.into()),
917        }
918    }
919
920    // getlk
921
922    // setlk
923
924    // bmap
925
926    #[cfg(target_os = "macos")]
927    fn setvolname(&self, req: &fuser::Request, name: &OsStr, reply: fuser::ReplyEmpty) {
928        debug!("setvolname: {:?}", name);
929        match self.target.setvolname(req.info(), name) {
930            Ok(()) => reply.ok(),
931            Err(e) => reply.error(e.into()),
932        }
933    }
934
935    // exchange (macOS only, undocumented)
936
937    #[cfg(target_os = "macos")]
938    fn getxtimes(&self, req: &fuser::Request, ino: INodeNo, reply: fuser::ReplyXTimes) {
939        let path = get_resolved_path!(self, ino, reply);
940        debug!("getxtimes: {:?}", path);
941        match self.target.getxtimes(req.info(), &path) {
942            Ok(xtimes) => {
943                reply.xtimes(xtimes.bkuptime, xtimes.crtime);
944            }
945            Err(e) => reply.error(e.into()),
946        }
947    }
948}