Skip to main content

arcbox_fs/
dispatcher.rs

1//! FUSE request dispatcher.
2//!
3//! This module handles parsing FUSE requests and dispatching them to the
4//! appropriate filesystem operations. It acts as the bridge between the
5//! raw FUSE protocol and the [`PassthroughFs`] implementation.
6
7// Allow casts and pointer operations for FUSE protocol binary compatibility
8#![allow(
9    clippy::cast_sign_loss,
10    clippy::cast_possible_wrap,
11    clippy::cast_possible_truncation,
12    clippy::ptr_as_ptr,
13    clippy::borrow_as_ptr,
14    clippy::significant_drop_tightening,
15    clippy::needless_pass_by_ref_mut
16)]
17
18use crate::error::{FsError, Result};
19use crate::fuse::{
20    FATTR_ATIME, FATTR_GID, FATTR_MODE, FATTR_MTIME, FATTR_SIZE, FATTR_UID,
21    FUSE_KERNEL_MINOR_VERSION, FUSE_KERNEL_VERSION, FuseAccessIn, FuseAttr, FuseAttrOut,
22    FuseCreateIn, FuseDirent, FuseEntryOut, FuseFallocateIn, FuseFlushIn, FuseForgetIn,
23    FuseFsyncIn, FuseGetxattrIn, FuseGetxattrOut, FuseInHeader, FuseInitIn, FuseInitOut,
24    FuseLinkIn, FuseLseekIn, FuseLseekOut, FuseMkdirIn, FuseMknodIn, FuseOpcode, FuseOpenIn,
25    FuseOpenOut, FuseOutHeader, FuseReadIn, FuseReleaseIn, FuseRenameIn, FuseSetattrIn,
26    FuseSetxattrIn, FuseStatfsOut, FuseWriteIn, FuseWriteOut,
27};
28use crate::passthrough::PassthroughFs;
29
30use std::ffi::OsStr;
31use std::mem::size_of;
32use std::os::unix::ffi::OsStrExt;
33use std::path::Path;
34use std::sync::Arc;
35
36// ============================================================================
37// Dispatcher Configuration
38// ============================================================================
39
40/// Configuration for the FUSE dispatcher.
41#[derive(Debug, Clone)]
42pub struct DispatcherConfig {
43    /// Entry cache timeout in seconds.
44    pub entry_timeout: u64,
45    /// Attribute cache timeout in seconds.
46    pub attr_timeout: u64,
47}
48
49impl Default for DispatcherConfig {
50    fn default() -> Self {
51        Self {
52            entry_timeout: 1,
53            attr_timeout: 1,
54        }
55    }
56}
57
58// ============================================================================
59// Request Context
60// ============================================================================
61
62/// Context for a FUSE request.
63#[derive(Debug, Clone, Copy)]
64pub struct RequestContext {
65    /// Unique request ID.
66    pub unique: u64,
67    /// Node ID (inode).
68    pub nodeid: u64,
69    /// User ID.
70    pub uid: u32,
71    /// Group ID.
72    pub gid: u32,
73    /// Process ID.
74    pub pid: u32,
75}
76
77impl From<&FuseInHeader> for RequestContext {
78    fn from(header: &FuseInHeader) -> Self {
79        Self {
80            unique: header.unique,
81            nodeid: header.nodeid,
82            uid: header.uid,
83            gid: header.gid,
84            pid: header.pid,
85        }
86    }
87}
88
89// ============================================================================
90// Response Builder
91// ============================================================================
92
93/// Helper for building FUSE responses.
94pub struct ResponseBuilder {
95    buffer: Vec<u8>,
96}
97
98impl ResponseBuilder {
99    /// Creates a new response builder.
100    #[must_use]
101    pub fn new() -> Self {
102        Self {
103            buffer: Vec::with_capacity(4096),
104        }
105    }
106
107    /// Writes an error response.
108    pub fn write_error(&mut self, unique: u64, errno: i32) {
109        self.buffer.clear();
110        let header = FuseOutHeader::error(unique, errno);
111        self.write_struct(&header);
112    }
113
114    /// Writes a success response with only a header.
115    pub fn write_empty(&mut self, unique: u64) {
116        self.buffer.clear();
117        let header = FuseOutHeader::success(unique, FuseOutHeader::SIZE as u32);
118        self.write_struct(&header);
119    }
120
121    /// Writes a success response with data.
122    pub fn write_data<T: Copy>(&mut self, unique: u64, data: &T) {
123        self.buffer.clear();
124        let len = (FuseOutHeader::SIZE + size_of::<T>()) as u32;
125        let header = FuseOutHeader::success(unique, len);
126        self.write_struct(&header);
127        self.write_struct(data);
128    }
129
130    /// Writes a success response with raw bytes.
131    pub fn write_bytes(&mut self, unique: u64, data: &[u8]) {
132        self.buffer.clear();
133        let len = (FuseOutHeader::SIZE + data.len()) as u32;
134        let header = FuseOutHeader::success(unique, len);
135        self.write_struct(&header);
136        self.buffer.extend_from_slice(data);
137    }
138
139    /// Returns the built response.
140    #[must_use]
141    pub fn finish(self) -> Vec<u8> {
142        self.buffer
143    }
144
145    /// Returns a reference to the buffer.
146    #[must_use]
147    pub fn as_bytes(&self) -> &[u8] {
148        &self.buffer
149    }
150
151    fn write_struct<T: Copy>(&mut self, value: &T) {
152        let bytes = unsafe {
153            std::slice::from_raw_parts(std::ptr::from_ref::<T>(value) as *const u8, size_of::<T>())
154        };
155        self.buffer.extend_from_slice(bytes);
156    }
157}
158
159impl Default for ResponseBuilder {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165// ============================================================================
166// FUSE Dispatcher
167// ============================================================================
168
169/// FUSE request dispatcher.
170///
171/// Parses incoming FUSE requests and routes them to the appropriate
172/// filesystem operations on [`PassthroughFs`].
173///
174/// # Example
175///
176/// ```no_run
177/// use arcbox_fs::dispatcher::{FuseDispatcher, DispatcherConfig};
178/// use arcbox_fs::passthrough::PassthroughFs;
179/// use std::sync::Arc;
180///
181/// let fs = Arc::new(PassthroughFs::new("/tmp").unwrap());
182/// let dispatcher = FuseDispatcher::new(fs, DispatcherConfig::default());
183///
184/// // Process a FUSE request
185/// let request = vec![0u8; 64]; // Example request bytes
186/// let response = dispatcher.dispatch(&request);
187/// ```
188pub struct FuseDispatcher {
189    /// The underlying filesystem.
190    fs: Arc<PassthroughFs>,
191    /// Dispatcher configuration.
192    config: DispatcherConfig,
193    /// Whether FUSE_INIT has been received.
194    initialized: std::sync::atomic::AtomicBool,
195}
196
197impl FuseDispatcher {
198    /// Creates a new dispatcher.
199    #[must_use]
200    pub fn new(fs: Arc<PassthroughFs>, config: DispatcherConfig) -> Self {
201        Self {
202            fs,
203            config,
204            initialized: std::sync::atomic::AtomicBool::new(false),
205        }
206    }
207
208    /// Dispatches a FUSE request and returns the response.
209    ///
210    /// # Errors
211    ///
212    /// Returns an error if the request cannot be parsed.
213    pub fn dispatch(&self, request: &[u8]) -> Result<Vec<u8>> {
214        // Parse header
215        if request.len() < FuseInHeader::SIZE {
216            return Err(FsError::Fuse("request too small".to_string()));
217        }
218
219        let header = unsafe { &*(request.as_ptr() as *const FuseInHeader) };
220        let body = &request[FuseInHeader::SIZE..];
221
222        // Parse opcode
223        let opcode = FuseOpcode::from_u32(header.opcode)
224            .ok_or_else(|| FsError::Fuse(format!("unknown opcode: {}", header.opcode)))?;
225
226        let ctx = RequestContext::from(header);
227        let mut response = ResponseBuilder::new();
228
229        // Dispatch to handler
230        match opcode {
231            FuseOpcode::Init => self.handle_init(&ctx, body, &mut response),
232            FuseOpcode::Destroy => self.handle_destroy(&ctx, &mut response),
233            FuseOpcode::Lookup => self.handle_lookup(&ctx, body, &mut response),
234            FuseOpcode::Forget => self.handle_forget(&ctx, body, &mut response),
235            FuseOpcode::Getattr => self.handle_getattr(&ctx, body, &mut response),
236            FuseOpcode::Setattr => self.handle_setattr(&ctx, body, &mut response),
237            FuseOpcode::Readlink => self.handle_readlink(&ctx, &mut response),
238            FuseOpcode::Mknod => self.handle_mknod(&ctx, body, &mut response),
239            FuseOpcode::Mkdir => self.handle_mkdir(&ctx, body, &mut response),
240            FuseOpcode::Unlink => self.handle_unlink(&ctx, body, &mut response),
241            FuseOpcode::Rmdir => self.handle_rmdir(&ctx, body, &mut response),
242            FuseOpcode::Symlink => self.handle_symlink(&ctx, body, &mut response),
243            FuseOpcode::Rename => self.handle_rename(&ctx, body, &mut response),
244            FuseOpcode::Link => self.handle_link(&ctx, body, &mut response),
245            FuseOpcode::Open => self.handle_open(&ctx, body, &mut response),
246            FuseOpcode::Read => self.handle_read(&ctx, body, &mut response),
247            FuseOpcode::Write => self.handle_write(&ctx, body, &mut response),
248            FuseOpcode::Statfs => self.handle_statfs(&ctx, &mut response),
249            FuseOpcode::Release => self.handle_release(&ctx, body, &mut response),
250            FuseOpcode::Fsync => self.handle_fsync(&ctx, body, &mut response),
251            FuseOpcode::Flush => self.handle_flush(&ctx, body, &mut response),
252            FuseOpcode::Opendir => self.handle_opendir(&ctx, body, &mut response),
253            FuseOpcode::Readdir => self.handle_readdir(&ctx, body, &mut response),
254            FuseOpcode::Releasedir => self.handle_releasedir(&ctx, body, &mut response),
255            FuseOpcode::Fsyncdir => self.handle_fsyncdir(&ctx, body, &mut response),
256            FuseOpcode::Access => self.handle_access(&ctx, body, &mut response),
257            FuseOpcode::Create => self.handle_create(&ctx, body, &mut response),
258            FuseOpcode::Getxattr => self.handle_getxattr(&ctx, body, &mut response),
259            FuseOpcode::Setxattr => self.handle_setxattr(&ctx, body, &mut response),
260            FuseOpcode::Removexattr => self.handle_removexattr(&ctx, body, &mut response),
261            FuseOpcode::Lseek => self.handle_lseek(&ctx, body, &mut response),
262            FuseOpcode::Fallocate => self.handle_fallocate(&ctx, body, &mut response),
263            _ => {
264                // Unsupported operation
265                response.write_error(ctx.unique, libc::ENOSYS);
266            }
267        }
268
269        Ok(response.finish())
270    }
271
272    // ========================================================================
273    // Init / Destroy
274    // ========================================================================
275
276    fn handle_init(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
277        if body.len() < size_of::<FuseInitIn>() {
278            response.write_error(ctx.unique, libc::EINVAL);
279            return;
280        }
281
282        let init_in = unsafe { &*(body.as_ptr() as *const FuseInitIn) };
283
284        // Check version compatibility
285        if init_in.major < FUSE_KERNEL_VERSION {
286            response.write_error(ctx.unique, libc::EPROTO);
287            return;
288        }
289
290        let init_out = FuseInitOut {
291            major: FUSE_KERNEL_VERSION,
292            minor: FUSE_KERNEL_MINOR_VERSION,
293            max_readahead: init_in.max_readahead,
294            ..FuseInitOut::default()
295        };
296
297        self.initialized
298            .store(true, std::sync::atomic::Ordering::SeqCst);
299        response.write_data(ctx.unique, &init_out);
300    }
301
302    fn handle_destroy(&self, ctx: &RequestContext, response: &mut ResponseBuilder) {
303        self.initialized
304            .store(false, std::sync::atomic::Ordering::SeqCst);
305        response.write_empty(ctx.unique);
306    }
307
308    // ========================================================================
309    // Lookup / Forget
310    // ========================================================================
311
312    fn handle_lookup(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
313        // Body is null-terminated name
314        let name = self.parse_name(body);
315
316        match self.fs.lookup(ctx.nodeid, name) {
317            Ok((inode, attr)) => {
318                let entry = self.make_entry_out(inode, &attr);
319                response.write_data(ctx.unique, &entry);
320            }
321            Err(e) => response.write_error(ctx.unique, e.to_errno()),
322        }
323    }
324
325    fn handle_forget(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
326        if body.len() >= size_of::<FuseForgetIn>() {
327            let forget_in = unsafe { &*(body.as_ptr() as *const FuseForgetIn) };
328            self.fs.forget(ctx.nodeid, forget_in.nlookup);
329        }
330        // FORGET has no response
331        let _ = response;
332    }
333
334    // ========================================================================
335    // Attributes
336    // ========================================================================
337
338    fn handle_getattr(&self, ctx: &RequestContext, _body: &[u8], response: &mut ResponseBuilder) {
339        match self.fs.getattr(ctx.nodeid) {
340            Ok(attr) => {
341                let attr_out = FuseAttrOut {
342                    attr_valid: self.config.attr_timeout,
343                    attr_valid_nsec: 0,
344                    dummy: 0,
345                    attr,
346                };
347                response.write_data(ctx.unique, &attr_out);
348            }
349            Err(e) => response.write_error(ctx.unique, e.to_errno()),
350        }
351    }
352
353    fn handle_setattr(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
354        if body.len() < size_of::<FuseSetattrIn>() {
355            response.write_error(ctx.unique, libc::EINVAL);
356            return;
357        }
358
359        let setattr_in = unsafe { &*(body.as_ptr() as *const FuseSetattrIn) };
360
361        let mode = if setattr_in.valid & FATTR_MODE != 0 {
362            Some(setattr_in.mode)
363        } else {
364            None
365        };
366
367        let uid = if setattr_in.valid & FATTR_UID != 0 {
368            Some(setattr_in.uid)
369        } else {
370            None
371        };
372
373        let gid = if setattr_in.valid & FATTR_GID != 0 {
374            Some(setattr_in.gid)
375        } else {
376            None
377        };
378
379        let size = if setattr_in.valid & FATTR_SIZE != 0 {
380            Some(setattr_in.size)
381        } else {
382            None
383        };
384
385        let atime = if setattr_in.valid & FATTR_ATIME != 0 {
386            Some((setattr_in.atime as i64, setattr_in.atimensec))
387        } else {
388            None
389        };
390
391        let mtime = if setattr_in.valid & FATTR_MTIME != 0 {
392            Some((setattr_in.mtime as i64, setattr_in.mtimensec))
393        } else {
394            None
395        };
396
397        match self
398            .fs
399            .setattr(ctx.nodeid, mode, uid, gid, size, atime, mtime)
400        {
401            Ok(attr) => {
402                let attr_out = FuseAttrOut {
403                    attr_valid: self.config.attr_timeout,
404                    attr_valid_nsec: 0,
405                    dummy: 0,
406                    attr,
407                };
408                response.write_data(ctx.unique, &attr_out);
409            }
410            Err(e) => response.write_error(ctx.unique, e.to_errno()),
411        }
412    }
413
414    fn handle_readlink(&self, ctx: &RequestContext, response: &mut ResponseBuilder) {
415        match self.fs.readlink(ctx.nodeid) {
416            Ok(target) => {
417                response.write_bytes(ctx.unique, target.as_os_str().as_bytes());
418            }
419            Err(e) => response.write_error(ctx.unique, e.to_errno()),
420        }
421    }
422
423    // ========================================================================
424    // File Creation
425    // ========================================================================
426
427    fn handle_mknod(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
428        if body.len() < size_of::<FuseMknodIn>() {
429            response.write_error(ctx.unique, libc::EINVAL);
430            return;
431        }
432
433        let mknod_in = unsafe { &*(body.as_ptr() as *const FuseMknodIn) };
434        let name = self.parse_name(&body[size_of::<FuseMknodIn>()..]);
435
436        match self
437            .fs
438            .mknod(ctx.nodeid, name, mknod_in.mode, u64::from(mknod_in.rdev))
439        {
440            Ok((inode, attr)) => {
441                let entry = self.make_entry_out(inode, &attr);
442                response.write_data(ctx.unique, &entry);
443            }
444            Err(e) => response.write_error(ctx.unique, e.to_errno()),
445        }
446    }
447
448    fn handle_mkdir(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
449        if body.len() < size_of::<FuseMkdirIn>() {
450            response.write_error(ctx.unique, libc::EINVAL);
451            return;
452        }
453
454        let mkdir_in = unsafe { &*(body.as_ptr() as *const FuseMkdirIn) };
455        let name = self.parse_name(&body[size_of::<FuseMkdirIn>()..]);
456
457        match self.fs.mkdir(ctx.nodeid, name, mkdir_in.mode) {
458            Ok((inode, attr)) => {
459                let entry = self.make_entry_out(inode, &attr);
460                response.write_data(ctx.unique, &entry);
461            }
462            Err(e) => response.write_error(ctx.unique, e.to_errno()),
463        }
464    }
465
466    fn handle_symlink(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
467        // Body: name\0linkname\0
468        let parts: Vec<&[u8]> = body.splitn(2, |&b| b == 0).collect();
469        if parts.len() < 2 {
470            response.write_error(ctx.unique, libc::EINVAL);
471            return;
472        }
473
474        let name = OsStr::from_bytes(parts[0]);
475        let link_target = Path::new(OsStr::from_bytes(
476            parts[1].split(|&b| b == 0).next().unwrap_or(&[]),
477        ));
478
479        match self.fs.symlink(ctx.nodeid, name, link_target) {
480            Ok((inode, attr)) => {
481                let entry = self.make_entry_out(inode, &attr);
482                response.write_data(ctx.unique, &entry);
483            }
484            Err(e) => response.write_error(ctx.unique, e.to_errno()),
485        }
486    }
487
488    fn handle_link(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
489        if body.len() < size_of::<FuseLinkIn>() {
490            response.write_error(ctx.unique, libc::EINVAL);
491            return;
492        }
493
494        let link_in = unsafe { &*(body.as_ptr() as *const FuseLinkIn) };
495        let name = self.parse_name(&body[size_of::<FuseLinkIn>()..]);
496
497        match self.fs.link(link_in.oldnodeid, ctx.nodeid, name) {
498            Ok((inode, attr)) => {
499                let entry = self.make_entry_out(inode, &attr);
500                response.write_data(ctx.unique, &entry);
501            }
502            Err(e) => response.write_error(ctx.unique, e.to_errno()),
503        }
504    }
505
506    fn handle_create(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
507        if body.len() < size_of::<FuseCreateIn>() {
508            response.write_error(ctx.unique, libc::EINVAL);
509            return;
510        }
511
512        let create_in = unsafe { &*(body.as_ptr() as *const FuseCreateIn) };
513        let name = self.parse_name(&body[size_of::<FuseCreateIn>()..]);
514
515        match self
516            .fs
517            .create(ctx.nodeid, name, create_in.mode, create_in.flags)
518        {
519            Ok((inode, attr, handle)) => {
520                let entry = self.make_entry_out(inode, &attr);
521                let open_out = FuseOpenOut {
522                    fh: handle,
523                    open_flags: 0,
524                    padding: 0,
525                };
526
527                // Write entry followed by open response
528                response.buffer.clear();
529                let len = (FuseOutHeader::SIZE
530                    + size_of::<FuseEntryOut>()
531                    + size_of::<FuseOpenOut>()) as u32;
532                let header = FuseOutHeader::success(ctx.unique, len);
533                response.write_struct(&header);
534                response.write_struct(&entry);
535                response.write_struct(&open_out);
536            }
537            Err(e) => response.write_error(ctx.unique, e.to_errno()),
538        }
539    }
540
541    // ========================================================================
542    // File Deletion
543    // ========================================================================
544
545    fn handle_unlink(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
546        let name = self.parse_name(body);
547
548        match self.fs.unlink(ctx.nodeid, name) {
549            Ok(()) => response.write_empty(ctx.unique),
550            Err(e) => response.write_error(ctx.unique, e.to_errno()),
551        }
552    }
553
554    fn handle_rmdir(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
555        let name = self.parse_name(body);
556
557        match self.fs.rmdir(ctx.nodeid, name) {
558            Ok(()) => response.write_empty(ctx.unique),
559            Err(e) => response.write_error(ctx.unique, e.to_errno()),
560        }
561    }
562
563    fn handle_rename(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
564        if body.len() < size_of::<FuseRenameIn>() {
565            response.write_error(ctx.unique, libc::EINVAL);
566            return;
567        }
568
569        let rename_in = unsafe { &*(body.as_ptr() as *const FuseRenameIn) };
570        let names = &body[size_of::<FuseRenameIn>()..];
571
572        // Parse old_name\0new_name\0
573        let parts: Vec<&[u8]> = names.splitn(2, |&b| b == 0).collect();
574        if parts.len() < 2 {
575            response.write_error(ctx.unique, libc::EINVAL);
576            return;
577        }
578
579        let old_name = OsStr::from_bytes(parts[0]);
580        let new_name = OsStr::from_bytes(parts[1].split(|&b| b == 0).next().unwrap_or(&[]));
581
582        match self
583            .fs
584            .rename(ctx.nodeid, old_name, rename_in.newdir, new_name, 0)
585        {
586            Ok(()) => response.write_empty(ctx.unique),
587            Err(e) => response.write_error(ctx.unique, e.to_errno()),
588        }
589    }
590
591    // ========================================================================
592    // File Operations
593    // ========================================================================
594
595    fn handle_open(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
596        if body.len() < size_of::<FuseOpenIn>() {
597            response.write_error(ctx.unique, libc::EINVAL);
598            return;
599        }
600
601        let open_in = unsafe { &*(body.as_ptr() as *const FuseOpenIn) };
602
603        match self.fs.open(ctx.nodeid, open_in.flags) {
604            Ok(handle) => {
605                let open_out = FuseOpenOut {
606                    fh: handle,
607                    open_flags: 0,
608                    padding: 0,
609                };
610                response.write_data(ctx.unique, &open_out);
611            }
612            Err(e) => response.write_error(ctx.unique, e.to_errno()),
613        }
614    }
615
616    fn handle_read(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
617        if body.len() < size_of::<FuseReadIn>() {
618            response.write_error(ctx.unique, libc::EINVAL);
619            return;
620        }
621
622        let read_in = unsafe { &*(body.as_ptr() as *const FuseReadIn) };
623
624        match self.fs.read(read_in.fh, read_in.offset, read_in.size) {
625            Ok(data) => response.write_bytes(ctx.unique, &data),
626            Err(e) => response.write_error(ctx.unique, e.to_errno()),
627        }
628    }
629
630    fn handle_write(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
631        if body.len() < size_of::<FuseWriteIn>() {
632            response.write_error(ctx.unique, libc::EINVAL);
633            return;
634        }
635
636        let write_in = unsafe { &*(body.as_ptr() as *const FuseWriteIn) };
637        let data = &body[size_of::<FuseWriteIn>()..];
638
639        match self
640            .fs
641            .write(write_in.fh, write_in.offset, data, write_in.write_flags)
642        {
643            Ok(written) => {
644                let write_out = FuseWriteOut {
645                    size: written,
646                    padding: 0,
647                };
648                response.write_data(ctx.unique, &write_out);
649            }
650            Err(e) => response.write_error(ctx.unique, e.to_errno()),
651        }
652    }
653
654    fn handle_release(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
655        if body.len() < size_of::<FuseReleaseIn>() {
656            response.write_error(ctx.unique, libc::EINVAL);
657            return;
658        }
659
660        let release_in = unsafe { &*(body.as_ptr() as *const FuseReleaseIn) };
661
662        match self.fs.release(release_in.fh) {
663            Ok(()) => response.write_empty(ctx.unique),
664            Err(e) => response.write_error(ctx.unique, e.to_errno()),
665        }
666    }
667
668    fn handle_flush(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
669        if body.len() < size_of::<FuseFlushIn>() {
670            response.write_error(ctx.unique, libc::EINVAL);
671            return;
672        }
673
674        let flush_in = unsafe { &*(body.as_ptr() as *const FuseFlushIn) };
675
676        match self.fs.flush(flush_in.fh) {
677            Ok(()) => response.write_empty(ctx.unique),
678            Err(e) => response.write_error(ctx.unique, e.to_errno()),
679        }
680    }
681
682    fn handle_fsync(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
683        if body.len() < size_of::<FuseFsyncIn>() {
684            response.write_error(ctx.unique, libc::EINVAL);
685            return;
686        }
687
688        let fsync_in = unsafe { &*(body.as_ptr() as *const FuseFsyncIn) };
689        let datasync = fsync_in.fsync_flags & 1 != 0;
690
691        match self.fs.fsync(fsync_in.fh, datasync) {
692            Ok(()) => response.write_empty(ctx.unique),
693            Err(e) => response.write_error(ctx.unique, e.to_errno()),
694        }
695    }
696
697    fn handle_lseek(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
698        if body.len() < size_of::<FuseLseekIn>() {
699            response.write_error(ctx.unique, libc::EINVAL);
700            return;
701        }
702
703        let lseek_in = unsafe { &*(body.as_ptr() as *const FuseLseekIn) };
704
705        match self
706            .fs
707            .lseek(lseek_in.fh, lseek_in.offset as i64, lseek_in.whence)
708        {
709            Ok(offset) => {
710                let lseek_out = FuseLseekOut { offset };
711                response.write_data(ctx.unique, &lseek_out);
712            }
713            Err(e) => response.write_error(ctx.unique, e.to_errno()),
714        }
715    }
716
717    fn handle_fallocate(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
718        if body.len() < size_of::<FuseFallocateIn>() {
719            response.write_error(ctx.unique, libc::EINVAL);
720            return;
721        }
722
723        let fallocate_in = unsafe { &*(body.as_ptr() as *const FuseFallocateIn) };
724
725        match self.fs.fallocate(
726            fallocate_in.fh,
727            fallocate_in.mode,
728            fallocate_in.offset,
729            fallocate_in.length,
730        ) {
731            Ok(()) => response.write_empty(ctx.unique),
732            Err(e) => response.write_error(ctx.unique, e.to_errno()),
733        }
734    }
735
736    // ========================================================================
737    // Directory Operations
738    // ========================================================================
739
740    fn handle_opendir(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
741        if body.len() < size_of::<FuseOpenIn>() {
742            response.write_error(ctx.unique, libc::EINVAL);
743            return;
744        }
745
746        match self.fs.opendir(ctx.nodeid) {
747            Ok(handle) => {
748                let open_out = FuseOpenOut {
749                    fh: handle,
750                    open_flags: 0,
751                    padding: 0,
752                };
753                response.write_data(ctx.unique, &open_out);
754            }
755            Err(e) => response.write_error(ctx.unique, e.to_errno()),
756        }
757    }
758
759    fn handle_readdir(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
760        if body.len() < size_of::<FuseReadIn>() {
761            response.write_error(ctx.unique, libc::EINVAL);
762            return;
763        }
764
765        let read_in = unsafe { &*(body.as_ptr() as *const FuseReadIn) };
766
767        match self.fs.readdir(read_in.fh, read_in.offset) {
768            Ok(entries) => {
769                let mut dirent_buf = Vec::new();
770                let mut offset = read_in.offset + 1;
771
772                for entry in entries {
773                    let name_bytes = entry.name.as_bytes();
774                    let entry_size = FuseDirent::size(name_bytes.len());
775
776                    // Check if we have room in the buffer
777                    if dirent_buf.len() + entry_size > read_in.size as usize {
778                        break;
779                    }
780
781                    let dirent = FuseDirent {
782                        ino: entry.ino,
783                        off: offset,
784                        namelen: name_bytes.len() as u32,
785                        typ: entry.file_type.to_dirent_type(),
786                    };
787
788                    // Write dirent header
789                    let dirent_bytes = unsafe {
790                        std::slice::from_raw_parts(
791                            std::ptr::from_ref::<FuseDirent>(&dirent) as *const u8,
792                            size_of::<FuseDirent>(),
793                        )
794                    };
795                    dirent_buf.extend_from_slice(dirent_bytes);
796
797                    // Write name
798                    dirent_buf.extend_from_slice(name_bytes);
799
800                    // Pad to 8-byte boundary
801                    let padding = entry_size - size_of::<FuseDirent>() - name_bytes.len();
802                    dirent_buf.extend(std::iter::repeat_n(0u8, padding));
803
804                    offset += 1;
805                }
806
807                response.write_bytes(ctx.unique, &dirent_buf);
808            }
809            Err(e) => response.write_error(ctx.unique, e.to_errno()),
810        }
811    }
812
813    fn handle_releasedir(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
814        if body.len() < size_of::<FuseReleaseIn>() {
815            response.write_error(ctx.unique, libc::EINVAL);
816            return;
817        }
818
819        let release_in = unsafe { &*(body.as_ptr() as *const FuseReleaseIn) };
820
821        match self.fs.releasedir(release_in.fh) {
822            Ok(()) => response.write_empty(ctx.unique),
823            Err(e) => response.write_error(ctx.unique, e.to_errno()),
824        }
825    }
826
827    fn handle_fsyncdir(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
828        if body.len() < size_of::<FuseFsyncIn>() {
829            response.write_error(ctx.unique, libc::EINVAL);
830            return;
831        }
832
833        let fsync_in = unsafe { &*(body.as_ptr() as *const FuseFsyncIn) };
834        let datasync = fsync_in.fsync_flags & 1 != 0;
835
836        match self.fs.fsyncdir(fsync_in.fh, datasync) {
837            Ok(()) => response.write_empty(ctx.unique),
838            Err(e) => response.write_error(ctx.unique, e.to_errno()),
839        }
840    }
841
842    // ========================================================================
843    // Extended Attributes
844    // ========================================================================
845
846    fn handle_getxattr(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
847        if body.len() < size_of::<FuseGetxattrIn>() {
848            response.write_error(ctx.unique, libc::EINVAL);
849            return;
850        }
851
852        let getxattr_in = unsafe { &*(body.as_ptr() as *const FuseGetxattrIn) };
853        let name = self.parse_name(&body[size_of::<FuseGetxattrIn>()..]);
854
855        match self.fs.getxattr(ctx.nodeid, name, getxattr_in.size) {
856            Ok(value) => {
857                if getxattr_in.size == 0 {
858                    // Return size only
859                    let out = FuseGetxattrOut {
860                        size: value.len() as u32,
861                        padding: 0,
862                    };
863                    response.write_data(ctx.unique, &out);
864                } else {
865                    response.write_bytes(ctx.unique, &value);
866                }
867            }
868            Err(e) => response.write_error(ctx.unique, e.to_errno()),
869        }
870    }
871
872    fn handle_setxattr(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
873        if body.len() < size_of::<FuseSetxattrIn>() {
874            response.write_error(ctx.unique, libc::EINVAL);
875            return;
876        }
877
878        let setxattr_in = unsafe { &*(body.as_ptr() as *const FuseSetxattrIn) };
879        let rest = &body[size_of::<FuseSetxattrIn>()..];
880
881        // Parse name\0value
882        if let Some(null_pos) = rest.iter().position(|&b| b == 0) {
883            let name = OsStr::from_bytes(&rest[..null_pos]);
884            let value = &rest[null_pos + 1..][..setxattr_in.size as usize];
885
886            match self.fs.setxattr(ctx.nodeid, name, value, setxattr_in.flags) {
887                Ok(()) => response.write_empty(ctx.unique),
888                Err(e) => response.write_error(ctx.unique, e.to_errno()),
889            }
890        } else {
891            response.write_error(ctx.unique, libc::EINVAL);
892        }
893    }
894
895    fn handle_removexattr(
896        &self,
897        ctx: &RequestContext,
898        body: &[u8],
899        response: &mut ResponseBuilder,
900    ) {
901        let name = self.parse_name(body);
902
903        match self.fs.removexattr(ctx.nodeid, name) {
904            Ok(()) => response.write_empty(ctx.unique),
905            Err(e) => response.write_error(ctx.unique, e.to_errno()),
906        }
907    }
908
909    // ========================================================================
910    // Other Operations
911    // ========================================================================
912
913    fn handle_statfs(&self, ctx: &RequestContext, response: &mut ResponseBuilder) {
914        match self.fs.statfs() {
915            Ok(st) => {
916                let statfs_out = FuseStatfsOut { st };
917                response.write_data(ctx.unique, &statfs_out);
918            }
919            Err(e) => response.write_error(ctx.unique, e.to_errno()),
920        }
921    }
922
923    fn handle_access(&self, ctx: &RequestContext, body: &[u8], response: &mut ResponseBuilder) {
924        if body.len() < size_of::<FuseAccessIn>() {
925            response.write_error(ctx.unique, libc::EINVAL);
926            return;
927        }
928
929        let access_in = unsafe { &*(body.as_ptr() as *const FuseAccessIn) };
930
931        match self.fs.access(ctx.nodeid, access_in.mask) {
932            Ok(()) => response.write_empty(ctx.unique),
933            Err(e) => response.write_error(ctx.unique, e.to_errno()),
934        }
935    }
936
937    // ========================================================================
938    // Helper Methods
939    // ========================================================================
940
941    fn parse_name<'a>(&self, body: &'a [u8]) -> &'a OsStr {
942        let name_end = body.iter().position(|&b| b == 0).unwrap_or(body.len());
943        OsStr::from_bytes(&body[..name_end])
944    }
945
946    fn make_entry_out(&self, inode: u64, attr: &FuseAttr) -> FuseEntryOut {
947        FuseEntryOut {
948            nodeid: inode,
949            generation: 0,
950            entry_valid: self.config.entry_timeout,
951            attr_valid: self.config.attr_timeout,
952            entry_valid_nsec: 0,
953            attr_valid_nsec: 0,
954            attr: *attr,
955        }
956    }
957}
958
959impl std::fmt::Debug for FuseDispatcher {
960    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
961        f.debug_struct("FuseDispatcher")
962            .field("config", &self.config)
963            .field(
964                "initialized",
965                &self.initialized.load(std::sync::atomic::Ordering::Relaxed),
966            )
967            .finish()
968    }
969}
970
971// ============================================================================
972// Tests
973// ============================================================================
974
975#[cfg(test)]
976mod tests {
977    use super::*;
978    use tempfile::TempDir;
979
980    fn setup_dispatcher() -> (TempDir, FuseDispatcher) {
981        let temp = TempDir::new().expect("failed to create temp dir");
982        let fs = Arc::new(PassthroughFs::new(temp.path()).expect("failed to create fs"));
983        let dispatcher = FuseDispatcher::new(fs, DispatcherConfig::default());
984        (temp, dispatcher)
985    }
986
987    fn make_header(opcode: FuseOpcode, nodeid: u64, body_len: usize) -> Vec<u8> {
988        let header = FuseInHeader {
989            len: (FuseInHeader::SIZE + body_len) as u32,
990            opcode: opcode as u32,
991            unique: 1,
992            nodeid,
993            uid: 0,
994            gid: 0,
995            pid: 0,
996            padding: 0,
997        };
998
999        let header_bytes = unsafe {
1000            std::slice::from_raw_parts(
1001                &header as *const FuseInHeader as *const u8,
1002                FuseInHeader::SIZE,
1003            )
1004        };
1005        header_bytes.to_vec()
1006    }
1007
1008    fn parse_response_header(response: &[u8]) -> FuseOutHeader {
1009        assert!(response.len() >= FuseOutHeader::SIZE);
1010        unsafe { *(response.as_ptr() as *const FuseOutHeader) }
1011    }
1012
1013    #[test]
1014    fn test_init() {
1015        let (_temp, dispatcher) = setup_dispatcher();
1016
1017        let init_in = FuseInitIn {
1018            major: FUSE_KERNEL_VERSION,
1019            minor: FUSE_KERNEL_MINOR_VERSION,
1020            max_readahead: 128 * 1024,
1021            flags: 0,
1022        };
1023
1024        let mut request = make_header(FuseOpcode::Init, 0, size_of::<FuseInitIn>());
1025        let init_bytes = unsafe {
1026            std::slice::from_raw_parts(
1027                &init_in as *const FuseInitIn as *const u8,
1028                size_of::<FuseInitIn>(),
1029            )
1030        };
1031        request.extend_from_slice(init_bytes);
1032
1033        let response = dispatcher.dispatch(&request).unwrap();
1034        let header = parse_response_header(&response);
1035
1036        assert_eq!(header.error, 0);
1037        assert!(
1038            dispatcher
1039                .initialized
1040                .load(std::sync::atomic::Ordering::Relaxed)
1041        );
1042    }
1043
1044    #[test]
1045    fn test_getattr_root() {
1046        let (_temp, dispatcher) = setup_dispatcher();
1047
1048        let request = make_header(FuseOpcode::Getattr, 1, 0);
1049        let response = dispatcher.dispatch(&request).unwrap();
1050        let header = parse_response_header(&response);
1051
1052        assert_eq!(header.error, 0);
1053        assert!(response.len() > FuseOutHeader::SIZE);
1054    }
1055
1056    #[test]
1057    fn test_lookup_nonexistent() {
1058        let (_temp, dispatcher) = setup_dispatcher();
1059
1060        let name = b"nonexistent\0";
1061        let mut request = make_header(FuseOpcode::Lookup, 1, name.len());
1062        request.extend_from_slice(name);
1063
1064        let response = dispatcher.dispatch(&request).unwrap();
1065        let header = parse_response_header(&response);
1066
1067        assert_eq!(header.error, -libc::ENOENT);
1068    }
1069
1070    #[test]
1071    fn test_lookup_existing() {
1072        let (temp, dispatcher) = setup_dispatcher();
1073
1074        // Create a file
1075        std::fs::write(temp.path().join("test.txt"), "hello").unwrap();
1076
1077        let name = b"test.txt\0";
1078        let mut request = make_header(FuseOpcode::Lookup, 1, name.len());
1079        request.extend_from_slice(name);
1080
1081        let response = dispatcher.dispatch(&request).unwrap();
1082        let header = parse_response_header(&response);
1083
1084        assert_eq!(header.error, 0);
1085    }
1086
1087    #[test]
1088    fn test_mkdir_and_rmdir() {
1089        let (_temp, dispatcher) = setup_dispatcher();
1090
1091        // Mkdir
1092        let mkdir_in = FuseMkdirIn {
1093            mode: 0o755,
1094            umask: 0,
1095        };
1096        let name = b"testdir\0";
1097
1098        let mut request = make_header(FuseOpcode::Mkdir, 1, size_of::<FuseMkdirIn>() + name.len());
1099        let mkdir_bytes = unsafe {
1100            std::slice::from_raw_parts(
1101                &mkdir_in as *const FuseMkdirIn as *const u8,
1102                size_of::<FuseMkdirIn>(),
1103            )
1104        };
1105        request.extend_from_slice(mkdir_bytes);
1106        request.extend_from_slice(name);
1107
1108        let response = dispatcher.dispatch(&request).unwrap();
1109        let header = parse_response_header(&response);
1110        assert_eq!(header.error, 0);
1111
1112        // Rmdir
1113        let mut request = make_header(FuseOpcode::Rmdir, 1, name.len());
1114        request.extend_from_slice(name);
1115
1116        let response = dispatcher.dispatch(&request).unwrap();
1117        let header = parse_response_header(&response);
1118        assert_eq!(header.error, 0);
1119    }
1120
1121    #[test]
1122    fn test_open_read_write_release() {
1123        let (temp, dispatcher) = setup_dispatcher();
1124
1125        // Create file
1126        std::fs::write(temp.path().join("test.txt"), "initial").unwrap();
1127
1128        // Lookup to get inode
1129        let name = b"test.txt\0";
1130        let mut request = make_header(FuseOpcode::Lookup, 1, name.len());
1131        request.extend_from_slice(name);
1132        let response = dispatcher.dispatch(&request).unwrap();
1133        let header = parse_response_header(&response);
1134        assert_eq!(header.error, 0);
1135
1136        // Extract inode from entry response
1137        let entry = unsafe {
1138            &*((response.as_ptr() as *const u8).add(FuseOutHeader::SIZE) as *const FuseEntryOut)
1139        };
1140        let inode = entry.nodeid;
1141
1142        // Open
1143        let open_in = FuseOpenIn {
1144            flags: libc::O_RDWR as u32,
1145            unused: 0,
1146        };
1147        let mut request = make_header(FuseOpcode::Open, inode, size_of::<FuseOpenIn>());
1148        let open_bytes = unsafe {
1149            std::slice::from_raw_parts(
1150                &open_in as *const FuseOpenIn as *const u8,
1151                size_of::<FuseOpenIn>(),
1152            )
1153        };
1154        request.extend_from_slice(open_bytes);
1155
1156        let response = dispatcher.dispatch(&request).unwrap();
1157        let header = parse_response_header(&response);
1158        assert_eq!(header.error, 0);
1159
1160        // Extract file handle
1161        let open_out = unsafe {
1162            &*((response.as_ptr() as *const u8).add(FuseOutHeader::SIZE) as *const FuseOpenOut)
1163        };
1164        let fh = open_out.fh;
1165
1166        // Read
1167        let read_in = FuseReadIn {
1168            fh,
1169            offset: 0,
1170            size: 100,
1171            read_flags: 0,
1172            lock_owner: 0,
1173            flags: 0,
1174            padding: 0,
1175        };
1176        let mut request = make_header(FuseOpcode::Read, inode, size_of::<FuseReadIn>());
1177        let read_bytes = unsafe {
1178            std::slice::from_raw_parts(
1179                &read_in as *const FuseReadIn as *const u8,
1180                size_of::<FuseReadIn>(),
1181            )
1182        };
1183        request.extend_from_slice(read_bytes);
1184
1185        let response = dispatcher.dispatch(&request).unwrap();
1186        let header = parse_response_header(&response);
1187        assert_eq!(header.error, 0);
1188
1189        let data = &response[FuseOutHeader::SIZE..];
1190        assert_eq!(data, b"initial");
1191
1192        // Release
1193        let release_in = FuseReleaseIn {
1194            fh,
1195            flags: 0,
1196            release_flags: 0,
1197            lock_owner: 0,
1198        };
1199        let mut request = make_header(FuseOpcode::Release, inode, size_of::<FuseReleaseIn>());
1200        let release_bytes = unsafe {
1201            std::slice::from_raw_parts(
1202                &release_in as *const FuseReleaseIn as *const u8,
1203                size_of::<FuseReleaseIn>(),
1204            )
1205        };
1206        request.extend_from_slice(release_bytes);
1207
1208        let response = dispatcher.dispatch(&request).unwrap();
1209        let header = parse_response_header(&response);
1210        assert_eq!(header.error, 0);
1211    }
1212
1213    #[test]
1214    fn test_statfs() {
1215        let (_temp, dispatcher) = setup_dispatcher();
1216
1217        let request = make_header(FuseOpcode::Statfs, 1, 0);
1218        let response = dispatcher.dispatch(&request).unwrap();
1219        let header = parse_response_header(&response);
1220
1221        assert_eq!(header.error, 0);
1222        assert!(response.len() >= FuseOutHeader::SIZE + size_of::<FuseStatfsOut>());
1223    }
1224
1225    #[test]
1226    fn test_unknown_opcode() {
1227        let (_temp, dispatcher) = setup_dispatcher();
1228
1229        let header = FuseInHeader {
1230            len: FuseInHeader::SIZE as u32,
1231            opcode: 9999, // Unknown opcode
1232            unique: 1,
1233            nodeid: 1,
1234            uid: 0,
1235            gid: 0,
1236            pid: 0,
1237            padding: 0,
1238        };
1239
1240        let request = unsafe {
1241            std::slice::from_raw_parts(
1242                &header as *const FuseInHeader as *const u8,
1243                FuseInHeader::SIZE,
1244            )
1245        };
1246
1247        let result = dispatcher.dispatch(request);
1248        assert!(result.is_err());
1249    }
1250
1251    #[test]
1252    fn test_unsupported_opcode() {
1253        let (_temp, dispatcher) = setup_dispatcher();
1254
1255        // IOCTL is unsupported
1256        let request = make_header(FuseOpcode::Ioctl, 1, 0);
1257        let response = dispatcher.dispatch(&request).unwrap();
1258        let header = parse_response_header(&response);
1259
1260        assert_eq!(header.error, -libc::ENOSYS);
1261    }
1262
1263    #[test]
1264    fn test_opendir_readdir_releasedir() {
1265        let (temp, dispatcher) = setup_dispatcher();
1266
1267        // Create some files
1268        std::fs::write(temp.path().join("file1.txt"), "").unwrap();
1269        std::fs::write(temp.path().join("file2.txt"), "").unwrap();
1270
1271        // Opendir
1272        let open_in = FuseOpenIn {
1273            flags: 0,
1274            unused: 0,
1275        };
1276        let mut request = make_header(FuseOpcode::Opendir, 1, size_of::<FuseOpenIn>());
1277        let open_bytes = unsafe {
1278            std::slice::from_raw_parts(
1279                &open_in as *const FuseOpenIn as *const u8,
1280                size_of::<FuseOpenIn>(),
1281            )
1282        };
1283        request.extend_from_slice(open_bytes);
1284
1285        let response = dispatcher.dispatch(&request).unwrap();
1286        let header = parse_response_header(&response);
1287        assert_eq!(header.error, 0);
1288
1289        let open_out = unsafe {
1290            &*((response.as_ptr() as *const u8).add(FuseOutHeader::SIZE) as *const FuseOpenOut)
1291        };
1292        let fh = open_out.fh;
1293
1294        // Readdir
1295        let read_in = FuseReadIn {
1296            fh,
1297            offset: 0,
1298            size: 4096,
1299            read_flags: 0,
1300            lock_owner: 0,
1301            flags: 0,
1302            padding: 0,
1303        };
1304        let mut request = make_header(FuseOpcode::Readdir, 1, size_of::<FuseReadIn>());
1305        let read_bytes = unsafe {
1306            std::slice::from_raw_parts(
1307                &read_in as *const FuseReadIn as *const u8,
1308                size_of::<FuseReadIn>(),
1309            )
1310        };
1311        request.extend_from_slice(read_bytes);
1312
1313        let response = dispatcher.dispatch(&request).unwrap();
1314        let header = parse_response_header(&response);
1315        assert_eq!(header.error, 0);
1316
1317        // Releasedir
1318        let release_in = FuseReleaseIn {
1319            fh,
1320            flags: 0,
1321            release_flags: 0,
1322            lock_owner: 0,
1323        };
1324        let mut request = make_header(FuseOpcode::Releasedir, 1, size_of::<FuseReleaseIn>());
1325        let release_bytes = unsafe {
1326            std::slice::from_raw_parts(
1327                &release_in as *const FuseReleaseIn as *const u8,
1328                size_of::<FuseReleaseIn>(),
1329            )
1330        };
1331        request.extend_from_slice(release_bytes);
1332
1333        let response = dispatcher.dispatch(&request).unwrap();
1334        let header = parse_response_header(&response);
1335        assert_eq!(header.error, 0);
1336    }
1337
1338    #[test]
1339    fn test_response_builder() {
1340        let mut builder = ResponseBuilder::new();
1341
1342        // Test error response
1343        builder.write_error(123, libc::ENOENT);
1344        let response = builder.as_bytes();
1345        let header = parse_response_header(response);
1346        assert_eq!(header.unique, 123);
1347        assert_eq!(header.error, -libc::ENOENT);
1348        assert_eq!(header.len as usize, FuseOutHeader::SIZE);
1349
1350        // Test empty response
1351        builder.write_empty(456);
1352        let response = builder.as_bytes();
1353        let header = parse_response_header(response);
1354        assert_eq!(header.unique, 456);
1355        assert_eq!(header.error, 0);
1356        assert_eq!(header.len as usize, FuseOutHeader::SIZE);
1357
1358        // Test bytes response
1359        builder.write_bytes(789, b"hello");
1360        let response = builder.as_bytes();
1361        let header = parse_response_header(response);
1362        assert_eq!(header.unique, 789);
1363        assert_eq!(header.error, 0);
1364        assert_eq!(header.len as usize, FuseOutHeader::SIZE + 5);
1365        assert_eq!(&response[FuseOutHeader::SIZE..], b"hello");
1366    }
1367}