blown_fuse/
proto.rs

1// Based on libfuse/include/fuse_kernel.h
2
3use crate::{util::display_or, FuseError, FuseResult};
4use bitflags::bitflags;
5use bytemuck::{self, try_cast_slice, try_from_bytes, Pod};
6use bytemuck_derive::{Pod, Zeroable};
7use num_enum::TryFromPrimitive;
8use std::{convert::TryFrom, ffi::CStr, fmt};
9
10pub const ROOT_ID: u64 = 1;
11pub const MAJOR_VERSION: u32 = 7;
12pub const TARGET_MINOR_VERSION: u32 = 32;
13pub const REQUIRED_MINOR_VERSION: u32 = 31;
14
15pub const MIN_READ_SIZE: usize = 8192;
16pub const DIRENT_ALIGNMENT_BITS: usize = 3;
17
18pub trait Structured<'o>: Sized {
19    fn split_from(bytes: &'o [u8], header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])>;
20
21    fn toplevel_from(bytes: &'o [u8], header: &InHeader) -> FuseResult<Self> {
22        match Self::split_from(bytes, header, true)? {
23            (ok, end) if end.is_empty() => Ok(ok),
24            _ => Err(FuseError::BadLength),
25        }
26    }
27}
28
29pub enum OpcodeSelect<L, R, const OP: u32> {
30    Match(L),
31    Alt(R),
32}
33
34#[derive(Pod, Zeroable, Copy, Clone)]
35#[repr(C)]
36pub struct InHeader {
37    pub len: u32,
38    pub opcode: u32,
39    pub unique: u64,
40    pub ino: u64,
41    pub uid: u32,
42    pub gid: u32,
43    pub pid: u32,
44    pub padding: u32,
45}
46
47#[derive(Pod, Zeroable, Copy, Clone)]
48#[repr(C)]
49pub struct OutHeader {
50    pub len: u32,
51    pub error: i32,
52    pub unique: u64,
53}
54
55#[derive(TryFromPrimitive, Copy, Clone, Debug)]
56#[repr(u32)]
57pub enum Opcode {
58    Lookup = 1,
59    Forget = 2,
60    Getattr = 3,
61    Setattr = 4,
62    Readlink = 5,
63    Symlink = 6,
64    Mknod = 8,
65    Mkdir = 9,
66    Unlink = 10,
67    Rmdir = 11,
68    Rename = 12,
69    Link = 13,
70    Open = 14,
71    Read = 15,
72    Write = 16,
73    Statfs = 17,
74    Release = 18,
75    Fsync = 20,
76    Setxattr = 21,
77    Getxattr = 22,
78    Listxattr = 23,
79    Removexattr = 24,
80    Flush = 25,
81    Init = 26,
82    Opendir = 27,
83    Readdir = 28,
84    Releasedir = 29,
85    Fsyncdir = 30,
86    Getlk = 31,
87    Setlk = 32,
88    Setlkw = 33,
89    Access = 34,
90    Create = 35,
91    Interrupt = 36,
92    Bmap = 37,
93    Destroy = 38,
94    Ioctl = 39,
95    Poll = 40,
96    NotifyReply = 41,
97    BatchForget = 42,
98    Fallocate = 43,
99    ReaddirPlus = 44,
100    Rename2 = 45,
101    Lseek = 46,
102    CopyFileRange = 47,
103}
104
105#[derive(TryFromPrimitive, Copy, Clone)]
106#[repr(i32)]
107pub enum NotifyCode {
108    Poll = 1,
109    InvalInode = 2,
110    InvalEntry = 3,
111    Store = 4,
112    Retrieve = 5,
113    Delete = 6,
114}
115
116#[derive(Pod, Zeroable, Copy, Clone)]
117#[repr(C)]
118pub struct Attrs {
119    pub ino: u64,
120    pub size: u64,
121    pub blocks: u64,
122    pub atime: u64,
123    pub mtime: u64,
124    pub ctime: u64,
125    pub atimensec: u32,
126    pub mtimensec: u32,
127    pub ctimensec: u32,
128    pub mode: u32,
129    pub nlink: u32,
130    pub uid: u32,
131    pub gid: u32,
132    pub rdev: u32,
133    pub blksize: u32,
134    pub padding: u32,
135}
136
137#[derive(Pod, Zeroable, Copy, Clone)]
138#[repr(C)]
139pub struct FileLock {
140    pub start: u64,
141    pub end: u64,
142    pub lock_type: u32,
143    pub pid: u32,
144}
145
146#[derive(Pod, Zeroable, Copy, Clone)]
147#[repr(C)]
148pub struct EntryOut {
149    pub nodeid: u64,
150    pub generation: u64,
151    pub entry_valid: u64,
152    pub attr_valid: u64,
153    pub entry_valid_nsec: u32,
154    pub attr_valid_nsec: u32,
155    pub attr: Attrs,
156}
157
158#[derive(Pod, Zeroable, Copy, Clone)]
159#[repr(C)]
160pub struct Dirent {
161    pub ino: u64,
162    pub off: u64,
163    pub namelen: u32,
164    pub entry_type: u32,
165}
166
167#[derive(Pod, Zeroable, Copy, Clone)]
168#[repr(C)]
169pub struct DirentPlus {
170    pub entry_out: EntryOut,
171    pub dirent: Dirent,
172}
173
174#[derive(Pod, Zeroable, Copy, Clone)]
175#[repr(C)]
176pub struct ForgetIn {
177    pub nlookup: u64,
178}
179
180#[derive(Pod, Zeroable, Copy, Clone)]
181#[repr(C)]
182pub struct GetattrIn {
183    pub flags: u32,
184    pub dummy: u32,
185    pub fh: u64,
186}
187
188#[derive(Pod, Zeroable, Copy, Clone)]
189#[repr(C)]
190pub struct AttrOut {
191    pub attr_valid: u64,
192    pub attr_valid_nsec: u32,
193    pub dummy: u32,
194    pub attr: Attrs,
195}
196
197#[derive(Pod, Zeroable, Copy, Clone)]
198#[repr(C)]
199pub struct SetattrIn {
200    pub valid: u32,
201    pub padding: u32,
202    pub fh: u64,
203    pub size: u64,
204    pub lock_owner: u64,
205    pub atime: u64,
206    pub mtime: u64,
207    pub ctime: u64,
208    pub atimensec: u32,
209    pub mtimensec: u32,
210    pub ctimensec: u32,
211    pub mode: u32,
212    pub unused: u32,
213    pub uid: u32,
214    pub gid: u32,
215    pub unused2: u32,
216}
217
218#[derive(Pod, Zeroable, Copy, Clone)]
219#[repr(C)]
220pub struct MknodIn {
221    pub mode: u32,
222    pub device: u32,
223    pub umask: u32,
224    pub padding: u32,
225}
226
227#[derive(Pod, Zeroable, Copy, Clone)]
228#[repr(C)]
229pub struct MkdirIn {
230    pub mode: u32,
231    pub umask: u32,
232}
233
234#[derive(Pod, Zeroable, Copy, Clone)]
235#[repr(C)]
236pub struct RenameIn {
237    pub new_dir: u64,
238}
239
240#[derive(Pod, Zeroable, Copy, Clone)]
241#[repr(C)]
242pub struct LinkIn {
243    pub old_ino: u64,
244}
245
246#[derive(Pod, Zeroable, Copy, Clone)]
247#[repr(C)]
248pub struct OpenIn {
249    pub flags: u32,
250    pub unused: u32,
251}
252
253#[derive(Pod, Zeroable, Copy, Clone)]
254#[repr(C)]
255pub struct OpenOut {
256    pub fh: u64,
257    pub open_flags: u32,
258    pub padding: u32,
259}
260
261bitflags! {
262    pub struct OpenOutFlags: u32 {
263        const DIRECT_IO   = 1 << 0;
264        const KEEP_CACHE  = 1 << 1;
265        const NONSEEKABLE = 1 << 2;
266        const CACHE_DIR   = 1 << 3;
267        const STREAM      = 1 << 4;
268    }
269}
270
271#[derive(Pod, Zeroable, Copy, Clone)]
272#[repr(C)]
273pub struct ReadIn {
274    pub fh: u64,
275    pub offset: u64,
276    pub size: u32,
277    pub read_flags: u32,
278    pub lock_owner: u64,
279    pub flags: u32,
280    pub padding: u32,
281}
282
283#[derive(Pod, Zeroable, Copy, Clone)]
284#[repr(C)]
285pub struct WriteIn {
286    pub fh: u64,
287    pub offset: u64,
288    pub size: u32,
289    pub write_flags: u32,
290    pub lock_owner: u64,
291    pub flags: u32,
292    pub padding: u32,
293}
294
295#[derive(Pod, Zeroable, Copy, Clone)]
296#[repr(C)]
297pub struct WriteOut {
298    pub size: u32,
299    pub padding: u32,
300}
301
302#[derive(Pod, Zeroable, Copy, Clone)]
303#[repr(C)]
304pub struct StatfsOut {
305    pub blocks: u64,
306    pub bfree: u64,
307    pub bavail: u64,
308    pub files: u64,
309    pub ffree: u64,
310    pub bsize: u32,
311    pub namelen: u32,
312    pub frsize: u32,
313    pub padding: u32,
314    pub spare: [u32; 6],
315}
316
317#[derive(Pod, Zeroable, Copy, Clone)]
318#[repr(C)]
319pub struct ReleaseIn {
320    pub fh: u64,
321    pub flags: u32,
322    pub release_flags: u32,
323    pub lock_owner: u64,
324}
325
326#[derive(Pod, Zeroable, Copy, Clone)]
327#[repr(C)]
328pub struct FsyncIn {
329    pub fh: u64,
330    pub fsync_flags: u32,
331    pub padding: u32,
332}
333
334bitflags! {
335    pub struct FsyncFlags: u32 {
336        const FDATASYNC = 1 << 0;
337    }
338}
339
340#[derive(Pod, Zeroable, Copy, Clone)]
341#[repr(C)]
342pub struct SetxattrIn {
343    pub size: u32,
344    pub flags: u32,
345}
346
347#[derive(Pod, Zeroable, Copy, Clone)]
348#[repr(C)]
349pub struct GetxattrIn {
350    pub size: u32,
351    pub padding: u32,
352}
353
354#[derive(Pod, Zeroable, Copy, Clone)]
355#[repr(C)]
356pub struct GetxattrOut {
357    pub size: u32,
358    pub padding: u32,
359}
360
361#[derive(Pod, Zeroable, Copy, Clone)]
362#[repr(C)]
363pub struct ListxattrIn {
364    pub getxattr_in: GetxattrIn,
365}
366
367#[derive(Pod, Zeroable, Copy, Clone)]
368#[repr(C)]
369pub struct ListxattrOut {
370    pub getxattr_out: GetxattrOut,
371}
372
373#[derive(Pod, Zeroable, Copy, Clone)]
374#[repr(C)]
375pub struct FlushIn {
376    pub fh: u64,
377    pub unused: u32,
378    pub padding: u32,
379    pub lock_owner: u64,
380}
381
382#[derive(Pod, Zeroable, Copy, Clone)]
383#[repr(C)]
384pub struct InitIn {
385    pub major: u32,
386    pub minor: u32,
387    pub max_readahead: u32,
388    pub flags: u32,
389}
390
391#[derive(Pod, Zeroable, Copy, Clone)]
392#[repr(C)]
393pub struct InitOut {
394    pub major: u32,
395    pub minor: u32,
396    pub max_readahead: u32,
397    pub flags: u32,
398    pub max_background: u16,
399    pub congestion_threshold: u16,
400    pub max_write: u32,
401    pub time_gran: u32,
402    pub max_pages: u16,
403    pub padding: u16,
404    pub unused: [u32; 8],
405}
406
407bitflags! {
408    pub struct InitFlags: u32 {
409        const ASYNC_READ          = 1 << 0;
410        const POSIX_LOCKS         = 1 << 1;
411        const FILE_OPS            = 1 << 2;
412        const ATOMIC_O_TRUNC      = 1 << 3;
413        const EXPORT_SUPPORT      = 1 << 4;
414        const BIG_WRITES          = 1 << 5;
415        const DONT_MASK           = 1 << 6;
416        const SPLICE_WRITE        = 1 << 7;
417        const SPLICE_MOVE         = 1 << 8;
418        const SPLICE_READ         = 1 << 9;
419        const FLOCK_LOCKS         = 1 << 10;
420        const HAS_IOCTL_DIR       = 1 << 11;
421        const AUTO_INVAL_DATA     = 1 << 12;
422        const DO_READDIRPLUS      = 1 << 13;
423        const READDIRPLUS_AUTO    = 1 << 14;
424        const ASYNC_DIO           = 1 << 15;
425        const WRITEBACK_CACHE     = 1 << 16;
426        const NO_OPEN_SUPPOR      = 1 << 17;
427        const PARALLEL_DIROPS     = 1 << 18;
428        const HANDLE_KILLPRIV     = 1 << 19;
429        const POSIX_ACL           = 1 << 20;
430        const ABORT_ERROR         = 1 << 21;
431        const MAX_PAGES           = 1 << 22;
432        const CACHE_SYMLINKS      = 1 << 23;
433        const NO_OPENDIR_SUPPORT  = 1 << 24;
434        const EXPLICIT_INVAL_DATA = 1 << 25;
435    }
436}
437
438#[derive(Pod, Zeroable, Copy, Clone)]
439#[repr(C)]
440pub struct OpendirIn {
441    pub open_in: OpenIn,
442}
443
444#[derive(Pod, Zeroable, Copy, Clone)]
445#[repr(C)]
446pub struct ReaddirIn {
447    pub read_in: ReadIn,
448}
449
450#[derive(Pod, Zeroable, Copy, Clone)]
451#[repr(C)]
452pub struct ReleasedirIn {
453    pub release_in: ReleaseIn,
454}
455
456#[derive(Pod, Zeroable, Copy, Clone)]
457#[repr(C)]
458pub struct FsyncdirIn {
459    pub fsync_in: FsyncIn,
460}
461
462#[derive(Pod, Zeroable, Copy, Clone)]
463#[repr(C)]
464pub struct LkIn {
465    pub fh: u64,
466    pub owner: u64,
467    pub lock: FileLock,
468    pub lock_flags: u32,
469    pub padding: u32,
470}
471
472#[derive(Pod, Zeroable, Copy, Clone)]
473#[repr(C)]
474pub struct GetlkIn {
475    pub lk_in: LkIn,
476}
477
478#[derive(Pod, Zeroable, Copy, Clone)]
479#[repr(C)]
480pub struct SetlkIn {
481    pub lk_in: LkIn,
482}
483
484#[derive(Pod, Zeroable, Copy, Clone)]
485#[repr(C)]
486pub struct SetlkwIn {
487    pub lk_in: LkIn,
488}
489
490#[derive(Pod, Zeroable, Copy, Clone)]
491#[repr(C)]
492pub struct AccessIn {
493    pub mask: u32,
494    pub padding: u32,
495}
496
497#[derive(Pod, Zeroable, Copy, Clone)]
498#[repr(C)]
499pub struct CreateIn {
500    pub flags: u32,
501    pub mode: u32,
502    pub umask: u32,
503    pub padding: u32,
504}
505
506#[derive(Pod, Zeroable, Copy, Clone)]
507#[repr(C)]
508pub struct InterruptIn {
509    pub unique: u64,
510}
511
512#[derive(Pod, Zeroable, Copy, Clone)]
513#[repr(C)]
514pub struct BmapIn {
515    pub block: u64,
516    pub block_size: u32,
517    pub padding: u32,
518}
519
520#[derive(Pod, Zeroable, Copy, Clone)]
521#[repr(C)]
522pub struct BmapOut {
523    pub block: u64,
524}
525
526#[derive(Pod, Zeroable, Copy, Clone)]
527#[repr(C)]
528pub struct IoctlIn {
529    pub fh: u64,
530    pub flags: u32,
531    pub cmd: u32,
532    pub arg: u64,
533    pub in_size: u32,
534    pub out_size: u32,
535}
536
537#[derive(Pod, Zeroable, Copy, Clone)]
538#[repr(C)]
539pub struct PollIn {
540    pub fh: u64,
541    pub kh: u64,
542    pub flags: u32,
543    pub events: u32,
544}
545
546#[derive(Pod, Zeroable, Copy, Clone)]
547#[repr(C)]
548pub struct ForgetOne {
549    pub ino: u64,
550    pub nlookup: u64,
551}
552
553#[derive(Pod, Zeroable, Copy, Clone)]
554#[repr(C)]
555pub struct BatchForgetIn {
556    pub count: u32,
557    pub dummy: u32,
558}
559
560#[derive(Pod, Zeroable, Copy, Clone)]
561#[repr(C)]
562pub struct FallocateIn {
563    pub fh: u64,
564    pub offset: u64,
565    pub length: u64,
566    pub mode: u32,
567    pub padding: u32,
568}
569
570#[derive(Pod, Zeroable, Copy, Clone)]
571#[repr(C)]
572pub struct ReaddirPlusIn {
573    pub read_in: ReadIn,
574}
575
576#[derive(Pod, Zeroable, Copy, Clone)]
577#[repr(C)]
578pub struct Rename2In {
579    pub new_dir: u64,
580    pub flags: u32,
581    pub padding: u32,
582}
583
584#[derive(Pod, Zeroable, Copy, Clone)]
585#[repr(C)]
586pub struct LseekIn {
587    pub fh: u64,
588    pub offset: u64,
589    pub whence: u32,
590    pub padding: u32,
591}
592
593#[derive(Pod, Zeroable, Copy, Clone)]
594#[repr(C)]
595pub struct CopyFileRangeIn {
596    pub fh_in: u64,
597    pub off_in: u64,
598    pub nodeid_out: u64,
599    pub fh_out: u64,
600    pub off_out: u64,
601    pub len: u64,
602    pub flags: u64,
603}
604
605impl<'o> Structured<'o> for () {
606    fn split_from(bytes: &'o [u8], _: &InHeader, _last: bool) -> FuseResult<(Self, &'o [u8])> {
607        Ok(((), bytes))
608    }
609}
610
611impl<'o, T, U> Structured<'o> for (T, U)
612where
613    T: Structured<'o>,
614    U: Structured<'o>,
615{
616    fn split_from(bytes: &'o [u8], header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
617        let (first, bytes) = T::split_from(bytes, header, false)?;
618        let (second, end) = U::split_from(bytes, header, last)?;
619        Ok(((first, second), end))
620    }
621}
622
623impl<'o, T, U, V> Structured<'o> for (T, U, V)
624where
625    T: Structured<'o>,
626    U: Structured<'o>,
627    V: Structured<'o>,
628{
629    fn split_from(bytes: &'o [u8], header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
630        let (first, bytes) = T::split_from(bytes, header, false)?;
631        let ((second, third), end) = <(U, V)>::split_from(bytes, header, last)?;
632        Ok(((first, second, third), end))
633    }
634}
635
636impl<'o, T: Pod> Structured<'o> for &'o T {
637    fn split_from(bytes: &'o [u8], _: &InHeader, _last: bool) -> FuseResult<(Self, &'o [u8])> {
638        let (bytes, next_bytes) = bytes.split_at(bytes.len().min(std::mem::size_of::<T>()));
639        match try_from_bytes(bytes) {
640            Ok(t) => Ok((t, next_bytes)),
641            Err(_) => Err(FuseError::Truncated),
642        }
643    }
644}
645
646impl<'o, T: Pod> Structured<'o> for &'o [T] {
647    fn split_from(bytes: &'o [u8], _header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
648        if !last {
649            unimplemented!();
650        }
651
652        match try_cast_slice(bytes) {
653            Ok(slice) => Ok((slice, &[])),
654            Err(_) => Err(FuseError::Truncated),
655        }
656    }
657}
658
659impl<'o> Structured<'o> for &'o CStr {
660    fn split_from(bytes: &'o [u8], _header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
661        let (cstr, after_cstr): (&[u8], &[u8]) = if last {
662            (bytes, &[])
663        } else {
664            match bytes.iter().position(|byte| *byte == b'\0') {
665                Some(nul) => bytes.split_at(nul + 1),
666                None => return Err(FuseError::Truncated),
667            }
668        };
669
670        let cstr = CStr::from_bytes_with_nul(cstr).map_err(|_| FuseError::BadLength)?;
671        Ok((cstr, after_cstr))
672    }
673}
674
675impl<'o, L, R, const OP: u32> Structured<'o> for OpcodeSelect<L, R, OP>
676where
677    L: Structured<'o>,
678    R: Structured<'o>,
679{
680    fn split_from(bytes: &'o [u8], header: &InHeader, last: bool) -> FuseResult<(Self, &'o [u8])> {
681        if header.opcode == OP {
682            L::split_from(bytes, header, last).map(|(l, end)| (OpcodeSelect::Match(l), end))
683        } else {
684            R::split_from(bytes, header, last).map(|(r, end)| (OpcodeSelect::Alt(r), end))
685        }
686    }
687}
688
689impl InHeader {
690    pub fn from_bytes(bytes: &[u8]) -> FuseResult<(Self, Opcode)> {
691        let header_bytes = &bytes[..bytes.len().min(std::mem::size_of::<InHeader>())];
692        let header = try_from_bytes::<InHeader>(header_bytes).map_err(|_| FuseError::Truncated)?;
693
694        if header.len as usize != bytes.len() {
695            return Err(FuseError::BadLength);
696        }
697
698        let opcode = match Opcode::try_from(header.opcode) {
699            Ok(opcode) => opcode,
700            Err(_) => return Err(FuseError::BadOpcode),
701        };
702
703        Ok((*header, opcode))
704    }
705}
706
707impl fmt::Display for InHeader {
708    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
709        let opcode = display_or(Opcode::try_from(self.opcode).ok(), "bad opcode");
710
711        write!(
712            fmt,
713            "<{}> #{} len={} ino={} uid={} gid={} pid={}",
714            opcode, self.unique, self.len, self.ino, self.uid, self.gid, self.pid
715        )
716    }
717}
718
719impl fmt::Display for Opcode {
720    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
721        write!(fmt, "{:?} ({})", self, *self as u32)
722    }
723}