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}