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