Skip to main content

arcbox_fs/
fuse.rs

1//! FUSE protocol implementation.
2//!
3//! This module implements the FUSE (Filesystem in Userspace) protocol structures
4//! for communication between the kernel and userspace filesystem implementation.
5//!
6//! Reference: <https://github.com/libfuse/libfuse/blob/master/include/fuse_kernel.h>
7
8// Allow casts for FUSE protocol binary compatibility
9#![allow(
10    clippy::cast_sign_loss,
11    clippy::cast_possible_wrap,
12    clippy::cast_possible_truncation
13)]
14
15use std::mem::size_of;
16
17// ============================================================================
18// Constants
19// ============================================================================
20
21/// FUSE kernel protocol major version.
22pub const FUSE_KERNEL_VERSION: u32 = 7;
23
24/// FUSE kernel protocol minor version.
25pub const FUSE_KERNEL_MINOR_VERSION: u32 = 38;
26
27/// Root inode number.
28pub const FUSE_ROOT_ID: u64 = 1;
29
30// FUSE init flags
31pub const FUSE_ASYNC_READ: u32 = 1 << 0;
32pub const FUSE_POSIX_LOCKS: u32 = 1 << 1;
33pub const FUSE_FILE_OPS: u32 = 1 << 2;
34pub const FUSE_ATOMIC_O_TRUNC: u32 = 1 << 3;
35pub const FUSE_EXPORT_SUPPORT: u32 = 1 << 4;
36pub const FUSE_BIG_WRITES: u32 = 1 << 5;
37pub const FUSE_DONT_MASK: u32 = 1 << 6;
38pub const FUSE_SPLICE_WRITE: u32 = 1 << 7;
39pub const FUSE_SPLICE_MOVE: u32 = 1 << 8;
40pub const FUSE_SPLICE_READ: u32 = 1 << 9;
41pub const FUSE_FLOCK_LOCKS: u32 = 1 << 10;
42pub const FUSE_HAS_IOCTL_DIR: u32 = 1 << 11;
43pub const FUSE_AUTO_INVAL_DATA: u32 = 1 << 12;
44pub const FUSE_DO_READDIRPLUS: u32 = 1 << 13;
45pub const FUSE_READDIRPLUS_AUTO: u32 = 1 << 14;
46pub const FUSE_ASYNC_DIO: u32 = 1 << 15;
47pub const FUSE_WRITEBACK_CACHE: u32 = 1 << 16;
48pub const FUSE_NO_OPEN_SUPPORT: u32 = 1 << 17;
49pub const FUSE_PARALLEL_DIROPS: u32 = 1 << 18;
50pub const FUSE_HANDLE_KILLPRIV: u32 = 1 << 19;
51pub const FUSE_POSIX_ACL: u32 = 1 << 20;
52pub const FUSE_ABORT_ERROR: u32 = 1 << 21;
53pub const FUSE_MAX_PAGES: u32 = 1 << 22;
54pub const FUSE_CACHE_SYMLINKS: u32 = 1 << 23;
55pub const FUSE_NO_OPENDIR_SUPPORT: u32 = 1 << 24;
56pub const FUSE_EXPLICIT_INVAL_DATA: u32 = 1 << 25;
57
58// Setattr valid flags
59pub const FATTR_MODE: u32 = 1 << 0;
60pub const FATTR_UID: u32 = 1 << 1;
61pub const FATTR_GID: u32 = 1 << 2;
62pub const FATTR_SIZE: u32 = 1 << 3;
63pub const FATTR_ATIME: u32 = 1 << 4;
64pub const FATTR_MTIME: u32 = 1 << 5;
65pub const FATTR_FH: u32 = 1 << 6;
66pub const FATTR_ATIME_NOW: u32 = 1 << 7;
67pub const FATTR_MTIME_NOW: u32 = 1 << 8;
68pub const FATTR_LOCKOWNER: u32 = 1 << 9;
69pub const FATTR_CTIME: u32 = 1 << 10;
70
71// Open flags
72pub const FOPEN_DIRECT_IO: u32 = 1 << 0;
73pub const FOPEN_KEEP_CACHE: u32 = 1 << 1;
74pub const FOPEN_NONSEEKABLE: u32 = 1 << 2;
75pub const FOPEN_CACHE_DIR: u32 = 1 << 3;
76pub const FOPEN_STREAM: u32 = 1 << 4;
77
78// ============================================================================
79// Opcodes
80// ============================================================================
81
82/// FUSE operation codes.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[repr(u32)]
85pub enum FuseOpcode {
86    Lookup = 1,
87    Forget = 2,
88    Getattr = 3,
89    Setattr = 4,
90    Readlink = 5,
91    Symlink = 6,
92    Mknod = 8,
93    Mkdir = 9,
94    Unlink = 10,
95    Rmdir = 11,
96    Rename = 12,
97    Link = 13,
98    Open = 14,
99    Read = 15,
100    Write = 16,
101    Statfs = 17,
102    Release = 18,
103    Fsync = 20,
104    Setxattr = 21,
105    Getxattr = 22,
106    Listxattr = 23,
107    Removexattr = 24,
108    Flush = 25,
109    Init = 26,
110    Opendir = 27,
111    Readdir = 28,
112    Releasedir = 29,
113    Fsyncdir = 30,
114    Getlk = 31,
115    Setlk = 32,
116    Setlkw = 33,
117    Access = 34,
118    Create = 35,
119    Interrupt = 36,
120    Bmap = 37,
121    Destroy = 38,
122    Ioctl = 39,
123    Poll = 40,
124    NotifyReply = 41,
125    BatchForget = 42,
126    Fallocate = 43,
127    Readdirplus = 44,
128    Rename2 = 45,
129    Lseek = 46,
130    CopyFileRange = 47,
131    SetupMapping = 48,
132    RemoveMapping = 49,
133}
134
135/// FUSE request header.
136#[derive(Debug, Clone, Copy)]
137#[repr(C)]
138pub struct FuseInHeader {
139    /// Total message length.
140    pub len: u32,
141    /// Operation code.
142    pub opcode: u32,
143    /// Unique request ID.
144    pub unique: u64,
145    /// Node ID.
146    pub nodeid: u64,
147    /// User ID.
148    pub uid: u32,
149    /// Group ID.
150    pub gid: u32,
151    /// Process ID.
152    pub pid: u32,
153    /// Padding.
154    pub padding: u32,
155}
156
157/// FUSE response header.
158#[derive(Debug, Clone, Copy)]
159#[repr(C)]
160pub struct FuseOutHeader {
161    /// Total message length.
162    pub len: u32,
163    /// Error code (0 on success, negative errno on error).
164    pub error: i32,
165    /// Unique request ID (must match request).
166    pub unique: u64,
167}
168
169/// File attributes.
170#[derive(Debug, Clone, Copy, Default)]
171#[repr(C)]
172pub struct FuseAttr {
173    pub ino: u64,
174    pub size: u64,
175    pub blocks: u64,
176    pub atime: u64,
177    pub mtime: u64,
178    pub ctime: u64,
179    pub atimensec: u32,
180    pub mtimensec: u32,
181    pub ctimensec: u32,
182    pub mode: u32,
183    pub nlink: u32,
184    pub uid: u32,
185    pub gid: u32,
186    pub rdev: u32,
187    pub blksize: u32,
188    pub padding: u32,
189}
190
191/// Filesystem statistics.
192#[derive(Debug, Clone, Copy, Default)]
193#[repr(C)]
194pub struct StatFs {
195    /// Total data blocks in filesystem.
196    pub blocks: u64,
197    /// Free blocks in filesystem.
198    pub bfree: u64,
199    /// Free blocks available to unprivileged user.
200    pub bavail: u64,
201    /// Total file nodes in filesystem.
202    pub files: u64,
203    /// Free file nodes in filesystem.
204    pub ffree: u64,
205    /// Optimal transfer block size.
206    pub bsize: u32,
207    /// Maximum length of filenames.
208    pub namelen: u32,
209    /// Fragment size.
210    pub frsize: u32,
211}
212
213// ============================================================================
214// Request Structures
215// ============================================================================
216
217/// FUSE_INIT request.
218#[derive(Debug, Clone, Copy)]
219#[repr(C)]
220pub struct FuseInitIn {
221    /// Major version supported by kernel.
222    pub major: u32,
223    /// Minor version supported by kernel.
224    pub minor: u32,
225    /// Maximum readahead size.
226    pub max_readahead: u32,
227    /// Flags.
228    pub flags: u32,
229}
230
231/// FUSE_GETATTR request.
232#[derive(Debug, Clone, Copy)]
233#[repr(C)]
234pub struct FuseGetattrIn {
235    /// Getattr flags.
236    pub getattr_flags: u32,
237    /// Padding.
238    pub dummy: u32,
239    /// File handle (if FUSE_GETATTR_FH is set).
240    pub fh: u64,
241}
242
243/// FUSE_SETATTR request.
244#[derive(Debug, Clone, Copy)]
245#[repr(C)]
246pub struct FuseSetattrIn {
247    /// Valid attribute mask.
248    pub valid: u32,
249    /// Padding.
250    pub padding: u32,
251    /// File handle.
252    pub fh: u64,
253    /// New size.
254    pub size: u64,
255    /// Lock owner.
256    pub lock_owner: u64,
257    /// New access time (seconds).
258    pub atime: u64,
259    /// New modification time (seconds).
260    pub mtime: u64,
261    /// New ctime (seconds).
262    pub ctime: u64,
263    /// Access time nanoseconds.
264    pub atimensec: u32,
265    /// Modification time nanoseconds.
266    pub mtimensec: u32,
267    /// Ctime nanoseconds.
268    pub ctimensec: u32,
269    /// New mode.
270    pub mode: u32,
271    /// Unused.
272    pub unused4: u32,
273    /// New UID.
274    pub uid: u32,
275    /// New GID.
276    pub gid: u32,
277    /// Unused.
278    pub unused5: u32,
279}
280
281/// FUSE_MKNOD request.
282#[derive(Debug, Clone, Copy)]
283#[repr(C)]
284pub struct FuseMknodIn {
285    /// File mode.
286    pub mode: u32,
287    /// Device number.
288    pub rdev: u32,
289    /// Umask.
290    pub umask: u32,
291    /// Padding.
292    pub padding: u32,
293}
294
295/// FUSE_MKDIR request.
296#[derive(Debug, Clone, Copy)]
297#[repr(C)]
298pub struct FuseMkdirIn {
299    /// Directory mode.
300    pub mode: u32,
301    /// Umask.
302    pub umask: u32,
303}
304
305/// FUSE_RENAME request.
306#[derive(Debug, Clone, Copy)]
307#[repr(C)]
308pub struct FuseRenameIn {
309    /// New parent directory inode.
310    pub newdir: u64,
311}
312
313/// FUSE_RENAME2 request.
314#[derive(Debug, Clone, Copy)]
315#[repr(C)]
316pub struct FuseRename2In {
317    /// New parent directory inode.
318    pub newdir: u64,
319    /// Rename flags.
320    pub flags: u32,
321    /// Padding.
322    pub padding: u32,
323}
324
325/// FUSE_LINK request.
326#[derive(Debug, Clone, Copy)]
327#[repr(C)]
328pub struct FuseLinkIn {
329    /// Old inode number.
330    pub oldnodeid: u64,
331}
332
333/// FUSE_OPEN request.
334#[derive(Debug, Clone, Copy)]
335#[repr(C)]
336pub struct FuseOpenIn {
337    /// Open flags.
338    pub flags: u32,
339    /// Unused.
340    pub unused: u32,
341}
342
343/// FUSE_CREATE request.
344#[derive(Debug, Clone, Copy)]
345#[repr(C)]
346pub struct FuseCreateIn {
347    /// Open flags.
348    pub flags: u32,
349    /// File mode.
350    pub mode: u32,
351    /// Umask.
352    pub umask: u32,
353    /// Padding.
354    pub padding: u32,
355}
356
357/// FUSE_READ request.
358#[derive(Debug, Clone, Copy)]
359#[repr(C)]
360pub struct FuseReadIn {
361    /// File handle.
362    pub fh: u64,
363    /// Read offset.
364    pub offset: u64,
365    /// Number of bytes to read.
366    pub size: u32,
367    /// Read flags.
368    pub read_flags: u32,
369    /// Lock owner.
370    pub lock_owner: u64,
371    /// Open flags.
372    pub flags: u32,
373    /// Padding.
374    pub padding: u32,
375}
376
377/// FUSE_WRITE request.
378#[derive(Debug, Clone, Copy)]
379#[repr(C)]
380pub struct FuseWriteIn {
381    /// File handle.
382    pub fh: u64,
383    /// Write offset.
384    pub offset: u64,
385    /// Number of bytes to write.
386    pub size: u32,
387    /// Write flags.
388    pub write_flags: u32,
389    /// Lock owner.
390    pub lock_owner: u64,
391    /// Open flags.
392    pub flags: u32,
393    /// Padding.
394    pub padding: u32,
395}
396
397/// FUSE_RELEASE request.
398#[derive(Debug, Clone, Copy)]
399#[repr(C)]
400pub struct FuseReleaseIn {
401    /// File handle.
402    pub fh: u64,
403    /// Open flags.
404    pub flags: u32,
405    /// Release flags.
406    pub release_flags: u32,
407    /// Lock owner.
408    pub lock_owner: u64,
409}
410
411/// FUSE_FLUSH request.
412#[derive(Debug, Clone, Copy)]
413#[repr(C)]
414pub struct FuseFlushIn {
415    /// File handle.
416    pub fh: u64,
417    /// Unused.
418    pub unused: u32,
419    /// Padding.
420    pub padding: u32,
421    /// Lock owner.
422    pub lock_owner: u64,
423}
424
425/// FUSE_FSYNC request.
426#[derive(Debug, Clone, Copy)]
427#[repr(C)]
428pub struct FuseFsyncIn {
429    /// File handle.
430    pub fh: u64,
431    /// Fsync flags (1 = datasync).
432    pub fsync_flags: u32,
433    /// Padding.
434    pub padding: u32,
435}
436
437/// FUSE_SETXATTR request.
438#[derive(Debug, Clone, Copy)]
439#[repr(C)]
440pub struct FuseSetxattrIn {
441    /// Attribute value size.
442    pub size: u32,
443    /// Setxattr flags.
444    pub flags: u32,
445}
446
447/// FUSE_GETXATTR request.
448#[derive(Debug, Clone, Copy)]
449#[repr(C)]
450pub struct FuseGetxattrIn {
451    /// Maximum size of attribute value.
452    pub size: u32,
453    /// Padding.
454    pub padding: u32,
455}
456
457/// FUSE_ACCESS request.
458#[derive(Debug, Clone, Copy)]
459#[repr(C)]
460pub struct FuseAccessIn {
461    /// Access mode mask.
462    pub mask: u32,
463    /// Padding.
464    pub padding: u32,
465}
466
467/// FUSE_LSEEK request.
468#[derive(Debug, Clone, Copy)]
469#[repr(C)]
470pub struct FuseLseekIn {
471    /// File handle.
472    pub fh: u64,
473    /// Seek offset.
474    pub offset: u64,
475    /// Seek whence (SEEK_SET, SEEK_CUR, SEEK_END, etc.).
476    pub whence: u32,
477    /// Padding.
478    pub padding: u32,
479}
480
481/// FUSE_FALLOCATE request.
482#[derive(Debug, Clone, Copy)]
483#[repr(C)]
484pub struct FuseFallocateIn {
485    /// File handle.
486    pub fh: u64,
487    /// Offset.
488    pub offset: u64,
489    /// Length.
490    pub length: u64,
491    /// Fallocate mode.
492    pub mode: u32,
493    /// Padding.
494    pub padding: u32,
495}
496
497/// FUSE_FORGET request.
498#[derive(Debug, Clone, Copy)]
499#[repr(C)]
500pub struct FuseForgetIn {
501    /// Number of lookups to forget.
502    pub nlookup: u64,
503}
504
505/// Single forget entry for batch forget.
506#[derive(Debug, Clone, Copy)]
507#[repr(C)]
508pub struct FuseForgetOne {
509    /// Inode number.
510    pub nodeid: u64,
511    /// Number of lookups to forget.
512    pub nlookup: u64,
513}
514
515/// FUSE_BATCH_FORGET request.
516#[derive(Debug, Clone, Copy)]
517#[repr(C)]
518pub struct FuseBatchForgetIn {
519    /// Number of entries.
520    pub count: u32,
521    /// Padding.
522    pub dummy: u32,
523}
524
525// ============================================================================
526// Response Structures
527// ============================================================================
528
529/// FUSE_INIT response.
530#[derive(Debug, Clone, Copy)]
531#[repr(C)]
532pub struct FuseInitOut {
533    /// Major version.
534    pub major: u32,
535    /// Minor version.
536    pub minor: u32,
537    /// Maximum readahead size.
538    pub max_readahead: u32,
539    /// Flags.
540    pub flags: u32,
541    /// Maximum background requests.
542    pub max_background: u16,
543    /// Congestion threshold.
544    pub congestion_threshold: u16,
545    /// Maximum write size.
546    pub max_write: u32,
547    /// Time granularity (nanoseconds).
548    pub time_gran: u32,
549    /// Maximum pages for a single request.
550    pub max_pages: u16,
551    /// Padding.
552    pub padding: u16,
553    /// Unused.
554    pub unused: [u32; 8],
555}
556
557impl Default for FuseInitOut {
558    fn default() -> Self {
559        Self {
560            major: FUSE_KERNEL_VERSION,
561            minor: FUSE_KERNEL_MINOR_VERSION,
562            max_readahead: 128 * 1024,
563            flags: FUSE_ASYNC_READ | FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE,
564            max_background: 16,
565            congestion_threshold: 12,
566            max_write: 128 * 1024,
567            time_gran: 1,
568            max_pages: 32,
569            padding: 0,
570            unused: [0; 8],
571        }
572    }
573}
574
575/// Entry response (for lookup, mkdir, mknod, symlink, link, create).
576#[derive(Debug, Clone, Copy, Default)]
577#[repr(C)]
578pub struct FuseEntryOut {
579    /// Inode number.
580    pub nodeid: u64,
581    /// Inode generation.
582    pub generation: u64,
583    /// Cache timeout for entry (seconds).
584    pub entry_valid: u64,
585    /// Cache timeout for attributes (seconds).
586    pub attr_valid: u64,
587    /// Entry timeout nanoseconds.
588    pub entry_valid_nsec: u32,
589    /// Attribute timeout nanoseconds.
590    pub attr_valid_nsec: u32,
591    /// File attributes.
592    pub attr: FuseAttr,
593}
594
595/// Attribute response.
596#[derive(Debug, Clone, Copy, Default)]
597#[repr(C)]
598pub struct FuseAttrOut {
599    /// Cache timeout for attributes (seconds).
600    pub attr_valid: u64,
601    /// Attribute timeout nanoseconds.
602    pub attr_valid_nsec: u32,
603    /// Padding.
604    pub dummy: u32,
605    /// File attributes.
606    pub attr: FuseAttr,
607}
608
609/// Open response.
610#[derive(Debug, Clone, Copy, Default)]
611#[repr(C)]
612pub struct FuseOpenOut {
613    /// File handle.
614    pub fh: u64,
615    /// Open flags.
616    pub open_flags: u32,
617    /// Padding.
618    pub padding: u32,
619}
620
621/// Write response.
622#[derive(Debug, Clone, Copy, Default)]
623#[repr(C)]
624pub struct FuseWriteOut {
625    /// Number of bytes written.
626    pub size: u32,
627    /// Padding.
628    pub padding: u32,
629}
630
631/// Getxattr response.
632#[derive(Debug, Clone, Copy, Default)]
633#[repr(C)]
634pub struct FuseGetxattrOut {
635    /// Attribute value size.
636    pub size: u32,
637    /// Padding.
638    pub padding: u32,
639}
640
641/// Statfs response.
642#[derive(Debug, Clone, Copy, Default)]
643#[repr(C)]
644pub struct FuseStatfsOut {
645    /// Filesystem statistics.
646    pub st: StatFs,
647}
648
649/// Lseek response.
650#[derive(Debug, Clone, Copy, Default)]
651#[repr(C)]
652pub struct FuseLseekOut {
653    /// New offset.
654    pub offset: u64,
655}
656
657/// Directory entry for readdir.
658#[derive(Debug, Clone, Copy)]
659#[repr(C)]
660pub struct FuseDirent {
661    /// Inode number.
662    pub ino: u64,
663    /// Offset to next entry.
664    pub off: u64,
665    /// Name length.
666    pub namelen: u32,
667    /// File type (DT_*).
668    pub typ: u32,
669    // Followed by name[namelen] (not null-terminated, but padded to 8-byte boundary)
670}
671
672impl FuseDirent {
673    /// Returns the total size of this entry including the name.
674    #[must_use]
675    pub const fn size(namelen: usize) -> usize {
676        // Header size + name length, rounded up to 8-byte boundary
677        let base = size_of::<Self>() + namelen;
678        (base + 7) & !7
679    }
680}
681
682// ============================================================================
683// Opcode Conversion
684// ============================================================================
685
686impl FuseOpcode {
687    /// Tries to convert a u32 to a `FuseOpcode`.
688    #[must_use]
689    pub fn from_u32(value: u32) -> Option<Self> {
690        match value {
691            1 => Some(Self::Lookup),
692            2 => Some(Self::Forget),
693            3 => Some(Self::Getattr),
694            4 => Some(Self::Setattr),
695            5 => Some(Self::Readlink),
696            6 => Some(Self::Symlink),
697            8 => Some(Self::Mknod),
698            9 => Some(Self::Mkdir),
699            10 => Some(Self::Unlink),
700            11 => Some(Self::Rmdir),
701            12 => Some(Self::Rename),
702            13 => Some(Self::Link),
703            14 => Some(Self::Open),
704            15 => Some(Self::Read),
705            16 => Some(Self::Write),
706            17 => Some(Self::Statfs),
707            18 => Some(Self::Release),
708            20 => Some(Self::Fsync),
709            21 => Some(Self::Setxattr),
710            22 => Some(Self::Getxattr),
711            23 => Some(Self::Listxattr),
712            24 => Some(Self::Removexattr),
713            25 => Some(Self::Flush),
714            26 => Some(Self::Init),
715            27 => Some(Self::Opendir),
716            28 => Some(Self::Readdir),
717            29 => Some(Self::Releasedir),
718            30 => Some(Self::Fsyncdir),
719            31 => Some(Self::Getlk),
720            32 => Some(Self::Setlk),
721            33 => Some(Self::Setlkw),
722            34 => Some(Self::Access),
723            35 => Some(Self::Create),
724            36 => Some(Self::Interrupt),
725            37 => Some(Self::Bmap),
726            38 => Some(Self::Destroy),
727            39 => Some(Self::Ioctl),
728            40 => Some(Self::Poll),
729            41 => Some(Self::NotifyReply),
730            42 => Some(Self::BatchForget),
731            43 => Some(Self::Fallocate),
732            44 => Some(Self::Readdirplus),
733            45 => Some(Self::Rename2),
734            46 => Some(Self::Lseek),
735            47 => Some(Self::CopyFileRange),
736            48 => Some(Self::SetupMapping),
737            49 => Some(Self::RemoveMapping),
738            _ => None,
739        }
740    }
741}
742
743// ============================================================================
744// Header Size Constants
745// ============================================================================
746
747impl FuseInHeader {
748    /// Size of the input header.
749    pub const SIZE: usize = size_of::<Self>();
750}
751
752impl FuseOutHeader {
753    /// Size of the output header.
754    pub const SIZE: usize = size_of::<Self>();
755
756    /// Creates a success response header.
757    #[must_use]
758    pub const fn success(unique: u64, len: u32) -> Self {
759        Self {
760            len,
761            error: 0,
762            unique,
763        }
764    }
765
766    /// Creates an error response header.
767    #[must_use]
768    pub const fn error(unique: u64, errno: i32) -> Self {
769        Self {
770            len: Self::SIZE as u32,
771            error: -errno,
772            unique,
773        }
774    }
775}