Skip to main content

fuser_ng/
types.rs

1// Public types exported by FuserNG.
2//
3// Copyright (c) 2016-2022 by William R. Fraser, 2026 by François NT
4//
5
6use crate::KernelConfig;
7use std::ffi::{OsStr, OsString};
8use std::ops::Deref;
9use std::path::{Path, PathBuf};
10use std::sync::Arc;
11use std::time::{Duration, SystemTime};
12
13pub type Inode = u64;
14
15/// Info about a request.
16#[derive(Clone, Copy, Debug)]
17pub struct RequestInfo {
18    /// The unique ID assigned to this request by FUSE.
19    pub unique: u64,
20    /// The user ID of the process making the request.
21    pub uid: u32,
22    /// The group ID of the process making the request.
23    pub gid: u32,
24    /// The process ID of the process making the request.
25    pub pid: u32,
26}
27
28/// A directory entry.
29#[derive(Clone, Debug)]
30pub struct DirectoryEntry {
31    /// Name of the entry
32    pub name: OsString,
33    /// Kind of file (directory, file, pipe, etc.)
34    pub kind: crate::FileType,
35}
36
37/// Filesystem statistics.
38#[derive(Clone, Copy, Debug)]
39pub struct Statfs {
40    /// Total data blocks in the filesystem
41    pub blocks: u64,
42    /// Free blocks in filesystem
43    pub bfree: u64,
44    /// Free blocks available to unprivileged user
45    pub bavail: u64,
46    /// Total file nodes in filesystem
47    pub files: u64,
48    /// Free file nodes in filesystem
49    pub ffree: u64,
50    /// Optimal transfer block size
51    pub bsize: u32,
52    /// Maximum length of filenames
53    pub namelen: u32,
54    /// Fragment size
55    pub frsize: u32,
56}
57
58/// File attributes.
59#[derive(Clone, Copy, Debug)]
60pub struct FileAttr {
61    /// Size in bytes
62    pub size: u64,
63    /// Size in blocks
64    pub blocks: u64,
65    /// Time of last access
66    pub atime: SystemTime,
67    /// Time of last modification
68    pub mtime: SystemTime,
69    /// Time of last metadata change
70    pub ctime: SystemTime,
71    /// Time of creation (macOS only)
72    pub crtime: SystemTime,
73    /// Kind of file (directory, file, pipe, etc.)
74    pub kind: crate::FileType,
75    /// Permissions
76    pub perm: u16,
77    /// Number of hard links
78    pub nlink: u32,
79    /// User ID
80    pub uid: u32,
81    /// Group ID
82    pub gid: u32,
83    /// Device ID (if special file)
84    pub rdev: u32,
85    /// block size
86    pub blksize: u32,
87    /// Flags (macOS only; see chflags(2))
88    pub flags: u32,
89}
90
91/// The return value for `create`: contains info on the newly-created file, as well as a handle to
92/// the opened file.
93#[derive(Clone, Debug)]
94pub struct CreatedEntry {
95    /// Entry cache time-to-live.
96    pub ttl: Duration,
97    /// Attributes returned for the created file.
98    pub attr: FileAttr,
99    /// File handle returned for the opened file.
100    pub fh: u64,
101    /// Open response flags returned to fuser.
102    pub flags: u32,
103}
104
105/// Represents the return value from the `listxattr` and `getxattr` calls, which can be either a
106/// size or contain data, depending on how they are called.
107#[derive(Clone, Debug)]
108pub enum Xattr {
109    /// Size needed to read the attribute value or name list.
110    Size(u32),
111    /// Attribute data or null-terminated attribute name list.
112    Data(Vec<u8>),
113}
114
115#[cfg(target_os = "macos")]
116#[derive(Clone, Debug)]
117pub struct XTimes {
118    pub bkuptime: SystemTime,
119    pub crtime: SystemTime,
120}
121
122pub type ResultEmpty = std::io::Result<()>;
123pub type ResultEntry = std::io::Result<(Duration, FileAttr)>;
124pub type ResultOpen = std::io::Result<(u64, u32)>;
125pub type ResultReaddir = std::io::Result<Vec<DirectoryEntry>>;
126pub type ResultData = std::io::Result<Vec<u8>>;
127pub type ResultSlice<'a> = std::io::Result<&'a [u8]>;
128pub type ResultWrite = std::io::Result<u32>;
129pub type ResultStatfs = std::io::Result<Statfs>;
130pub type ResultCreate = std::io::Result<CreatedEntry>;
131pub type ResultXattr = std::io::Result<Xattr>;
132
133#[cfg(target_os = "macos")]
134pub type ResultXTimes = std::io::Result<XTimes>;
135
136/// Dummy struct returned by the callback in the `read()` method. Cannot be constructed outside
137/// this crate, `read()` requires you to return it, thus ensuring that you don't forget to call the
138/// callback.
139pub struct CallbackResult {
140    pub(crate) _private: std::marker::PhantomData<()>,
141}
142
143/// Path resolved from an inode with the current inode number attached.
144#[derive(Debug)]
145pub struct ResolvedPath {
146    parent: Arc<PathBuf>,
147    name: OsString,
148    ino: Inode,
149}
150
151impl ResolvedPath {
152    /// Returns the full path by joining the parent path and entry name.
153    pub fn full_path(&self) -> PathBuf {
154        self.parent.join(&self.name)
155    }
156    /// Returns the final path component.
157    pub fn name(&self) -> &OsStr {
158        &self.name
159    }
160    /// Returns the inode associated with this resolved path.
161    pub fn ino(&self) -> Inode {
162        self.ino
163    }
164    /// Returns the parent directory path.
165    pub fn parent_path(&self) -> Arc<PathBuf> {
166        self.parent.clone()
167    }
168    /// Returns the same path without the inode context.
169    pub fn entry_name(&self) -> EntryName {
170        EntryName {
171            parent: self.parent.clone(),
172            name: self.name.clone(),
173        }
174    }
175}
176
177/// Entry name resolved relative to a parent directory path.
178#[derive(Debug)]
179pub struct EntryName {
180    parent: Arc<PathBuf>,
181    //parent_ino: Inode,
182    name: OsString,
183}
184
185impl EntryName {
186    /// Attaches an inode to this entry name.
187    pub(crate) fn with(self, ino: Inode) -> ResolvedPath {
188        ResolvedPath {
189            parent: self.parent,
190            name: self.name,
191            ino,
192        }
193    }
194    /// Creates an entry name from a parent folder path and child name.
195    pub(crate) fn new(parent: FolderPath, name: OsString) -> Self {
196        Self {
197            parent: parent.0,
198            name,
199        }
200    }
201    /// Returns the full path by joining the parent path and entry name.
202    pub fn full_path(&self) -> PathBuf {
203        self.parent.join(&self.name)
204    }
205    /// Returns the final path component.
206    pub fn name(&self) -> &OsStr {
207        &self.name
208    }
209
210    /// Returns the parent directory path.
211    pub fn parent_path(&self) -> Arc<PathBuf> {
212        self.parent.clone()
213    }
214}
215
216/// Shared path for a directory entry in the inode table.
217#[repr(transparent)]
218#[derive(Debug)]
219pub(crate) struct FolderPath(Arc<PathBuf>);
220
221impl From<Arc<PathBuf>> for FolderPath {
222    fn from(value: Arc<PathBuf>) -> Self {
223        Self(value)
224    }
225}
226
227impl From<&OsStr> for FolderPath {
228    fn from(value: &OsStr) -> Self {
229        Self(Arc::new(value.into()))
230    }
231}
232
233impl Deref for FolderPath {
234    type Target = Arc<PathBuf>;
235    fn deref(&self) -> &Self::Target {
236        &self.0
237    }
238}
239
240fn enosys_error<T>() -> std::io::Result<T> {
241    Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
242}
243
244/// This trait must be implemented to implement a filesystem with FuserNG.
245///
246/// Methods have default ENOSYS implementations, so a filesystem can implement only the operations
247/// it supports.
248///
249/// ```no_run
250/// use std::io;
251/// use std::time::{Duration, SystemTime};
252///
253/// use fuser_ng::{EntryName, FileAttr, FileType, Filesystem, RequestInfo, ResultEntry};
254///
255/// struct RootOnly;
256///
257/// impl Filesystem for RootOnly {
258///     fn getattr(&self, _req: RequestInfo, path: &EntryName, _fh: Option<u64>) -> ResultEntry {
259///         if path.full_path() != std::path::Path::new("/") {
260///             return Err(io::Error::from_raw_os_error(libc::ENOENT));
261///         }
262///
263///         let now = SystemTime::now();
264///
265///         Ok((
266///             Duration::from_secs(1),
267///             FileAttr {
268///                 size: 0,
269///                 blocks: 0,
270///                 atime: now,
271///                 mtime: now,
272///                 ctime: now,
273///                 crtime: now,
274///                 kind: FileType::Directory,
275///                 perm: 0o755,
276///                 nlink: 2,
277///                 uid: 0,
278///                 gid: 0,
279///                 rdev: 0,
280///                 blksize: 512,
281///                 flags: 0,
282///             },
283///         ))
284///     }
285/// }
286/// ```
287#[allow(unused_variables)]
288pub trait Filesystem {
289    /// Called on mount, before any other function.
290    fn init(&self, req: RequestInfo, config: &mut KernelConfig) -> ResultEmpty {
291        Ok(())
292    }
293
294    /// Called on filesystem unmount.
295    fn destroy(&self) {
296        // Nothing.
297    }
298
299    /// Get the attributes of a filesystem entry.
300    ///
301    /// * `fh`: a file handle if this is called on an open file.
302    fn getattr(&self, req: RequestInfo, path: &EntryName, fh: Option<u64>) -> ResultEntry {
303        enosys_error()
304    }
305
306    // The following operations in the FUSE C API are all one kernel call: setattr
307    // We split them out to match the C API's behavior.
308
309    /// Change the mode of a filesystem entry.
310    ///
311    /// * `fh`: a file handle if this is called on an open file.
312    /// * `mode`: the mode to change the file to.
313    fn chmod(
314        &self,
315        req: RequestInfo,
316        path: &ResolvedPath,
317        fh: Option<u64>,
318        mode: u32,
319    ) -> ResultEmpty {
320        enosys_error()
321    }
322
323    /// Change the owner UID and/or group GID of a filesystem entry.
324    ///
325    /// * `fh`: a file handle if this is called on an open file.
326    /// * `uid`: user ID to change the file's owner to. If `None`, leave the UID unchanged.
327    /// * `gid`: group ID to change the file's group to. If `None`, leave the GID unchanged.
328    fn chown(
329        &self,
330        req: RequestInfo,
331        path: &ResolvedPath,
332        fh: Option<u64>,
333        uid: Option<u32>,
334        gid: Option<u32>,
335    ) -> ResultEmpty {
336        enosys_error()
337    }
338
339    /// Set the length of a file.
340    ///
341    /// * `fh`: a file handle if this is called on an open file.
342    /// * `size`: size in bytes to set as the file's length.
343    fn truncate(
344        &self,
345        req: RequestInfo,
346        path: &ResolvedPath,
347        fh: Option<u64>,
348        size: u64,
349    ) -> ResultEmpty {
350        enosys_error()
351    }
352
353    /// Set timestamps of a filesystem entry.
354    ///
355    /// * `fh`: a file handle if this is called on an open file.
356    /// * `atime`: the time of last access.
357    /// * `mtime`: the time of last modification.
358    fn utimens(
359        &self,
360        req: RequestInfo,
361        path: &ResolvedPath,
362        fh: Option<u64>,
363        atime: Option<SystemTime>,
364        mtime: Option<SystemTime>,
365    ) -> ResultEmpty {
366        enosys_error()
367    }
368
369    /// Set timestamps of a filesystem entry (with extra options only used on MacOS).
370    #[allow(clippy::too_many_arguments)]
371    fn utimens_macos(
372        &self,
373        req: RequestInfo,
374        path: &ResolvedPath,
375        fh: Option<u64>,
376        crtime: Option<SystemTime>,
377        chgtime: Option<SystemTime>,
378        bkuptime: Option<SystemTime>,
379        flags: Option<u32>,
380    ) -> ResultEmpty {
381        enosys_error()
382    }
383
384    // END OF SETATTR FUNCTIONS
385
386    /// Read a symbolic link.
387    fn readlink(&self, req: RequestInfo, path: &ResolvedPath) -> ResultData {
388        enosys_error()
389    }
390
391    /// Create a special file.
392    ///
393    /// * `parent`: path to the directory to make the entry under.
394    /// * `name`: name of the entry.
395    /// * `mode`: mode for the new entry.
396    /// * `rdev`: if mode has the bits `S_IFCHR` or `S_IFBLK` set, this is the major and minor numbers for the device file. Otherwise it should be ignored.
397    fn mknod(&self, req: RequestInfo, entry: &EntryName, mode: u32, rdev: u32) -> ResultEntry {
398        enosys_error()
399    }
400
401    /// Create a directory.
402    ///
403    /// * `parent`: path to the directory to make the directory under.
404    /// * `name`: name of the directory.
405    /// * `mode`: permissions for the new directory.
406    fn mkdir(&self, req: RequestInfo, entry: &EntryName, mode: u32) -> ResultEntry {
407        enosys_error()
408    }
409
410    /// Remove a file.
411    ///
412    /// * `parent`: path to the directory containing the file to delete.
413    /// * `name`: name of the file to delete.
414    fn unlink(&self, req: RequestInfo, entry: &EntryName) -> ResultEmpty {
415        enosys_error()
416    }
417
418    /// Remove a directory.
419    ///
420    /// * `parent`: path to the directory containing the directory to delete.
421    /// * `name`: name of the directory to delete.
422    fn rmdir(&self, req: RequestInfo, entry: &EntryName) -> ResultEmpty {
423        enosys_error()
424    }
425
426    /// Create a symbolic link.
427    ///
428    /// * `parent`: path to the directory to make the link in.
429    /// * `name`: name of the symbolic link.
430    /// * `target`: path (may be relative or absolute) to the target of the link.
431    fn symlink(&self, req: RequestInfo, entry: &EntryName, target: &Path) -> ResultEntry {
432        enosys_error()
433    }
434
435    /// Rename a filesystem entry.
436    ///
437    /// * `parent`: path to the directory containing the existing entry.
438    /// * `name`: name of the existing entry.
439    /// * `newparent`: path to the directory it should be renamed into (may be the same as `parent`).
440    /// * `newname`: name of the new entry.
441    fn rename(&self, req: RequestInfo, entry: &EntryName, new_entry: &EntryName) -> ResultEmpty {
442        enosys_error()
443    }
444
445    /// Create a hard link.
446    ///
447    /// * `path`: path to an existing file.
448    /// * `newparent`: path to the directory for the new link.
449    /// * `newname`: name for the new link.
450    fn link(&self, req: RequestInfo, path: &ResolvedPath, new_entry: &EntryName) -> ResultEntry {
451        enosys_error()
452    }
453
454    /// Open a file.
455    ///
456    /// * `path`: path to the file.
457    /// * `flags`: one of `O_RDONLY`, `O_WRONLY`, or `O_RDWR`, plus maybe additional flags.
458    ///
459    /// Return a tuple of (file handle, flags). The file handle will be passed to any subsequent
460    /// calls that operate on the file, and can be any value you choose, though it should allow
461    /// your filesystem to identify the file opened even without any path info.
462    fn open(&self, req: RequestInfo, path: &ResolvedPath, flags: u32) -> ResultOpen {
463        enosys_error()
464    }
465
466    /// Read from a file.
467    ///
468    /// Note that it is not an error for this call to request to read past the end of the file, and
469    /// you should only return data up to the end of the file (i.e. the number of bytes returned
470    /// will be fewer than requested; possibly even zero). Do not extend the file in this case.
471    ///
472    /// * `path`: path to the file.
473    /// * `fh`: file handle returned from the `open` call.
474    /// * `offset`: offset into the file to start reading.
475    /// * `size`: number of bytes to read.
476    /// * `callback`: a callback that must be invoked to return the result of the operation: either
477    ///   the result data as a slice, or an error code.
478    ///
479    /// Return the return value from the `callback` function.
480    fn read(
481        &self,
482        req: RequestInfo,
483        path: &ResolvedPath,
484        fh: u64,
485        offset: u64,
486        size: u32,
487        callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult,
488    ) -> CallbackResult {
489        callback(enosys_error())
490    }
491
492    /// Write to a file.
493    ///
494    /// * `path`: path to the file.
495    /// * `fh`: file handle returned from the `open` call.
496    /// * `offset`: offset into the file to start writing.
497    /// * `data`: the data to write
498    /// * `flags`:
499    ///
500    /// Return the number of bytes written.
501    fn write(
502        &self,
503        req: RequestInfo,
504        path: &ResolvedPath,
505        fh: u64,
506        offset: u64,
507        data: Vec<u8>,
508        flags: u32,
509    ) -> ResultWrite {
510        enosys_error()
511    }
512
513    /// Called each time a program calls `close` on an open file.
514    ///
515    /// Note that because file descriptors can be duplicated (by `dup`, `dup2`, `fork`) this may be
516    /// called multiple times for a given file handle. The main use of this function is if the
517    /// filesystem would like to return an error to the `close` call. Note that most programs
518    /// ignore the return value of `close`, though.
519    ///
520    /// * `path`: path to the file.
521    /// * `fh`: file handle returned from the `open` call.
522    /// * `lock_owner`: if the filesystem supports locking (`setlk`, `getlk`), remove all locks
523    ///   belonging to this lock owner.
524    fn flush(
525        &self,
526        req: RequestInfo,
527        path: &ResolvedPath,
528        fh: u64,
529        lock_owner: u64,
530    ) -> ResultEmpty {
531        enosys_error()
532    }
533
534    /// Called when an open file is closed.
535    ///
536    /// There will be one of these for each `open` call. After `release`, no more calls will be
537    /// made with the given file handle.
538    ///
539    /// * `path`: path to the file.
540    /// * `fh`: file handle returned from the `open` call.
541    /// * `flags`: the flags passed when the file was opened.
542    /// * `lock_owner`: if the filesystem supports locking (`setlk`, `getlk`), remove all locks
543    ///   belonging to this lock owner.
544    /// * `flush`: whether pending data must be flushed or not.
545    fn release(
546        &self,
547        req: RequestInfo,
548        path: &ResolvedPath,
549        fh: u64,
550        flags: u32,
551        lock_owner: u64,
552        flush: bool,
553    ) -> ResultEmpty {
554        enosys_error()
555    }
556
557    /// Write out any pending changes of a file.
558    ///
559    /// When this returns, data should be written to persistent storage.
560    ///
561    /// * `path`: path to the file.
562    /// * `fh`: file handle returned from the `open` call.
563    /// * `datasync`: if `false`, also write metadata, otherwise just write file data.
564    fn fsync(&self, req: RequestInfo, path: &ResolvedPath, fh: u64, datasync: bool) -> ResultEmpty {
565        enosys_error()
566    }
567
568    /// Open a directory.
569    ///
570    /// Analogous to the `opend` call.
571    ///
572    /// * `path`: path to the directory.
573    /// * `flags`: file access flags. Will contain `O_DIRECTORY` at least.
574    ///
575    /// Return a tuple of (file handle, flags). The file handle will be passed to any subsequent
576    /// calls that operate on the directory, and can be any value you choose, though it should
577    /// allow your filesystem to identify the directory opened even without any path info.
578    fn opendir(&self, req: RequestInfo, path: &ResolvedPath, flags: u32) -> ResultOpen {
579        enosys_error()
580    }
581
582    /// Get the entries of a directory.
583    ///
584    /// * `path`: path to the directory.
585    /// * `fh`: file handle returned from the `opendir` call.
586    ///
587    /// Return all the entries of the directory.
588    fn readdir(&self, req: RequestInfo, path: &ResolvedPath, fh: u64) -> ResultReaddir {
589        enosys_error()
590    }
591
592    /// Close an open directory.
593    ///
594    /// This will be called exactly once for each `opendir` call.
595    ///
596    /// * `path`: path to the directory.
597    /// * `fh`: file handle returned from the `opendir` call.
598    /// * `flags`: the file access flags passed to the `opendir` call.
599    fn releasedir(
600        &self,
601        req: RequestInfo,
602        path: &ResolvedPath,
603        fh: u64,
604        flags: u32,
605    ) -> ResultEmpty {
606        enosys_error()
607    }
608
609    /// Write out any pending changes to a directory.
610    ///
611    /// Analogous to the `fsync` call.
612    fn fsyncdir(
613        &self,
614        req: RequestInfo,
615        path: &ResolvedPath,
616        fh: u64,
617        datasync: bool,
618    ) -> ResultEmpty {
619        enosys_error()
620    }
621
622    /// Get filesystem statistics.
623    ///
624    /// * `path`: path to some folder in the filesystem.
625    ///
626    /// See the `Statfs` struct for more details.
627    fn statfs(&self, req: RequestInfo, path: &ResolvedPath) -> ResultStatfs {
628        enosys_error()
629    }
630
631    /// Set a file extended attribute.
632    ///
633    /// * `path`: path to the file.
634    /// * `name`: attribute name.
635    /// * `value`: the data to set the value to.
636    /// * `flags`: can be either `XATTR_CREATE` or `XATTR_REPLACE`.
637    /// * `position`: offset into the attribute value to write data.
638    fn setxattr(
639        &self,
640        req: RequestInfo,
641        path: &ResolvedPath,
642        name: &OsStr,
643        value: &[u8],
644        flags: u32,
645        position: u32,
646    ) -> ResultEmpty {
647        enosys_error()
648    }
649
650    /// Get a file extended attribute.
651    ///
652    /// * `path`: path to the file
653    /// * `name`: attribute name.
654    /// * `size`: the maximum number of bytes to read.
655    ///
656    /// If `size` is 0, return `Xattr::Size(n)` where `n` is the size of the attribute data.
657    /// Otherwise, return `Xattr::Data(data)` with the requested data.
658    fn getxattr(
659        &self,
660        req: RequestInfo,
661        path: &ResolvedPath,
662        name: &OsStr,
663        size: u32,
664    ) -> ResultXattr {
665        enosys_error()
666    }
667
668    /// List extended attributes for a file.
669    ///
670    /// * `path`: path to the file.
671    /// * `size`: maximum number of bytes to return.
672    ///
673    /// If `size` is 0, return `Xattr::Size(n)` where `n` is the size required for the list of
674    /// attribute names.
675    /// Otherwise, return `Xattr::Data(data)` where `data` is all the null-terminated attribute
676    /// names.
677    fn listxattr(&self, req: RequestInfo, path: &ResolvedPath, size: u32) -> ResultXattr {
678        enosys_error()
679    }
680
681    /// Remove an extended attribute for a file.
682    ///
683    /// * `path`: path to the file.
684    /// * `name`: name of the attribute to remove.
685    fn removexattr(&self, req: RequestInfo, path: &ResolvedPath, name: &OsStr) -> ResultEmpty {
686        enosys_error()
687    }
688
689    /// Check for access to a file.
690    ///
691    /// * `path`: path to the file.
692    /// * `mask`: mode bits to check for access to.
693    ///
694    /// Return `Ok(())` if all requested permissions are allowed, otherwise return `Err(EACCES)`
695    /// or other error code as appropriate (e.g. `ENOENT` if the file doesn't exist).
696    fn access(&self, req: RequestInfo, path: &ResolvedPath, mask: u32) -> ResultEmpty {
697        enosys_error()
698    }
699
700    /// Create and open a new file.
701    ///
702    /// * `parent`: path to the directory to create the file in.
703    /// * `name`: name of the file to be created.
704    /// * `mode`: the mode to set on the new file.
705    /// * `flags`: flags like would be passed to `open`.
706    ///
707    /// Return a `CreatedEntry` (which contains the new file's attributes as well as a file handle
708    /// -- see documentation on `open` for more info on that).
709    fn create(&self, req: RequestInfo, entry: &EntryName, mode: u32, flags: u32) -> ResultCreate {
710        enosys_error()
711    }
712
713    // getlk
714
715    // setlk
716
717    // bmap
718
719    /// macOS only: Rename the volume.
720    ///
721    /// * `name`: new name for the volume
722    #[cfg(target_os = "macos")]
723    fn setvolname(&self, req: RequestInfo, name: &OsStr) -> ResultEmpty {
724        enosys_error()
725    }
726
727    // exchange (macOS only, undocumented)
728
729    /// macOS only: Query extended times (bkuptime and crtime).
730    ///
731    /// * `path`: path to the file to get the times for.
732    ///
733    /// Return an `XTimes` struct with the times, or other error code as appropriate.
734    #[cfg(target_os = "macos")]
735    fn getxtimes(&self, req: RequestInfo, path: &ResolvedPath) -> ResultXTimes {
736        Err(libc::ENOSYS)
737    }
738}