1#[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
15fuse_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 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
71pub struct Request {
73 header: fuse_in_header,
74 operation: FuseOperation,
75}
76
77impl Request {
78 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 pub fn opcode(&self) -> u32 {
87 self.header.opcode
88 }
89
90 pub fn unique(&self) -> u64 {
92 self.header.unique
93 }
94
95 pub fn node_id(&self) -> u64 {
97 self.header.nodeid
98 }
99
100 pub fn uid(&self) -> lx::uid_t {
102 self.header.uid
103 }
104
105 pub fn gid(&self) -> lx::gid_t {
107 self.header.gid
108 }
109
110 pub fn pid(&self) -> u32 {
112 self.header.pid
113 }
114
115 pub fn operation(&self) -> &FuseOperation {
117 &self.operation
118 }
119
120 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 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
206pub trait RequestReader: io::Read {
208 fn read_until(&mut self, byte: u8) -> lx::Result<Vec<u8>>;
215
216 fn remaining_len(&self) -> usize;
218
219 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 fn read_all(&mut self) -> lx::Result<Box<[u8]>> {
228 self.read_count(self.remaining_len())
229 }
230
231 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 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 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 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]); 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 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 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 let mut data = vec![b'a'; 256];
575 data.push(0); 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 let data = b"..\0";
587 let mut reader: &[u8] = &data[..];
588 assert_eq!(reader.name(), Err(lx::Error::EINVAL));
589
590 let data = b".\0";
592 let mut reader: &[u8] = &data[..];
593 assert_eq!(reader.name(), Err(lx::Error::EINVAL));
594
595 let data = b"../host_secret.txt\0";
597 let mut reader: &[u8] = &data[..];
598 assert_eq!(reader.name(), Err(lx::Error::EINVAL));
599
600 let data = b"\0";
602 let mut reader: &[u8] = &data[..];
603 assert_eq!(reader.name(), Err(lx::Error::EINVAL));
604
605 let data = b"valid_file.txt\0";
607 let mut reader: &[u8] = &data[..];
608 assert!(reader.name().is_ok());
609 }
610}