Skip to main content

fuse/
request.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[macro_use]
5mod macros;
6
7use crate::protocol::*;
8use std::io;
9use zerocopy::FromBytes;
10use zerocopy::FromZeros;
11use zerocopy::Immutable;
12use zerocopy::IntoBytes;
13use zerocopy::KnownLayout;
14
15// Define an enum for all operations and their arguments.
16fuse_operations! {
17    FUSE_LOOKUP Lookup name:name;
18    FUSE_FORGET Forget arg:fuse_forget_in;
19    FUSE_GETATTR GetAttr arg:fuse_getattr_in;
20    FUSE_SETATTR SetAttr arg:fuse_setattr_in;
21    FUSE_READLINK ReadLink;
22    FUSE_SYMLINK Symlink name:name target:str;
23    FUSE_MKNOD MkNod arg:fuse_mknod_in name:name;
24    FUSE_MKDIR MkDir arg:fuse_mkdir_in name:name;
25    FUSE_UNLINK Unlink name:name;
26    FUSE_RMDIR RmDir name:name;
27    FUSE_RENAME Rename arg:fuse_rename_in name:name new_name:name;
28    FUSE_LINK Link arg:fuse_link_in name:name;
29    FUSE_OPEN Open arg:fuse_open_in;
30    FUSE_READ Read arg:fuse_read_in;
31    FUSE_WRITE Write arg:fuse_write_in data:[u8; arg.size];
32    FUSE_STATFS StatFs;
33    FUSE_RELEASE Release arg:fuse_release_in;
34    FUSE_FSYNC FSync arg:fuse_fsync_in;
35    FUSE_SETXATTR SetXAttr arg:fuse_setxattr_in name:str value:[u8; arg.size];
36    FUSE_GETXATTR GetXAttr arg:fuse_getxattr_in name:str;
37    FUSE_LISTXATTR ListXAttr arg:fuse_getxattr_in;
38    FUSE_REMOVEXATTR RemoveXAttr name:str;
39    FUSE_FLUSH Flush arg:fuse_flush_in;
40    // Note: FUSE_INIT parsing is handled specially in read_operation() for
41    // backward compatibility, but the variant must still be declared here.
42    FUSE_INIT Init arg:fuse_init_in;
43    FUSE_OPENDIR OpenDir arg:fuse_open_in;
44    FUSE_READDIR ReadDir arg:fuse_read_in;
45    FUSE_RELEASEDIR ReleaseDir arg:fuse_release_in;
46    FUSE_FSYNCDIR FSyncDir arg:fuse_fsync_in;
47    FUSE_GETLK GetLock arg:fuse_lk_in;
48    FUSE_SETLK SetLock arg:fuse_lk_in;
49    FUSE_SETLKW SetLockSleep arg:fuse_lk_in;
50    FUSE_ACCESS Access arg:fuse_access_in;
51    FUSE_CREATE Create arg:fuse_create_in name:name;
52    FUSE_INTERRUPT Interrupt arg:fuse_interrupt_in;
53    FUSE_BMAP BMap arg:fuse_bmap_in;
54    FUSE_DESTROY Destroy;
55    FUSE_IOCTL Ioctl arg:fuse_ioctl_in data:[u8; arg.in_size];
56    FUSE_POLL Poll arg:fuse_poll_in;
57    FUSE_NOTIFY_REPLY NotifyReply arg:fuse_notify_retrieve_in data:[u8];
58    FUSE_BATCH_FORGET BatchForget arg:fuse_batch_forget_in nodes:[u8];
59    FUSE_FALLOCATE FAllocate arg:fuse_fallocate_in;
60    FUSE_READDIRPLUS ReadDirPlus arg:fuse_read_in;
61    FUSE_RENAME2 Rename2 arg:fuse_rename2_in name:name new_name:name;
62    FUSE_LSEEK LSeek arg:fuse_lseek_in;
63    FUSE_COPY_FILE_RANGE CopyFileRange arg:fuse_copy_file_range_in;
64    FUSE_SETUPMAPPING SetupMapping arg:fuse_setupmapping_in;
65    FUSE_REMOVEMAPPING RemoveMapping arg:fuse_removemapping_in mappings:[u8];
66    FUSE_SYNCFS SyncFs _arg:fuse_syncfs_in;
67    FUSE_STATX StatX arg:fuse_statx_in;
68    FUSE_CANONICAL_PATH CanonicalPath;
69}
70
71/// A request received from the FUSE kernel module.
72pub struct Request {
73    header: fuse_in_header,
74    operation: FuseOperation,
75}
76
77impl Request {
78    /// Create a new request from the specified data.
79    pub fn new(mut reader: impl RequestReader) -> lx::Result<Self> {
80        let header: fuse_in_header = reader.read_type()?;
81        let operation = Self::read_operation(&header, reader);
82        Ok(Self { header, operation })
83    }
84
85    /// Gets the FUSE opcode for this request.
86    pub fn opcode(&self) -> u32 {
87        self.header.opcode
88    }
89
90    /// Gets the unique identifier of this request.
91    pub fn unique(&self) -> u64 {
92        self.header.unique
93    }
94
95    /// Gets the FUSE node ID of the inode that this request is for.
96    pub fn node_id(&self) -> u64 {
97        self.header.nodeid
98    }
99
100    /// Gets the user ID of the user that issued this request.
101    pub fn uid(&self) -> lx::uid_t {
102        self.header.uid
103    }
104
105    /// Gets the group ID of the user that issued this request.
106    pub fn gid(&self) -> lx::gid_t {
107        self.header.gid
108    }
109
110    /// Gets the process ID of the process that issued this request.
111    pub fn pid(&self) -> u32 {
112        self.header.pid
113    }
114
115    /// Gets the operation that this request should perform.
116    pub fn operation(&self) -> &FuseOperation {
117        &self.operation
118    }
119
120    /// Log the request.
121    pub fn log(&self) {
122        tracing::trace!(
123            unique = self.unique(),
124            node_id = self.node_id(),
125            uid = self.uid(),
126            gid = self.gid(),
127            pid = self.pid(),
128            operation = ?self.operation,
129            "Request",
130        );
131    }
132
133    fn read_operation(header: &fuse_in_header, mut reader: impl RequestReader) -> FuseOperation {
134        if header.len as usize > reader.remaining_len() + size_of_val(header) {
135            tracing::error!(
136                opcode = header.opcode,
137                unique = header.unique,
138                header_len = header.len,
139                len = reader.remaining_len() + size_of_val(header),
140                "Invalid message length",
141            );
142            return FuseOperation::Invalid;
143        }
144
145        // FUSE_INIT requires special handling: the kernel may send either the
146        // legacy 16-byte payload or the extended 64-byte payload (when
147        // FUSE_INIT_EXT is supported). Read whatever is available and
148        // zero-fill the rest so that flags2 defaults to 0 for old kernels.
149        if header.opcode == FUSE_INIT {
150            let payload_len = (header.len as usize) - size_of::<fuse_in_header>();
151            let available = payload_len.min(size_of::<fuse_init_in>());
152            if available < FUSE_COMPAT_INIT_IN_SIZE as usize {
153                tracing::error!(
154                    opcode = header.opcode,
155                    unique = header.unique,
156                    len = available,
157                    "FUSE_INIT payload too small",
158                );
159                return FuseOperation::Invalid;
160            }
161            let mut init = fuse_init_in::new_zeroed();
162            if let Err(e) = reader.read_exact(&mut init.as_mut_bytes()[..available]) {
163                tracing::error!(
164                    opcode = header.opcode,
165                    unique = header.unique,
166                    error = &e as &dyn std::error::Error,
167                    "Failed to read FUSE_INIT payload",
168                );
169                return FuseOperation::Invalid;
170            }
171            return FuseOperation::Init { arg: init };
172        }
173
174        match FuseOperation::read(header.opcode, reader) {
175            Ok(operation) => operation,
176            Err(e) => {
177                tracing::error!(
178                    opcode = header.opcode,
179                    unique = header.unique,
180                    error = &e as &dyn std::error::Error,
181                    "Invalid message payload",
182                );
183                FuseOperation::Error(e)
184            }
185        }
186    }
187}
188
189const NAME_MAX: usize = 255;
190
191pub fn check_name(name: &[u8]) -> lx::Result<()> {
192    if name.is_empty()
193        || name == b"."
194        || name == b".."
195        || name.contains(&b'/')
196        || name.contains(&b'\0')
197    {
198        return Err(lx::Error::EINVAL);
199    }
200    if name.len() > NAME_MAX {
201        return Err(lx::Error::ENAMETOOLONG);
202    }
203    Ok(())
204}
205
206/// Helpers to parse FUSE messages.
207pub trait RequestReader: io::Read {
208    /// Read until a matching byte is found.
209    ///
210    /// This should advance the read position beyond the matching byte, and return the data up to
211    /// (but not including) the matching byte.
212    ///
213    /// This is used to read NULL-terminated strings.
214    fn read_until(&mut self, byte: u8) -> lx::Result<Vec<u8>>;
215
216    /// Gets the remaining, unread length of the input data.
217    fn remaining_len(&self) -> usize;
218
219    /// Consume the next `count` bytes.
220    fn read_count(&mut self, count: usize) -> lx::Result<Box<[u8]>> {
221        let mut buffer = vec![0u8; count];
222        self.read_exact(&mut buffer)?;
223        Ok(buffer.into_boxed_slice())
224    }
225
226    /// Read all the remaining data.
227    fn read_all(&mut self) -> lx::Result<Box<[u8]>> {
228        self.read_count(self.remaining_len())
229    }
230
231    /// Read a struct of type `T`.
232    fn read_type<T: IntoBytes + FromBytes + Immutable + KnownLayout>(&mut self) -> lx::Result<T> {
233        let mut value: T = T::new_zeroed();
234        self.read_exact(value.as_mut_bytes())?;
235        Ok(value)
236    }
237
238    /// Read a NULL-terminated string
239    fn string(&mut self) -> lx::Result<lx::LxString> {
240        let buffer = self.read_until(b'\0')?;
241        Ok(lx::LxString::from_vec(buffer))
242    }
243
244    /// Read a NULL-terminated string and ensure it's a valid path name component.
245    fn name(&mut self) -> lx::Result<lx::LxString> {
246        let name = self.string()?;
247        check_name(name.as_bytes())?;
248        Ok(name)
249    }
250}
251
252impl RequestReader for &[u8] {
253    fn read_until(&mut self, byte: u8) -> lx::Result<Vec<u8>> {
254        let length = self
255            .iter()
256            .position(|&c| c == byte)
257            .ok_or(lx::Error::EINVAL)?;
258
259        let result = Vec::from(&self[..length]);
260        *self = &self[length + 1..];
261        Ok(result)
262    }
263
264    fn remaining_len(&self) -> usize {
265        self.len()
266    }
267}
268
269#[cfg(test)]
270pub(crate) mod tests {
271    use super::*;
272
273    #[test]
274    fn parse_init() {
275        let request = Request::new(FUSE_INIT_REQUEST).unwrap();
276        check_header(&request, 1, FUSE_INIT, 0);
277        if let FuseOperation::Init { arg } = request.operation {
278            assert_eq!(arg.major, 7);
279            assert_eq!(arg.minor, 27);
280            assert_eq!(arg.max_readahead, 131072);
281            assert_eq!(arg.flags, 0x3FFFFB);
282            assert_eq!(arg.flags2, 0);
283        } else {
284            panic!("Incorrect operation {:?}", request.operation);
285        }
286    }
287
288    #[test]
289    fn parse_init_too_small_payload_is_invalid() {
290        // Build a FUSE_INIT request with only 8 bytes of payload (less than
291        // the required 16-byte legacy minimum).
292        let header = fuse_in_header {
293            len: (size_of::<fuse_in_header>() + 8) as u32,
294            opcode: FUSE_INIT,
295            unique: 1,
296            nodeid: 0,
297            uid: 0,
298            gid: 0,
299            pid: 0,
300            padding: 0,
301        };
302        let mut data = Vec::new();
303        data.extend_from_slice(header.as_bytes());
304        data.extend_from_slice(&[7, 0, 0, 0, 27, 0, 0, 0]); // only major + minor
305        let request = Request::new(data.as_slice()).unwrap();
306        assert!(matches!(request.operation, FuseOperation::Invalid));
307    }
308
309    #[test]
310    fn parse_init_extended_preserves_flags2() {
311        // Build a full 64-byte extended FUSE_INIT payload with FUSE_INIT_EXT
312        // set in flags and a non-zero flags2.
313        let init_in = fuse_init_in {
314            major: 7,
315            minor: 39,
316            max_readahead: 131072,
317            flags: FUSE_INIT_EXT,
318            flags2: FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2,
319            unused: [0; 11],
320        };
321        let header = fuse_in_header {
322            len: (size_of::<fuse_in_header>() + size_of::<fuse_init_in>()) as u32,
323            opcode: FUSE_INIT,
324            unique: 1,
325            nodeid: 0,
326            uid: 0,
327            gid: 0,
328            pid: 0,
329            padding: 0,
330        };
331        let mut data = Vec::new();
332        data.extend_from_slice(header.as_bytes());
333        data.extend_from_slice(init_in.as_bytes());
334        let request = Request::new(data.as_slice()).unwrap();
335        check_header(&request, 1, FUSE_INIT, 0);
336        if let FuseOperation::Init { arg } = request.operation {
337            assert_eq!(arg.major, 7);
338            assert_eq!(arg.minor, 39);
339            assert_eq!(arg.max_readahead, 131072);
340            assert_eq!(arg.flags, FUSE_INIT_EXT);
341            assert_eq!(arg.flags2, FUSE_DIRECT_IO_ALLOW_MMAP_FLAG2);
342        } else {
343            panic!("Incorrect operation {:?}", request.operation);
344        }
345    }
346
347    #[test]
348    fn parse_get_attr() {
349        let request = Request::new(FUSE_GETATTR_REQUEST).unwrap();
350        check_header(&request, 2, FUSE_GETATTR, 1);
351        if let FuseOperation::GetAttr { arg } = request.operation {
352            assert_eq!(arg.fh, 0);
353            assert_eq!(arg.getattr_flags, 0);
354        } else {
355            panic!("Incorrect operation {:?}", request.operation);
356        }
357    }
358
359    #[test]
360    fn parse_statx() {
361        let request = Request::new(FUSE_STATX_REQUEST).unwrap();
362        check_header(&request, 2, FUSE_STATX, 1);
363        if let FuseOperation::StatX { arg } = request.operation {
364            assert_eq!(arg.fh, 0);
365            assert_eq!(arg.getattr_flags, 0);
366            let mask = lx::StatExMask::new()
367                .with_file_type(true)
368                .with_mode(true)
369                .with_nlink(true)
370                .with_uid(true)
371                .with_gid(true)
372                .with_atime(true)
373                .with_mtime(true)
374                .with_ctime(true)
375                .with_ino(true)
376                .with_size(true)
377                .with_blocks(true)
378                .with_btime(true);
379            assert_eq!(arg.mask, mask.into_bits());
380            let flags = StatxFlags::new().with_dont_sync(true);
381            assert_eq!(arg.flags.into_bits(), flags.into_bits());
382        } else {
383            panic!("Incorrect operation {:?}", request.operation);
384        }
385    }
386
387    #[test]
388    fn parse_lookup() {
389        let request = Request::new(FUSE_LOOKUP_REQUEST).unwrap();
390        check_header(&request, 3, FUSE_LOOKUP, 1);
391        if let FuseOperation::Lookup { name } = request.operation {
392            assert_eq!(name, "hello");
393        } else {
394            panic!("Incorrect operation {:?}", request.operation);
395        }
396    }
397
398    #[test]
399    fn parse_open() {
400        let request = Request::new(FUSE_OPEN_REQUEST).unwrap();
401        check_header(&request, 4, FUSE_OPEN, 2);
402        if let FuseOperation::Open { arg } = request.operation {
403            assert_eq!(arg.flags, 0x8000);
404        } else {
405            panic!("Incorrect operation {:?}", request.operation);
406        }
407    }
408
409    #[test]
410    fn parse_read() {
411        let request = Request::new(FUSE_READ_REQUEST).unwrap();
412        check_header(&request, 5, FUSE_READ, 2);
413        if let FuseOperation::Read { arg } = request.operation {
414            assert_eq!(arg.fh, 1);
415            assert_eq!(arg.offset, 0);
416            assert_eq!(arg.size, 4096);
417            assert_eq!(arg.read_flags, 0);
418            assert_eq!(arg.lock_owner, 0);
419            assert_eq!(arg.flags, 0x8000);
420        } else {
421            panic!("Incorrect operation {:?}", request.operation);
422        }
423    }
424
425    #[test]
426    fn parse_flush() {
427        let request = Request::new(FUSE_FLUSH_REQUEST).unwrap();
428        check_header(&request, 7, FUSE_FLUSH, 2);
429        if let FuseOperation::Flush { arg } = request.operation {
430            assert_eq!(arg.fh, 1);
431            // This was copied from a real fuse request; I have no idea why it sends this number
432            // for lock owner especially since locks were not being used.
433            assert_eq!(arg.lock_owner, 13021892616250331871);
434        } else {
435            panic!("Incorrect operation {:?}", request.operation);
436        }
437    }
438
439    #[test]
440    fn parse_release() {
441        let request = Request::new(FUSE_RELEASE_REQUEST).unwrap();
442        check_header(&request, 8, FUSE_RELEASE, 2);
443        if let FuseOperation::Release { arg } = request.operation {
444            assert_eq!(arg.fh, 1);
445            assert_eq!(arg.flags, 0x8000);
446            assert_eq!(arg.release_flags, 0);
447            assert_eq!(arg.lock_owner, 0);
448        } else {
449            panic!("Incorrect operation {:?}", request.operation);
450        }
451    }
452
453    #[test]
454    fn parse_opendir() {
455        let request = Request::new(FUSE_OPENDIR_REQUEST).unwrap();
456        check_header(&request, 9, FUSE_OPENDIR, 1);
457        if let FuseOperation::OpenDir { arg } = request.operation {
458            assert_eq!(arg.flags, 0x18800);
459        } else {
460            panic!("Incorrect operation {:?}", request.operation);
461        }
462    }
463
464    #[test]
465    fn parse_readdir() {
466        let request = Request::new(FUSE_READDIR_REQUEST).unwrap();
467        check_header(&request, 11, FUSE_READDIR, 1);
468        if let FuseOperation::ReadDir { arg } = request.operation {
469            assert_eq!(arg.fh, 0);
470            assert_eq!(arg.offset, 3);
471            assert_eq!(arg.size, 4096);
472            assert_eq!(arg.read_flags, 0);
473            assert_eq!(arg.lock_owner, 0);
474            assert_eq!(arg.flags, 0x18800);
475        } else {
476            panic!("Incorrect operation {:?}", request.operation);
477        }
478    }
479
480    #[test]
481    fn parse_releasedir() {
482        let request = Request::new(FUSE_RELEASEDIR_REQUEST).unwrap();
483        check_header(&request, 12, FUSE_RELEASEDIR, 1);
484        if let FuseOperation::ReleaseDir { arg } = request.operation {
485            assert_eq!(arg.fh, 0);
486            assert_eq!(arg.flags, 0x18800);
487            assert_eq!(arg.release_flags, 0);
488            assert_eq!(arg.lock_owner, 0);
489        } else {
490            panic!("Incorrect operation {:?}", request.operation);
491        }
492    }
493
494    fn check_header(request: &Request, unique: u64, opcode: u32, ino: u64) {
495        assert_eq!(request.unique(), unique);
496        assert_eq!(request.opcode(), opcode);
497        assert_eq!(request.node_id(), ino);
498        assert_eq!(request.uid(), 0);
499        assert_eq!(request.gid(), 0);
500        assert_eq!(
501            request.pid(),
502            if opcode == FUSE_INIT || opcode == FUSE_RELEASE || opcode == FUSE_RELEASEDIR {
503                0
504            } else {
505                971
506            }
507        );
508    }
509
510    pub const FUSE_INIT_REQUEST: &[u8] = &[
511        56, 0, 0, 0, 26, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
512        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 27, 0, 0, 0, 0, 0, 2, 0, 251, 255, 63, 0,
513    ];
514
515    pub const FUSE_GETATTR_REQUEST: &[u8] = &[
516        56, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
517        0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
518    ];
519
520    pub const FUSE_LOOKUP_REQUEST: &[u8] = &[
521        46, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
522        0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 104, 101, 108, 108, 111, 0,
523    ];
524
525    const FUSE_OPEN_REQUEST: &[u8] = &[
526        48, 0, 0, 0, 14, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
527        0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0,
528    ];
529
530    const FUSE_READ_REQUEST: &[u8] = &[
531        80, 0, 0, 0, 15, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
532        0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0,
533        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0,
534    ];
535
536    const FUSE_FLUSH_REQUEST: &[u8] = &[
537        64, 0, 0, 0, 25, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
538        0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 223, 18,
539        226, 110, 87, 14, 183, 180,
540    ];
541
542    const FUSE_RELEASE_REQUEST: &[u8] = &[
543        64, 0, 0, 0, 18, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
544        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
545        0, 0, 0, 0,
546    ];
547
548    const FUSE_OPENDIR_REQUEST: &[u8] = &[
549        48, 0, 0, 0, 27, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
550        0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 0, 136, 1, 0, 0, 0, 0, 0,
551    ];
552
553    const FUSE_READDIR_REQUEST: &[u8] = &[
554        80, 0, 0, 0, 28, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
555        0, 0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 16,
556        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 1, 0, 0, 0, 0, 0,
557    ];
558
559    const FUSE_RELEASEDIR_REQUEST: &[u8] = &[
560        64, 0, 0, 0, 29, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
561        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 1, 0, 0, 0, 0, 0, 0, 0, 0,
562        0, 0, 0, 0, 0,
563    ];
564
565    const FUSE_STATX_REQUEST: &[u8] = &[
566        64, 0, 0, 0, 52, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
567        0, 0, 203, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0,
568        0, 255, 15, 0, 0,
569    ];
570
571    #[test]
572    fn name_too_long_returns_enametoolong() {
573        // Create a name with 256 characters (exceeds NAME_MAX of 255) + null terminator
574        let mut data = vec![b'a'; 256];
575        data.push(0); // null terminator
576
577        let mut reader: &[u8] = &data;
578        let result = reader.name();
579
580        assert_eq!(result, Err(lx::Error::ENAMETOOLONG));
581    }
582
583    #[test]
584    fn name_rejects_path_traversal() {
585        // ".." must be rejected to prevent directory traversal
586        let data = b"..\0";
587        let mut reader: &[u8] = &data[..];
588        assert_eq!(reader.name(), Err(lx::Error::EINVAL));
589
590        // "." must be rejected
591        let data = b".\0";
592        let mut reader: &[u8] = &data[..];
593        assert_eq!(reader.name(), Err(lx::Error::EINVAL));
594
595        // Names containing "/" must be rejected
596        let data = b"../host_secret.txt\0";
597        let mut reader: &[u8] = &data[..];
598        assert_eq!(reader.name(), Err(lx::Error::EINVAL));
599
600        // Empty names must be rejected
601        let data = b"\0";
602        let mut reader: &[u8] = &data[..];
603        assert_eq!(reader.name(), Err(lx::Error::EINVAL));
604
605        // Valid single-component names must be accepted
606        let data = b"valid_file.txt\0";
607        let mut reader: &[u8] = &data[..];
608        assert!(reader.name().is_ok());
609    }
610}