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, Mutex, RwLock};
9use std::time::SystemTime;
10
11#[cfg(not(feature = "legacy_readdir"))]
12use fuser::InitFlags;
13use fuser::{
14    AccessFlags, Errno, FileHandle, FopenFlags, Generation, INodeNo, LockOwner, OpenFlags,
15    RenameFlags, TimeOrNow, WriteFlags,
16};
17
18use crate::FileType;
19use crate::directory_cache::*;
20use crate::inode_table::{InodeTable, InodeToPath};
21use crate::types::*;
22
23type ReaddirIterator = Box<dyn Iterator<Item = std::io::Result<Vec<DirectoryEntry>>> + Send>;
24type ReaddirSlot = Mutex<Option<ReaddirState<ReaddirIterator, DirectoryEntry>>>;
25
26#[cfg(feature = "legacy_readdir")]
27type LegacyReaddirIterator =
28    Box<dyn Iterator<Item = std::io::Result<Vec<LegacyDirectoryEntry>>> + Send>;
29#[cfg(feature = "legacy_readdir")]
30type LegacyReaddirSlot = Mutex<Option<ReaddirState<LegacyReaddirIterator, LegacyDirectoryEntry>>>;
31
32/// FUSE directory reply backed by the modern readdir stream.
33enum ReaddirReply {
34    #[cfg(not(feature = "legacy_readdir"))]
35    Plain(fuser::ReplyDirectory),
36    Plus(fuser::ReplyDirectoryPlus),
37}
38
39impl ReaddirReply {
40    /// Completes the reply successfully.
41    fn ok(self) {
42        match self {
43            #[cfg(not(feature = "legacy_readdir"))]
44            Self::Plain(reply) => reply.ok(),
45            Self::Plus(reply) => reply.ok(),
46        }
47    }
48
49    /// Completes the reply with an error.
50    fn error(self, error: Errno) {
51        match self {
52            #[cfg(not(feature = "legacy_readdir"))]
53            Self::Plain(reply) => reply.error(error),
54            Self::Plus(reply) => reply.error(error),
55        }
56    }
57}
58
59trait IntoRequestInfo {
60    fn info(&self) -> RequestInfo;
61}
62
63impl IntoRequestInfo for fuser::Request {
64    fn info(&self) -> RequestInfo {
65        RequestInfo {
66            unique: self.unique().0,
67            uid: self.uid(),
68            gid: self.gid(),
69            pid: self.pid(),
70        }
71    }
72}
73
74fn fuse_fileattr(attr: FileAttr, ino: INodeNo) -> fuser::FileAttr {
75    fuser::FileAttr {
76        ino,
77        size: attr.size,
78        blocks: attr.blocks,
79        atime: attr.atime,
80        mtime: attr.mtime,
81        ctime: attr.ctime,
82        crtime: attr.crtime,
83        kind: attr.kind,
84        perm: attr.perm,
85        nlink: attr.nlink,
86        uid: attr.uid,
87        gid: attr.gid,
88        rdev: attr.rdev,
89        blksize: attr.blksize,
90        flags: attr.flags,
91    }
92}
93
94trait TimeOrNowExt {
95    fn time(self) -> SystemTime;
96}
97
98impl TimeOrNowExt for TimeOrNow {
99    fn time(self) -> SystemTime {
100        match self {
101            TimeOrNow::SpecificTime(t) => t,
102            TimeOrNow::Now => SystemTime::now(),
103        }
104    }
105}
106
107/// Path-oriented wrapper around a user filesystem implementation.
108///
109/// ```no_run
110/// struct EmptyFs;
111///
112/// impl fuser_ng::Filesystem for EmptyFs {}
113///
114/// # fn main() -> std::io::Result<()> {
115/// let options = [fuser_ng::MountOption::FSName("emptyfs".into())];
116///
117/// fuser_ng::mount(
118///     fuser_ng::FuserNG::new(EmptyFs),
119///     "/tmp/emptyfs",
120///     &options,
121///     fuser_ng::ThreadCount::Default,
122/// )?;
123/// # Ok(())
124/// # }
125/// ```
126#[derive(Debug)]
127pub struct FuserNG<T> {
128    target: Arc<T>,
129    table: InodeTable,
130    directory_cache: RwLock<DirectoryCache>,
131    readdir_cache: RwLock<ReaddirCache<ReaddirSlot>>,
132    #[cfg(feature = "legacy_readdir")]
133    legacy_readdir_cache: RwLock<ReaddirCache<LegacyReaddirSlot>>,
134}
135
136impl<T: Filesystem + Sync + Send + 'static> FuserNG<T> {
137    /// Creates a wrapper with a fresh inode table and directory cache.
138    pub fn new(target_fs: T) -> FuserNG<T> {
139        FuserNG {
140            target: Arc::new(target_fs),
141            table: InodeTable::new(),
142            directory_cache: DirectoryCache::new().into(),
143            readdir_cache: ReaddirCache::new().into(),
144            #[cfg(feature = "legacy_readdir")]
145            legacy_readdir_cache: ReaddirCache::new().into(),
146        }
147    }
148    fn get_path(&self, ino: INodeNo) -> Option<EntryName> {
149        self.table.get_path(ino.0)
150    }
151    fn add_or_get_dir(&self, parent: INodeNo, name: &OsStr) -> Option<(u64, u64)> {
152        self.table.add_or_get_dir(parent.0, name)
153    }
154    fn add_or_get_leaf(&self, parent: INodeNo, name: &OsStr) -> Option<(u64, u64)> {
155        self.table.add_or_get_leaf(parent.0, name)
156    }
157    fn create_or_get_leaf(&self, parent: INodeNo, name: &OsStr) -> Option<(bool, u64, u64)> {
158        self.table.create_or_get_leaf(parent.0, name)
159    }
160    fn lookup(&self, ino: u64) {
161        self.table.lookup(ino);
162    }
163    fn forget(&self, ino: INodeNo, n: u64) -> u64 {
164        self.table.forget(ino.0, n)
165    }
166    fn add_leaf(&self, parent: INodeNo, name: &OsStr) -> Option<(u64, u64)> {
167        self.table.add_leaf(parent.0, name)
168    }
169    fn add_dir(&self, parent: INodeNo, name: &OsStr) -> Option<(u64, u64)> {
170        self.table.add_dir(parent.0, name)
171    }
172    fn inode_unlink(&self, parent: INodeNo, name: &OsStr) {
173        self.table.unlink(parent.0, name)
174    }
175    fn get_parent_inode(&self, ino: INodeNo) -> Option<u64> {
176        self.table.get_parent_inode(ino.0)
177    }
178    fn inode_rename(
179        &self,
180        oldparent: INodeNo,
181        oldname: &OsStr,
182        newparent: INodeNo,
183        newname: &OsStr,
184    ) -> Option<()> {
185        self.table
186            .rename(oldparent.0, oldname, newparent.0, newname)
187    }
188
189    /// Adds an entry to a plain FUSE readdir reply without creating a lookup reference.
190    #[cfg(not(feature = "legacy_readdir"))]
191    fn add_plain_readdir_entry(
192        &self,
193        reply: &mut fuser::ReplyDirectory,
194        entry: &DirectoryEntry,
195        ino: INodeNo,
196        parent_inode: INodeNo,
197        entry_offset: u64,
198    ) -> bool {
199        let entry_inode = if entry.name == Path::new(".") {
200            ino
201        } else if entry.name == Path::new("..") {
202            parent_inode
203        } else {
204            INodeNo(!1u64)
205        };
206        reply.add(
207            entry_inode,
208            entry_offset,
209            entry.attr.kind,
210            entry.name.as_os_str(),
211        )
212    }
213
214    /// Adds an entry to a FUSE readdirplus reply and records its lookup on success.
215    fn add_readdirplus_entry(
216        &self,
217        reply: &mut fuser::ReplyDirectoryPlus,
218        entry: &DirectoryEntry,
219        ino: INodeNo,
220        parent_inode: INodeNo,
221        entry_offset: u64,
222    ) -> Result<bool, Errno> {
223        let (entry_inode, generation, count_lookup) = if entry.name == Path::new(".") {
224            (ino, Generation(0), false)
225        } else if entry.name == Path::new("..") {
226            (parent_inode, Generation(0), false)
227        } else {
228            let inode = if entry.attr.kind == FileType::Directory {
229                self.add_or_get_dir(ino, &entry.name)
230            } else {
231                self.add_or_get_leaf(ino, &entry.name)
232            };
233            let Some((inode, generation)) = inode else {
234                return Err(Errno::EINVAL);
235            };
236            (INodeNo(inode), Generation(generation), true)
237        };
238
239        let attr = fuse_fileattr(entry.attr, entry_inode);
240        let buffer_full = reply.add(
241            entry_inode,
242            entry_offset,
243            entry.name.as_os_str(),
244            &entry.ttl,
245            &attr,
246            generation,
247        );
248        if !buffer_full && count_lookup {
249            self.lookup(entry_inode.0);
250        }
251        Ok(buffer_full)
252    }
253
254    /// Adds a modern entry to either form of FUSE directory reply.
255    fn add_readdir_entry(
256        &self,
257        reply: &mut ReaddirReply,
258        entry: &DirectoryEntry,
259        ino: INodeNo,
260        parent_inode: INodeNo,
261        entry_offset: u64,
262    ) -> Result<bool, Errno> {
263        match reply {
264            #[cfg(not(feature = "legacy_readdir"))]
265            ReaddirReply::Plain(reply) => {
266                Ok(self.add_plain_readdir_entry(reply, entry, ino, parent_inode, entry_offset))
267            }
268            ReaddirReply::Plus(reply) => {
269                self.add_readdirplus_entry(reply, entry, ino, parent_inode, entry_offset)
270            }
271        }
272    }
273
274    /// Fills one FUSE directory reply and reports whether the producer must be retained.
275    fn fill_readdir<I>(
276        &self,
277        state: &mut ReaddirState<ReaddirIterator, DirectoryEntry>,
278        mut producer: Option<&mut I>,
279        ino: INodeNo,
280        parent_inode: INodeNo,
281        offset: u64,
282        mut reply: ReaddirReply,
283    ) -> bool
284    where
285        I: Iterator<Item = std::io::Result<Vec<DirectoryEntry>>> + ?Sized,
286    {
287        let Ok(mut index) = usize::try_from(offset) else {
288            reply.error(Errno::EINVAL);
289            return producer.is_some();
290        };
291        if index > state.entries.len() {
292            reply.error(Errno::EINVAL);
293            return producer.is_some();
294        }
295
296        let mut added = false;
297        loop {
298            if index < state.entries.len() {
299                let entry = &state.entries[index];
300                let entry_offset = match u64::try_from(index)
301                    .ok()
302                    .and_then(|value| value.checked_add(1))
303                {
304                    Some(offset) => offset,
305                    None => {
306                        if added {
307                            reply.ok();
308                        } else {
309                            reply.error(Errno::EOVERFLOW);
310                        }
311                        return producer.is_some();
312                    }
313                };
314
315                let buffer_full = match self.add_readdir_entry(
316                    &mut reply,
317                    entry,
318                    ino,
319                    parent_inode,
320                    entry_offset,
321                ) {
322                    Ok(buffer_full) => buffer_full,
323                    Err(error) => {
324                        if added {
325                            reply.ok();
326                        } else {
327                            reply.error(error);
328                        }
329                        return producer.is_some();
330                    }
331                };
332                if buffer_full {
333                    if added {
334                        reply.ok();
335                    } else {
336                        reply.error(Errno::EOVERFLOW);
337                    }
338                    return producer.is_some();
339                }
340                added = true;
341                index += 1;
342                continue;
343            }
344
345            if let Some(error) = state.pending_error {
346                if added {
347                    reply.ok();
348                } else {
349                    reply.error(error);
350                }
351                return false;
352            }
353
354            let Some(source) = producer.as_deref_mut() else {
355                reply.ok();
356                return false;
357            };
358            match source.next() {
359                Some(Ok(entries)) => state.entries.extend(entries),
360                Some(Err(error)) => {
361                    producer = None;
362                    state.pending_error = Some(error.into());
363                }
364                None => producer = None,
365            }
366        }
367    }
368
369    /// Fills one legacy FUSE readdir reply from its independent streaming producer.
370    #[cfg(feature = "legacy_readdir")]
371    fn fill_legacy_readdir<I>(
372        &self,
373        state: &mut ReaddirState<LegacyReaddirIterator, LegacyDirectoryEntry>,
374        mut producer: Option<&mut I>,
375        ino: INodeNo,
376        parent_inode: INodeNo,
377        offset: u64,
378        mut reply: fuser::ReplyDirectory,
379    ) -> bool
380    where
381        I: Iterator<Item = std::io::Result<Vec<LegacyDirectoryEntry>>> + ?Sized,
382    {
383        let Ok(mut index) = usize::try_from(offset) else {
384            reply.error(Errno::EINVAL);
385            return producer.is_some();
386        };
387        if index > state.entries.len() {
388            reply.error(Errno::EINVAL);
389            return producer.is_some();
390        }
391
392        let mut added = false;
393        loop {
394            if index < state.entries.len() {
395                let entry = &state.entries[index];
396                let entry_offset = match u64::try_from(index)
397                    .ok()
398                    .and_then(|value| value.checked_add(1))
399                {
400                    Some(offset) => offset,
401                    None => {
402                        if added {
403                            reply.ok();
404                        } else {
405                            reply.error(Errno::EOVERFLOW);
406                        }
407                        return producer.is_some();
408                    }
409                };
410                let entry_inode = if entry.name == Path::new(".") {
411                    ino
412                } else if entry.name == Path::new("..") {
413                    parent_inode
414                } else {
415                    INodeNo(!1u64)
416                };
417                if reply.add(
418                    entry_inode,
419                    entry_offset,
420                    entry.kind,
421                    entry.name.as_os_str(),
422                ) {
423                    if added {
424                        reply.ok();
425                    } else {
426                        reply.error(Errno::EOVERFLOW);
427                    }
428                    return producer.is_some();
429                }
430                added = true;
431                index += 1;
432                continue;
433            }
434
435            if let Some(error) = state.pending_error {
436                if added {
437                    reply.ok();
438                } else {
439                    reply.error(error);
440                }
441                return false;
442            }
443
444            let Some(source) = producer.as_deref_mut() else {
445                reply.ok();
446                return false;
447            };
448            match source.next() {
449                Some(Ok(entries)) => state.entries.extend(entries),
450                Some(Err(error)) => {
451                    producer = None;
452                    state.pending_error = Some(error.into());
453                }
454                None => producer = None,
455            }
456        }
457    }
458}
459
460macro_rules! get_entry_name {
461    ($s:expr, $ino:expr, $reply:expr) => {
462        if let Some(path) = $s.get_path($ino) {
463            path
464        } else {
465            $reply.error(Errno::EINVAL);
466            return;
467        }
468    };
469}
470
471macro_rules! resolve_from_parent {
472    ($s:expr, $ino:expr, $name:expr, $reply:expr) => {
473        if let Some(path) = $s.table.resolve_from_parent($ino.0, $name.into()) {
474            path
475        } else {
476            $reply.error(Errno::EINVAL);
477            return;
478        }
479    };
480}
481
482macro_rules! get_resolved_path {
483    ($s:expr, $ino:expr, $reply:expr) => {{ get_entry_name!($s, $ino, $reply).with($ino.0) }};
484}
485
486impl<T: Filesystem + Sync + Send + 'static> fuser::Filesystem for FuserNG<T> {
487    fn init(
488        &mut self,
489        req: &fuser::Request,
490        config: &mut fuser::KernelConfig,
491    ) -> Result<(), std::io::Error> {
492        debug!("init");
493        self.target.init(req.info(), config)?;
494        #[cfg(not(feature = "legacy_readdir"))]
495        if let Err(unsupported) = config.add_capabilities(InitFlags::FUSE_DO_READDIRPLUS) {
496            warn!("kernel does not support FUSE_READDIRPLUS: {unsupported:?}");
497        }
498        Ok(())
499    }
500
501    fn destroy(&mut self) {
502        debug!("destroy");
503        self.target.destroy();
504    }
505
506    fn lookup(
507        &self,
508        req: &fuser::Request,
509        parent: INodeNo,
510        name: &OsStr,
511        reply: fuser::ReplyEntry,
512    ) {
513        let path = resolve_from_parent!(self, parent, name, reply);
514        debug!("lookup: {:?}", path);
515        let path = EntryRef::Lookup(path);
516        //let parent_path = get_folder_path!(self, parent, reply);
517        //debug!("lookup: {:?}, {:?}", parent_path, name);
518        //let path = Arc::new((*parent_path).clone().join(name));
519        match self.target.getattr(req.info(), &path, None) {
520            Ok((ttl, attr)) => {
521                let value = if attr.kind == FileType::Directory {
522                    self.add_or_get_dir(parent, name)
523                } else {
524                    self.add_or_get_leaf(parent, name)
525                };
526                if let Some((ino, generation)) = value {
527                    self.lookup(ino);
528                    reply.entry(
529                        &ttl,
530                        &fuse_fileattr(attr, INodeNo(ino)),
531                        Generation(generation),
532                    );
533                } else {
534                    reply.error(Errno::EINVAL)
535                }
536            }
537            Err(e) => reply.error(e.into()),
538        }
539    }
540
541    fn forget(&self, _req: &fuser::Request, ino: INodeNo, nlookup: u64) {
542        let lookups = self.forget(ino, nlookup);
543        let path = self.get_path(ino).unwrap_or_else(|| {
544            EntryName::new(
545                Arc::new(PathBuf::from(OsStr::new(""))).into(),
546                OsString::from("[unknown]").into(),
547            )
548        });
549        debug!(
550            "forget: inode {} ({:?}) now at {} lookups",
551            ino, path, lookups
552        );
553    }
554
555    fn getattr(
556        &self,
557        req: &fuser::Request,
558        ino: INodeNo,
559        fh: Option<fuser::FileHandle>,
560        reply: fuser::ReplyAttr,
561    ) {
562        let path = get_resolved_path!(self, ino, reply);
563        debug!("getattr: {:?}", path);
564        let path = EntryRef::Resolved(path);
565        match self.target.getattr(req.info(), &path, fh.map(|fh| fh.0)) {
566            Ok((ttl, attr)) => reply.attr(&ttl, &fuse_fileattr(attr, ino)),
567            Err(e) => reply.error(e.into()),
568        }
569    }
570
571    fn setattr(
572        &self,
573        req: &fuser::Request,               // passed to all
574        ino: INodeNo,                       // translated to path; passed to all
575        mode: Option<u32>,                  // chmod
576        uid: Option<u32>,                   // chown
577        gid: Option<u32>,                   // chown
578        size: Option<u64>,                  // truncate
579        atime: Option<TimeOrNow>,           // utimens
580        mtime: Option<TimeOrNow>,           // utimens
581        _ctime: Option<SystemTime>,         // ? TODO
582        fh: Option<fuser::FileHandle>,      // passed to all
583        crtime: Option<SystemTime>,         // utimens_osx  (OS X only)
584        chgtime: Option<SystemTime>,        // utimens_osx  (OS X only)
585        bkuptime: Option<SystemTime>,       // utimens_osx  (OS X only)
586        flags: Option<fuser::BsdFileFlags>, // utimens_osx  (OS X only)
587        reply: fuser::ReplyAttr,
588    ) {
589        let path = get_resolved_path!(self, ino, reply);
590        debug!("setattr: {:?}", path);
591
592        debug!("\tino:\t{:?}", ino);
593        debug!("\tmode:\t{:?}", mode);
594        debug!("\tuid:\t{:?}", uid);
595        debug!("\tgid:\t{:?}", gid);
596        debug!("\tsize:\t{:?}", size);
597        debug!("\tatime:\t{:?}", atime);
598        debug!("\tmtime:\t{:?}", mtime);
599        debug!("\tfh:\t{:?}", fh);
600
601        // TODO: figure out what C FUSE does when only some of these are implemented.
602
603        if let Some(mode) = mode
604            && let Err(e) = self
605                .target
606                .chmod(req.info(), &path, fh.map(|fh| fh.0), mode)
607        {
608            reply.error(e.into());
609            return;
610        }
611
612        if (uid.is_some() || gid.is_some())
613            && let Err(e) = self
614                .target
615                .chown(req.info(), &path, fh.map(|fh| fh.0), uid, gid)
616        {
617            reply.error(e.into());
618            return;
619        }
620
621        if let Some(size) = size
622            && let Err(e) = self
623                .target
624                .truncate(req.info(), &path, fh.map(|fh| fh.0), size)
625        {
626            reply.error(e.into());
627            return;
628        }
629
630        if atime.is_some() || mtime.is_some() {
631            let atime = atime.map(TimeOrNowExt::time);
632            let mtime = mtime.map(TimeOrNowExt::time);
633            if let Err(e) = self
634                .target
635                .utimens(req.info(), &path, fh.map(|fh| fh.0), atime, mtime)
636            {
637                reply.error(e.into());
638                return;
639            }
640        }
641
642        if (crtime.is_some() || chgtime.is_some() || bkuptime.is_some() || flags.is_some())
643            && let Err(e) = self.target.utimens_macos(
644                req.info(),
645                &path,
646                fh.map(|fh| fh.0),
647                crtime,
648                chgtime,
649                bkuptime,
650                flags.map(|flags| flags.bits()),
651            )
652        {
653            reply.error(e.into());
654            return;
655        }
656
657        let path = EntryRef::Resolved(path);
658        match self.target.getattr(req.info(), &path, fh.map(|fh| fh.0)) {
659            Ok((ttl, attr)) => reply.attr(&ttl, &fuse_fileattr(attr, ino)),
660            Err(e) => reply.error(e.into()),
661        }
662    }
663
664    fn readlink(&self, req: &fuser::Request, ino: INodeNo, reply: fuser::ReplyData) {
665        let path = get_resolved_path!(self, ino, reply);
666        debug!("readlink: {:?}", path);
667        match self.target.readlink(req.info(), &path) {
668            Ok(data) => reply.data(&data),
669            Err(e) => reply.error(e.into()),
670        }
671    }
672
673    fn mknod(
674        &self,
675        req: &fuser::Request,
676        parent: INodeNo,
677        name: &OsStr,
678        mode: u32,
679        _umask: u32, // TODO
680        rdev: u32,
681        reply: fuser::ReplyEntry,
682    ) {
683        let entry = resolve_from_parent!(self, parent, name, reply);
684        debug!("mknod: {:?}", entry);
685        match self.target.mknod(req.info(), &entry, mode, rdev) {
686            Ok((ttl, attr)) => {
687                if let Some((ino, generation)) = self.add_leaf(parent, name) {
688                    reply.entry(
689                        &ttl,
690                        &fuse_fileattr(attr, INodeNo(ino)),
691                        Generation(generation),
692                    )
693                } else {
694                    reply.error(Errno::from_i32(libc::EINVAL))
695                }
696            }
697            Err(e) => reply.error(e.into()),
698        }
699    }
700
701    fn mkdir(
702        &self,
703        req: &fuser::Request,
704        parent: INodeNo,
705        name: &OsStr,
706        mode: u32,
707        _umask: u32, // TODO
708        reply: fuser::ReplyEntry,
709    ) {
710        let entry = resolve_from_parent!(self, parent, name, reply);
711        debug!("mkdir: {:?} (mode={:#o})", entry, mode);
712        match self.target.mkdir(req.info(), &entry, mode) {
713            Ok((ttl, attr)) => {
714                if let Some((ino, generation)) = self.add_dir(parent, name) {
715                    reply.entry(
716                        &ttl,
717                        &fuse_fileattr(attr, INodeNo(ino)),
718                        Generation(generation),
719                    )
720                } else {
721                    reply.error(Errno::from_i32(libc::EINVAL))
722                }
723            }
724            Err(e) => reply.error(e.into()),
725        }
726    }
727
728    fn unlink(
729        &self,
730        req: &fuser::Request,
731        parent: INodeNo,
732        name: &OsStr,
733        reply: fuser::ReplyEmpty,
734    ) {
735        let entry = resolve_from_parent!(self, parent, name, reply);
736        debug!("unlink: {:?}", entry);
737        match self.target.unlink(req.info(), &entry) {
738            Ok(()) => {
739                self.inode_unlink(parent, name);
740                reply.ok()
741            }
742            Err(e) => reply.error(e.into()),
743        }
744    }
745
746    fn rmdir(&self, req: &fuser::Request, parent: INodeNo, name: &OsStr, reply: fuser::ReplyEmpty) {
747        let entry = resolve_from_parent!(self, parent, name, reply);
748        debug!("rmdir: {:?}", entry);
749        match self.target.rmdir(req.info(), &entry) {
750            Ok(()) => {
751                self.inode_unlink(parent, name);
752                reply.ok()
753            }
754            Err(e) => reply.error(e.into()),
755        }
756    }
757
758    fn symlink(
759        &self,
760        req: &fuser::Request,
761        parent: INodeNo,
762        name: &OsStr,
763        link: &Path,
764        reply: fuser::ReplyEntry,
765    ) {
766        let entry = resolve_from_parent!(self, parent, name, reply);
767        debug!("symlink: {:?} -> {:?}", entry, link);
768        match self.target.symlink(req.info(), &entry, link) {
769            Ok((ttl, attr)) => {
770                if let Some((ino, generation)) = self.add_leaf(parent, name) {
771                    reply.entry(
772                        &ttl,
773                        &fuse_fileattr(attr, INodeNo(ino)),
774                        Generation(generation),
775                    )
776                } else {
777                    reply.error(Errno::EINVAL)
778                }
779            }
780            Err(e) => reply.error(e.into()),
781        }
782    }
783
784    fn rename(
785        &self,
786        req: &fuser::Request,
787        parent: INodeNo,
788        name: &OsStr,
789        newparent: INodeNo,
790        newname: &OsStr,
791        _flags: RenameFlags, // TODO
792        reply: fuser::ReplyEmpty,
793    ) {
794        let entry = resolve_from_parent!(self, parent, name, reply);
795        let new_entry = resolve_from_parent!(self, newparent, newname, reply);
796        debug!("rename: {:?} -> {:?}", entry, new_entry);
797        match self.target.rename(req.info(), &entry, &new_entry) {
798            Ok(()) => {
799                self.inode_rename(parent, name, newparent, newname);
800                reply.ok()
801            }
802            Err(e) => reply.error(e.into()),
803        }
804    }
805
806    fn link(
807        &self,
808        req: &fuser::Request,
809        ino: INodeNo,
810        newparent: INodeNo,
811        newname: &OsStr,
812        reply: fuser::ReplyEntry,
813    ) {
814        let path = get_resolved_path!(self, ino, reply);
815        let new_entry = resolve_from_parent!(self, newparent, newname, reply);
816        debug!("link: {:?} -> {:?}", path, new_entry);
817        match self.target.link(req.info(), &path, &new_entry) {
818            Ok((ttl, attr)) => {
819                // NOTE: this results in the new link having a different inode from the original.
820                // This is needed because our inode table is a 1:1 map between paths and inodes.
821                if let Some((new_ino, generation)) = self.add_leaf(newparent, newname) {
822                    reply.entry(
823                        &ttl,
824                        &fuse_fileattr(attr, INodeNo(new_ino)),
825                        Generation(generation),
826                    );
827                } else {
828                    reply.error(Errno::EINVAL);
829                }
830            }
831            Err(e) => reply.error(e.into()),
832        }
833    }
834
835    fn open(&self, req: &fuser::Request, ino: INodeNo, flags: OpenFlags, reply: fuser::ReplyOpen) {
836        let path = get_resolved_path!(self, ino, reply);
837        debug!("open: {:?}", path);
838        match self.target.open(req.info(), &path, flags.0 as u32) {
839            // TODO: change flags to i32
840            Ok((fh, flags)) => reply.opened(FileHandle(fh), FopenFlags::from_bits_retain(flags)),
841            Err(e) => reply.error(e.into()),
842        }
843    }
844
845    fn read(
846        &self,
847        req: &fuser::Request,
848        ino: INodeNo,
849        fh: FileHandle,
850        offset: u64,
851        size: u32,
852        _flags: OpenFlags,              // TODO
853        _lock_owner: Option<LockOwner>, // TODO
854        reply: fuser::ReplyData,
855    ) {
856        let path = get_resolved_path!(self, ino, reply);
857        debug!("read: {:?} {:#x} @ {:#x}", path, size, offset);
858        self.target
859            .read(req.info(), &path, fh.0, offset, size, |result| {
860                match result {
861                    Ok(data) => reply.data(data),
862                    Err(e) => reply.error(e.into()),
863                }
864                CallbackResult {
865                    _private: std::marker::PhantomData {},
866                }
867            });
868    }
869
870    fn write(
871        &self,
872        req: &fuser::Request,
873        ino: INodeNo,
874        fh: FileHandle,
875        offset: u64,
876        data: &[u8],
877        _write_flags: WriteFlags, // TODO
878        flags: OpenFlags,
879        _lock_owner: Option<LockOwner>, // TODO
880        reply: fuser::ReplyWrite,
881    ) {
882        let path = get_resolved_path!(self, ino, reply);
883        debug!("write: {:?} {:#x} @ {:#x}", path, data.len(), offset);
884        // The target API owns the write buffer, while fuser gives us borrowed request data.
885        let data_buf = Vec::from(data);
886        match self
887            .target
888            .write(req.info(), &path, fh.0, offset, data_buf, flags.0 as u32)
889        {
890            Ok(written) => reply.written(written),
891            Err(e) => reply.error(e.into()),
892        }
893    }
894
895    fn flush(
896        &self,
897        req: &fuser::Request,
898        ino: INodeNo,
899        fh: FileHandle,
900        lock_owner: LockOwner,
901        reply: fuser::ReplyEmpty,
902    ) {
903        let path = get_resolved_path!(self, ino, reply);
904        debug!("flush: {:?}", path);
905        match self.target.flush(req.info(), &path, fh.0, lock_owner.0) {
906            Ok(()) => reply.ok(),
907            Err(e) => reply.error(e.into()),
908        }
909    }
910
911    fn release(
912        &self,
913        req: &fuser::Request,
914        ino: INodeNo,
915        fh: FileHandle,
916        flags: OpenFlags,
917        lock_owner: Option<LockOwner>,
918        flush: bool,
919        reply: fuser::ReplyEmpty,
920    ) {
921        let path = get_resolved_path!(self, ino, reply);
922        debug!("release: {:?}", path);
923        match self.target.release(
924            req.info(),
925            &path,
926            fh.0,
927            flags.0 as u32,
928            lock_owner.map(|owner| owner.0).unwrap_or(0), /* TODO */
929            flush,
930        ) {
931            Ok(()) => reply.ok(),
932            Err(e) => reply.error(e.into()),
933        }
934    }
935
936    fn fsync(
937        &self,
938        req: &fuser::Request,
939        ino: INodeNo,
940        fh: FileHandle,
941        datasync: bool,
942        reply: fuser::ReplyEmpty,
943    ) {
944        let path = get_resolved_path!(self, ino, reply);
945        debug!("fsync: {:?}", path);
946        match self.target.fsync(req.info(), &path, fh.0, datasync) {
947            Ok(()) => reply.ok(),
948            Err(e) => reply.error(e.into()),
949        }
950    }
951
952    fn opendir(
953        &self,
954        req: &fuser::Request,
955        ino: INodeNo,
956        flags: OpenFlags,
957        reply: fuser::ReplyOpen,
958    ) {
959        let path = get_resolved_path!(self, ino, reply);
960        debug!("opendir: {:?}", path);
961        match self.target.opendir(req.info(), &path, flags.0 as u32) {
962            Ok((fh, flags)) => {
963                let dcache_key = self.directory_cache.write().unwrap().new_entry(fh);
964                self.readdir_cache
965                    .write()
966                    .unwrap()
967                    .insert(dcache_key, Mutex::new(None));
968                #[cfg(feature = "legacy_readdir")]
969                self.legacy_readdir_cache
970                    .write()
971                    .unwrap()
972                    .insert(dcache_key, Mutex::new(None));
973                reply.opened(FileHandle(dcache_key), FopenFlags::from_bits_retain(flags));
974            }
975            Err(e) => reply.error(e.into()),
976        }
977    }
978
979    fn readdir(
980        &self,
981        req: &fuser::Request,
982        ino: INodeNo,
983        fh: FileHandle,
984        offset: u64,
985        reply: fuser::ReplyDirectory,
986    ) {
987        let path = get_resolved_path!(self, ino, reply);
988        debug!("readdir: {:?} @ {}", path, offset);
989
990        let parent_inode = if ino == INodeNo::ROOT {
991            ino
992        } else {
993            match self.get_parent_inode(ino) {
994                Some(inode) => INodeNo(inode),
995                None => {
996                    error!("readdir: unable to get parent inode for {:?}", path);
997                    reply.error(Errno::EIO);
998                    return;
999                }
1000            }
1001        };
1002
1003        #[cfg(feature = "legacy_readdir")]
1004        {
1005            let Some(slot) = self.legacy_readdir_cache.read().unwrap().get(fh.0) else {
1006                reply.error(Errno::EINVAL);
1007                return;
1008            };
1009            let mut slot = slot.lock().unwrap();
1010            if slot.is_none() {
1011                let real_fh = self.directory_cache.read().unwrap().real_fh(fh.0);
1012                let producer = self.target.legacy_readdir(req.info(), &path, real_fh);
1013                *slot = Some(ReaddirState::new(Box::new(producer)));
1014            }
1015
1016            let state = slot.as_mut().unwrap();
1017            let mut producer = state.producer.take();
1018            if self.fill_legacy_readdir(
1019                state,
1020                producer.as_deref_mut(),
1021                ino,
1022                parent_inode,
1023                offset,
1024                reply,
1025            ) {
1026                state.producer = producer;
1027            }
1028        }
1029
1030        #[cfg(not(feature = "legacy_readdir"))]
1031        {
1032            let Some(slot) = self.readdir_cache.read().unwrap().get(fh.0) else {
1033                reply.error(Errno::EINVAL);
1034                return;
1035            };
1036            let mut slot = slot.lock().unwrap();
1037            if slot.is_none() {
1038                let real_fh = self.directory_cache.read().unwrap().real_fh(fh.0);
1039                let producer = self.target.readdir(req.info(), &path, real_fh);
1040                *slot = Some(ReaddirState::new(Box::new(producer)));
1041            }
1042
1043            let state = slot.as_mut().unwrap();
1044            let mut producer = state.producer.take();
1045            if self.fill_readdir(
1046                state,
1047                producer.as_deref_mut(),
1048                ino,
1049                parent_inode,
1050                offset,
1051                ReaddirReply::Plain(reply),
1052            ) {
1053                state.producer = producer;
1054            }
1055        }
1056    }
1057
1058    fn readdirplus(
1059        &self,
1060        req: &fuser::Request,
1061        ino: INodeNo,
1062        fh: FileHandle,
1063        offset: u64,
1064        reply: fuser::ReplyDirectoryPlus,
1065    ) {
1066        let path = get_resolved_path!(self, ino, reply);
1067        debug!("readdirplus: {:?} @ {}", path, offset);
1068
1069        let parent_inode = if ino == INodeNo::ROOT {
1070            ino
1071        } else {
1072            match self.get_parent_inode(ino) {
1073                Some(inode) => INodeNo(inode),
1074                None => {
1075                    error!("readdirplus: unable to get parent inode for {:?}", path);
1076                    reply.error(Errno::EIO);
1077                    return;
1078                }
1079            }
1080        };
1081
1082        let Some(slot) = self.readdir_cache.read().unwrap().get(fh.0) else {
1083            reply.error(Errno::EINVAL);
1084            return;
1085        };
1086        let mut slot = slot.lock().unwrap();
1087
1088        if slot.is_none() {
1089            let real_fh = self.directory_cache.read().unwrap().real_fh(fh.0);
1090            let producer = self.target.readdir(req.info(), &path, real_fh);
1091            *slot = Some(ReaddirState::new(Box::new(producer)));
1092        }
1093
1094        let state = slot.as_mut().unwrap();
1095        let mut producer = state.producer.take();
1096        if self.fill_readdir(
1097            state,
1098            producer.as_deref_mut(),
1099            ino,
1100            parent_inode,
1101            offset,
1102            ReaddirReply::Plus(reply),
1103        ) {
1104            state.producer = producer;
1105        }
1106    }
1107
1108    fn releasedir(
1109        &self,
1110        req: &fuser::Request,
1111        ino: INodeNo,
1112        fh: FileHandle,
1113        flags: OpenFlags,
1114        reply: fuser::ReplyEmpty,
1115    ) {
1116        let path = get_resolved_path!(self, ino, reply);
1117        debug!("releasedir: {:?}", path);
1118        let real_fh = self.directory_cache.read().unwrap().real_fh(fh.0);
1119        match self
1120            .target
1121            .releasedir(req.info(), &path, real_fh, flags.0 as u32)
1122        {
1123            Ok(()) => reply.ok(),
1124            Err(e) => reply.error(e.into()),
1125        }
1126        self.directory_cache.write().unwrap().delete(fh.0);
1127        self.readdir_cache.write().unwrap().delete(fh.0);
1128        #[cfg(feature = "legacy_readdir")]
1129        self.legacy_readdir_cache.write().unwrap().delete(fh.0);
1130    }
1131
1132    fn fsyncdir(
1133        &self,
1134        req: &fuser::Request,
1135        ino: INodeNo,
1136        fh: FileHandle,
1137        datasync: bool,
1138        reply: fuser::ReplyEmpty,
1139    ) {
1140        let path = get_resolved_path!(self, ino, reply);
1141        debug!("fsyncdir: {:?} (datasync: {:?})", path, datasync);
1142        let real_fh = self.directory_cache.read().unwrap().real_fh(fh.0);
1143        match self.target.fsyncdir(req.info(), &path, real_fh, datasync) {
1144            Ok(()) => reply.ok(),
1145            Err(e) => reply.error(e.into()),
1146        }
1147    }
1148
1149    fn statfs(&self, req: &fuser::Request, ino: INodeNo, reply: fuser::ReplyStatfs) {
1150        let path = get_resolved_path!(self, ino, reply);
1151        debug!("statfs: {:?}", path);
1152        match self.target.statfs(req.info(), &path) {
1153            Ok(statfs) => reply.statfs(
1154                statfs.blocks,
1155                statfs.bfree,
1156                statfs.bavail,
1157                statfs.files,
1158                statfs.ffree,
1159                statfs.bsize,
1160                statfs.namelen,
1161                statfs.frsize,
1162            ),
1163            Err(e) => reply.error(e.into()),
1164        }
1165    }
1166
1167    fn setxattr(
1168        &self,
1169        req: &fuser::Request,
1170        ino: INodeNo,
1171        name: &OsStr,
1172        value: &[u8],
1173        flags: i32,
1174        position: u32,
1175        reply: fuser::ReplyEmpty,
1176    ) {
1177        let path = get_resolved_path!(self, ino, reply);
1178        debug!(
1179            "setxattr: {:?} {:?} ({} bytes, flags={:#x}, pos={:#x}",
1180            path,
1181            name,
1182            value.len(),
1183            flags,
1184            position
1185        );
1186        match self
1187            .target
1188            .setxattr(req.info(), &path, name, value, flags as u32, position)
1189        {
1190            Ok(()) => reply.ok(),
1191            Err(e) => reply.error(e.into()),
1192        }
1193    }
1194
1195    fn getxattr(
1196        &self,
1197        req: &fuser::Request,
1198        ino: INodeNo,
1199        name: &OsStr,
1200        size: u32,
1201        reply: fuser::ReplyXattr,
1202    ) {
1203        let path = get_resolved_path!(self, ino, reply);
1204        debug!("getxattr: {:?} {:?}", path, name);
1205        match self.target.getxattr(req.info(), &path, name, size) {
1206            Ok(Xattr::Size(size)) => {
1207                debug!("getxattr: sending size {}", size);
1208                reply.size(size)
1209            }
1210            Ok(Xattr::Data(vec)) => {
1211                debug!("getxattr: sending {} bytes", vec.len());
1212                reply.data(&vec)
1213            }
1214            Err(e) => {
1215                debug!("getxattr: error {}", e);
1216                reply.error(e.into())
1217            }
1218        }
1219    }
1220
1221    fn listxattr(&self, req: &fuser::Request, ino: INodeNo, size: u32, reply: fuser::ReplyXattr) {
1222        let path = get_resolved_path!(self, ino, reply);
1223        debug!("listxattr: {:?}", path);
1224        match self.target.listxattr(req.info(), &path, size) {
1225            Ok(Xattr::Size(size)) => {
1226                debug!("listxattr: sending size {}", size);
1227                reply.size(size)
1228            }
1229            Ok(Xattr::Data(vec)) => {
1230                debug!("listxattr: sending {} bytes", vec.len());
1231                reply.data(&vec)
1232            }
1233            Err(e) => reply.error(e.into()),
1234        }
1235    }
1236
1237    fn removexattr(
1238        &self,
1239        req: &fuser::Request,
1240        ino: INodeNo,
1241        name: &OsStr,
1242        reply: fuser::ReplyEmpty,
1243    ) {
1244        let path = get_resolved_path!(self, ino, reply);
1245        debug!("removexattr: {:?}, {:?}", path, name);
1246        match self.target.removexattr(req.info(), &path, name) {
1247            Ok(()) => reply.ok(),
1248            Err(e) => reply.error(e.into()),
1249        }
1250    }
1251
1252    fn access(
1253        &self,
1254        req: &fuser::Request,
1255        ino: INodeNo,
1256        mask: AccessFlags,
1257        reply: fuser::ReplyEmpty,
1258    ) {
1259        let path = get_resolved_path!(self, ino, reply);
1260        debug!("access: {:?}, mask={:#o}", path, mask.bits());
1261        match self.target.access(req.info(), &path, mask.bits() as u32) {
1262            Ok(()) => reply.ok(),
1263            Err(e) => reply.error(e.into()),
1264        }
1265    }
1266
1267    fn create(
1268        &self,
1269        req: &fuser::Request,
1270        parent: INodeNo,
1271        name: &OsStr,
1272        mode: u32,
1273        _umask: u32, // TODO
1274        flags: i32,
1275        reply: fuser::ReplyCreate,
1276    ) {
1277        let (created, path, generation) = match self.create_or_get_leaf(parent, name) {
1278            Some((created, ino, generation)) => (
1279                created,
1280                get_resolved_path!(self, INodeNo(ino), reply),
1281                generation,
1282            ),
1283            _ => {
1284                reply.error(Errno::EINVAL);
1285                return;
1286            }
1287        };
1288        debug!("create: {:?} (mode={:#o}, flags={:#x})", path, mode, flags);
1289        match self.target.create(req.info(), &path, mode, flags as u32) {
1290            Ok(create) => {
1291                if !created {
1292                    self.lookup(path.ino());
1293                }
1294                let attr = fuse_fileattr(create.attr, INodeNo(path.ino()));
1295                reply.created(
1296                    &create.ttl,
1297                    &attr,
1298                    Generation(generation),
1299                    FileHandle(create.fh),
1300                    FopenFlags::from_bits_retain(create.flags),
1301                );
1302            }
1303            Err(e) => {
1304                if created {
1305                    self.inode_unlink(parent, name);
1306                    self.forget(INodeNo(path.ino()), 1);
1307                }
1308                reply.error(e.into())
1309            }
1310        }
1311    }
1312
1313    // getlk
1314
1315    // setlk
1316
1317    // bmap
1318
1319    #[cfg(target_os = "macos")]
1320    fn setvolname(&self, req: &fuser::Request, name: &OsStr, reply: fuser::ReplyEmpty) {
1321        debug!("setvolname: {:?}", name);
1322        match self.target.setvolname(req.info(), name) {
1323            Ok(()) => reply.ok(),
1324            Err(e) => reply.error(e.into()),
1325        }
1326    }
1327
1328    // exchange (macOS only, undocumented)
1329
1330    #[cfg(target_os = "macos")]
1331    fn getxtimes(&self, req: &fuser::Request, ino: INodeNo, reply: fuser::ReplyXTimes) {
1332        let path = get_resolved_path!(self, ino, reply);
1333        debug!("getxtimes: {:?}", path);
1334        match self.target.getxtimes(req.info(), &path) {
1335            Ok(xtimes) => {
1336                reply.xtimes(xtimes.bkuptime, xtimes.crtime);
1337            }
1338            Err(e) => reply.error(e.into()),
1339        }
1340    }
1341}