1use 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}