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