async_fusex/
fuse_reply.rs

1//! The implementation of FUSE response
2
3// We allow unused async here for supporting other asynchronous fuse IO in the future
4#![allow(clippy::unused_async)]
5
6use std::convert::AsRef;
7#[cfg(feature = "abi-7-18")]
8use std::ffi::CString;
9use std::ffi::OsStr;
10use std::fmt::Debug;
11use std::fs::File;
12use std::io::{IoSlice, Write};
13use std::os::unix::ffi::OsStrExt;
14use std::time::Duration;
15use std::{mem, slice};
16
17use clippy_utilities::{Cast, OverflowArithmetic};
18use nix::errno::Errno;
19use nix::sys::stat::SFlag;
20use tracing::debug;
21
22use super::abi_marker;
23use super::protocol::{
24    FuseAttr, FuseAttrOut, FuseBMapOut, FuseDirEnt, FuseEntryOut, FuseFileLock, FuseGetXAttrOut,
25    FuseInitOut, FuseKStatFs, FuseLockOut, FuseOpenOut, FuseOutHeader, FuseStatFsOut, FuseWriteOut,
26};
27#[cfg(feature = "abi-7-18")]
28use super::protocol::{FuseNotifyCode::FUSE_NOTIFY_DELETE, FuseNotifyDeleteOut};
29
30/// This trait describes a type that can be converted to `Vec<IoSlice>`
31pub trait AsIoSliceList {
32    /// Convert the type to a Vec of `IoSlice`
33    fn as_io_slice_list(&self) -> Vec<IoSlice<'_>>;
34    /// The sum of the length of all the `IoSlice`s in the Vec
35    fn len(&self) -> usize;
36    /// Vec of `IoSlice` is empty
37    fn is_empty(&self) -> bool;
38}
39
40/// Any type implement the `AsIoSlice` trait, its Vec can be automatically for
41/// `Vec<IoSlice>`.
42impl<T> AsIoSliceList for Vec<T>
43where
44    T: AsIoSlice,
45{
46    fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
47        self.iter().map(T::as_io_slice).collect()
48    }
49
50    fn len(&self) -> usize {
51        self.iter().map(T::len).sum()
52    }
53
54    fn is_empty(&self) -> bool {
55        self.is_empty()
56    }
57}
58
59/// All the Type implement `CouldBeAsIoSliceList` and `AsIoSlice` can
60/// automatically implement `AsIoSliceList`
61pub trait CouldBeAsIoSliceList {}
62
63impl<T> AsIoSliceList for T
64where
65    T: AsIoSlice + CouldBeAsIoSliceList,
66{
67    fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
68        vec![self.as_io_slice()]
69    }
70
71    fn len(&self) -> usize {
72        self.len()
73    }
74
75    fn is_empty(&self) -> bool {
76        self.is_empty()
77    }
78}
79
80/// Implement `AsIoSliceList` for Vec<u8> Separately
81impl AsIoSliceList for Vec<u8> {
82    fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
83        vec![self.as_io_slice()]
84    }
85
86    fn len(&self) -> usize {
87        self.len()
88    }
89
90    fn is_empty(&self) -> bool {
91        self.is_empty()
92    }
93}
94
95/// Implement `AsIoSliceList` for empty tuple
96impl AsIoSliceList for () {
97    fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
98        vec![]
99    }
100
101    fn len(&self) -> usize {
102        0
103    }
104
105    fn is_empty(&self) -> bool {
106        true
107    }
108}
109
110impl<U, V> AsIoSliceList for (U, V)
111where
112    U: AsIoSlice,
113    V: AsIoSlice,
114{
115    fn as_io_slice_list(&self) -> Vec<IoSlice<'_>> {
116        vec![self.0.as_io_slice(), self.1.as_io_slice()]
117    }
118
119    fn len(&self) -> usize {
120        AsIoSlice::len(&self.0).overflow_add(AsIoSlice::len(&self.1))
121    }
122
123    fn is_empty(&self) -> bool {
124        self.0.is_empty() && self.1.is_empty()
125    }
126}
127
128/// The trait indicates the ability to be converted to `IoSlice`
129pub trait AsIoSlice {
130    /// Convert the type to `IoSlice`
131    fn as_io_slice(&self) -> IoSlice<'_>;
132    /// Tell if the type is ready to be converted, please call it before calling
133    /// `as_io_slice`
134    fn can_convert(&self) -> bool;
135    /// The length of the `IoSlice`
136    fn len(&self) -> usize;
137    /// `IoSlice` is empty
138    fn is_empty(&self) -> bool;
139}
140
141impl AsIoSlice for Vec<u8> {
142    fn as_io_slice(&self) -> IoSlice<'_> {
143        IoSlice::new(self.as_slice())
144    }
145
146    fn can_convert(&self) -> bool {
147        true
148    }
149
150    fn len(&self) -> usize {
151        self.len()
152    }
153
154    fn is_empty(&self) -> bool {
155        self.is_empty()
156    }
157}
158
159/// FUSE raw response
160#[derive(Debug)]
161struct ReplyRaw<'a> {
162    /// The FUSE request unique ID
163    unique: u64,
164    /// The FUSE device fd
165    file: &'a mut File,
166}
167
168impl<'a> ReplyRaw<'a> {
169    /// Create `ReplyRaw`
170    fn new(unique: u64, file: &'a mut File) -> Self {
171        Self { unique, file }
172    }
173
174    /// Send raw message to FUSE kernel
175    #[allow(clippy::needless_pass_by_value)]
176    fn send_raw_message(
177        self,
178        error: i32,
179        data: impl AsIoSliceList + Send + Sync + 'static,
180    ) -> nix::Result<usize> {
181        let header_len = mem::size_of::<FuseOutHeader>();
182
183        let header = FuseOutHeader {
184            len: (header_len.overflow_add(data.len())).cast(),
185            error,
186            unique: self.unique,
187        };
188
189        let header_bytes = abi_marker::as_abi_bytes(&header);
190
191        let (single, mut vecs);
192        let io_slices: &[IoSlice] = if data.len() > 0 {
193            vecs = data.as_io_slice_list();
194            vecs.insert(0, IoSlice::new(header_bytes));
195            vecs.as_slice()
196        } else {
197            single = [IoSlice::new(header_bytes)];
198            &single
199        };
200
201        let wsize = self
202            .file
203            .write_vectored(io_slices)
204            .map_err(|e| Errno::try_from(e).unwrap_or(Errno::EIO))?;
205
206        debug!("sent {} bytes to fuse device successfully", wsize);
207        Ok(wsize)
208    }
209
210    /// Send response to FUSE kernel
211    async fn send(self, data: impl AsIoSliceList + Send + Sync + 'static) -> nix::Result<usize> {
212        self.send_raw_message(0_i32, data)
213    }
214
215    /// Send error code response to FUSE kernel
216    async fn send_error_code(self, error_code: Errno) -> nix::Result<usize> {
217        // FUSE requires the error number to be negative
218        self.send_raw_message(
219            crate::util::convert_nix_errno_to_cint(error_code).overflow_neg(),
220            (),
221        )
222    }
223
224    #[allow(clippy::wildcard_enum_match_arm)]
225    /// Send error response to FUSE kernel
226    async fn send_error(self, err: AsyncFusexError) -> nix::Result<usize> {
227        match err {
228            AsyncFusexError::InternalErr { source, context } => {
229                let error_code = if let Some(nix_err) =
230                    source.root_cause().downcast_ref::<nix::Error>()
231                {
232                    if *nix_err == nix::errno::Errno::UnknownErrno {
233                        panic!(
234                            "should not send nix::errno::Errno::UnknownErrno to FUSE kernel, \
235                                    the error is: {} ,context is : {:?}",
236                            crate::util::format_anyhow_error(&source),
237                            context,
238                        );
239                    } else {
240                        nix_err
241                    }
242                } else {
243                    panic!(
244                            "should not send non-nix error to FUSE kernel, the error is: {},context is : {:?}",
245                            crate::util::format_anyhow_error(&source),context,
246                        );
247                };
248                self.send_error_code(*error_code).await
249            }
250            err => {
251                panic!("should not send non-internal error to FUSE kernel ,the error is : {err}",);
252            }
253        }
254    }
255}
256
257/// Impl fuse reply new
258macro_rules! impl_fuse_reply_new_for{
259    {$($t:tt,)+} => {
260        $(impl<'a> $t<'a> {
261            /// New fuse reply
262            #[must_use]
263            pub fn new(unique: u64, file: &'a mut File) -> Self {
264                Self {
265                    reply: ReplyRaw::new(unique, file),
266                }
267            }
268        })+
269    }
270}
271
272impl_fuse_reply_new_for! {
273    ReplyAttr,
274    ReplyBMap,
275    ReplyCreate,
276    ReplyData,
277    ReplyEmpty,
278    ReplyEntry,
279    ReplyInit,
280    ReplyLock,
281    ReplyOpen,
282    ReplyStatFs,
283    ReplyWrite,
284    ReplyXAttr,
285}
286
287use crate::error::AsyncFusexError;
288use crate::fs_util::StatFsParam;
289
290/// Impl fuse reply error
291macro_rules! impl_fuse_reply_error_for{
292    {$($t:tt,)+} => {
293        $(impl $t<'_> {
294            #[allow(dead_code)]
295            /// fuse reply error
296            pub async fn error(self, err: AsyncFusexError) -> nix::Result<usize> {
297                self.reply.send_error(err).await
298            }
299
300            #[allow(dead_code)]
301            /// fuse reply error code
302            pub async fn error_code(self, error_code: Errno) -> nix::Result<usize> {
303                self.reply.send_error_code(error_code).await
304            }
305        })+
306    }
307}
308
309impl_fuse_reply_error_for! {
310    ReplyAttr,
311    ReplyBMap,
312    ReplyCreate,
313    ReplyData,
314    ReplyDirectory,
315    ReplyEmpty,
316    ReplyEntry,
317    ReplyInit,
318    ReplyLock,
319    ReplyOpen,
320    ReplyStatFs,
321    ReplyWrite,
322    ReplyXAttr,
323}
324
325/// Impl `AsIoSlice` trait
326macro_rules! impl_as_ioslice_for {
327    {$($t:ty,)+} => {
328        $(impl AsIoSlice for $t {
329            #[allow(dead_code)]
330            fn as_io_slice(&self) -> IoSlice<'_> {
331                IoSlice::new(abi_marker::as_abi_bytes(self))
332            }
333
334            #[allow(dead_code)]
335            fn can_convert(&self) -> bool {
336                true
337            }
338
339            #[allow(dead_code)]
340            fn len(&self) -> usize {
341                mem::size_of::<Self>()
342            }
343
344            #[allow(dead_code)]
345            fn is_empty(&self) -> bool {
346                AsIoSlice::len(self) == 0
347            }
348        }
349
350        impl CouldBeAsIoSliceList for $t {}
351        )+
352    }
353}
354
355impl_as_ioslice_for! {
356    FuseAttrOut,
357    FuseBMapOut,
358    FuseEntryOut,
359    FuseInitOut,
360    FuseLockOut,
361    FuseOpenOut,
362    FuseStatFsOut,
363    FuseWriteOut,
364    FuseGetXAttrOut,
365}
366#[cfg(feature = "abi-7-18")]
367impl_as_ioslice_for! {
368    FuseNotifyDeleteOut,
369}
370
371/// FUSE init response
372#[derive(Debug)]
373pub struct ReplyInit<'a> {
374    /// The inner raw reply
375    reply: ReplyRaw<'a>,
376}
377
378impl ReplyInit<'_> {
379    /// Reply init response
380    pub async fn init(self, resp: FuseInitOut) -> nix::Result<usize> {
381        self.reply.send(resp).await
382    }
383}
384
385/// FUSE empty response
386#[derive(Debug)]
387pub struct ReplyEmpty<'a> {
388    /// The inner raw reply
389    reply: ReplyRaw<'a>,
390}
391
392impl ReplyEmpty<'_> {
393    /// Reply with empty OK response
394    pub async fn ok(self) -> nix::Result<usize> {
395        self.reply.send(()).await
396    }
397}
398
399/// FUSE data response
400#[derive(Debug)]
401pub struct ReplyData<'a> {
402    /// The inner raw reply
403    reply: ReplyRaw<'a>,
404}
405
406impl ReplyData<'_> {
407    /// Reply with byte data response
408    pub async fn data(
409        self,
410        bytes: impl AsIoSliceList + Send + Sync + 'static,
411    ) -> nix::Result<usize> {
412        self.reply.send(bytes).await
413    }
414}
415
416/// FUSE entry response
417#[derive(Debug)]
418pub struct ReplyEntry<'a> {
419    /// The inner raw reply
420    reply: ReplyRaw<'a>,
421}
422
423impl ReplyEntry<'_> {
424    /// Reply to a request with the given entry
425    pub async fn entry(self, ttl: Duration, attr: FuseAttr, generation: u64) -> nix::Result<usize> {
426        self.reply
427            .send(FuseEntryOut {
428                nodeid: attr.ino,
429                generation,
430                entry_valid: ttl.as_secs(),
431                attr_valid: ttl.as_secs(),
432                entry_valid_nsec: ttl.subsec_nanos(),
433                attr_valid_nsec: ttl.subsec_nanos(),
434                attr,
435            })
436            .await
437    }
438}
439
440/// FUSE attribute response
441#[derive(Debug)]
442pub struct ReplyAttr<'a> {
443    /// The inner raw reply
444    reply: ReplyRaw<'a>,
445}
446
447impl ReplyAttr<'_> {
448    /// Reply to a request with the given attribute
449    pub async fn attr(self, ttl: Duration, attr: FuseAttr) -> nix::Result<usize> {
450        self.reply
451            .send(FuseAttrOut {
452                attr_valid: ttl.as_secs(),
453                attr_valid_nsec: ttl.subsec_nanos(),
454                dummy: 0,
455                attr,
456            })
457            .await
458    }
459}
460
461/// FUSE open response
462#[derive(Debug)]
463pub struct ReplyOpen<'a> {
464    /// The inner raw reply
465    reply: ReplyRaw<'a>,
466}
467
468impl ReplyOpen<'_> {
469    /// Reply to a request with the given open result
470    pub async fn opened(self, fh: u64, flags: u32) -> nix::Result<usize> {
471        self.reply
472            .send(FuseOpenOut {
473                fh,
474                open_flags: flags,
475                padding: 0,
476            })
477            .await
478    }
479}
480
481/// FUSE write response
482#[derive(Debug)]
483pub struct ReplyWrite<'a> {
484    /// The inner raw reply
485    reply: ReplyRaw<'a>,
486}
487
488impl ReplyWrite<'_> {
489    /// Reply to a request with the given open result
490    pub async fn written(self, size: u32) -> nix::Result<usize> {
491        self.reply.send(FuseWriteOut { size, padding: 0 }).await
492    }
493}
494
495/// FUSE statfs response
496#[derive(Debug)]
497pub struct ReplyStatFs<'a> {
498    /// The inner raw reply
499    reply: ReplyRaw<'a>,
500}
501
502impl ReplyStatFs<'_> {
503    /// Reply statfs response
504    #[allow(dead_code)]
505    pub async fn statfs(self, param: StatFsParam) -> nix::Result<usize> {
506        self.reply
507            .send(FuseStatFsOut {
508                st: FuseKStatFs {
509                    blocks: param.blocks,
510                    bfree: param.bfree,
511                    bavail: param.bavail,
512                    files: param.files,
513                    ffree: param.f_free,
514                    bsize: param.bsize,
515                    namelen: param.namelen,
516                    frsize: param.frsize,
517                    padding: 0,
518                    spare: [0; 6],
519                },
520            })
521            .await
522    }
523}
524
525/// FUSE create response
526#[derive(Debug)]
527pub struct ReplyCreate<'a> {
528    /// The inner raw reply
529    reply: ReplyRaw<'a>,
530}
531
532impl ReplyCreate<'_> {
533    /// Reply to a request with the given entry
534    #[allow(dead_code)]
535    pub async fn created(
536        self,
537        ttl: &Duration,
538        attr: FuseAttr,
539        generation: u64,
540        fh: u64,
541        flags: u32,
542    ) -> nix::Result<usize> {
543        self.reply
544            .send((
545                FuseEntryOut {
546                    nodeid: attr.ino,
547                    generation,
548                    entry_valid: ttl.as_secs(),
549                    attr_valid: ttl.as_secs(),
550                    entry_valid_nsec: ttl.subsec_nanos(),
551                    attr_valid_nsec: ttl.subsec_nanos(),
552                    attr,
553                },
554                FuseOpenOut {
555                    fh,
556                    open_flags: flags,
557                    padding: 0,
558                },
559            ))
560            .await
561    }
562}
563
564/// FUSE lock response
565#[derive(Debug)]
566pub struct ReplyLock<'a> {
567    /// The inner raw reply
568    reply: ReplyRaw<'a>,
569}
570
571impl ReplyLock<'_> {
572    /// Reply to a request with the given open result
573    #[allow(dead_code)]
574    pub async fn locked(self, start: u64, end: u64, typ: u32, pid: u32) -> nix::Result<usize> {
575        self.reply
576            .send(FuseLockOut {
577                lk: FuseFileLock {
578                    start,
579                    end,
580                    typ,
581                    pid,
582                },
583            })
584            .await
585    }
586}
587
588/// FUSE bmap response
589#[derive(Debug)]
590pub struct ReplyBMap<'a> {
591    /// The inner raw reply
592    reply: ReplyRaw<'a>,
593}
594
595impl ReplyBMap<'_> {
596    /// Reply to a request with the given open result
597    #[allow(dead_code)]
598    pub async fn bmap(self, block: u64) -> nix::Result<usize> {
599        self.reply.send(FuseBMapOut { block }).await
600    }
601}
602
603/// FUSE directory response
604#[derive(Debug)]
605pub struct ReplyDirectory<'a> {
606    /// The inner raw reply
607    reply: ReplyRaw<'a>,
608    /// The directory data in bytes
609    data: Vec<u8>,
610}
611
612impl<'a> ReplyDirectory<'a> {
613    /// Creates a new `ReplyDirectory` with a specified buffer size.
614    #[must_use]
615    pub fn new(unique: u64, file: &'a mut File, size: usize) -> Self {
616        Self {
617            reply: ReplyRaw::new(unique, file),
618            data: Vec::with_capacity(size),
619        }
620    }
621
622    /// Add an entry to the directory reply buffer. Returns true if the buffer
623    /// is full. A transparent offset value can be provided for each entry.
624    /// The kernel uses these value to request the next entries in further
625    /// readdir calls
626    pub fn add<T: AsRef<OsStr>>(&mut self, ino: u64, offset: i64, kind: SFlag, name: T) -> bool {
627        let name_bytes = name.as_ref().as_bytes();
628        let dirent = FuseDirEnt {
629            ino,
630            off: offset.cast(),
631            namelen: name_bytes.len().cast(),
632            typ: crate::util::mode_from_kind_and_perm(kind, 0).overflow_shr(12),
633        };
634        let entlen = dirent.size_with_name();
635
636        // This is similar to call `FUSE_REC_ALIGN(entlen)` in `fuse.h`.
637        //
638        // <https://github.com/torvalds/linux/blob/b85ea95d086471afb4ad062012a4d73cd328fa86/include/uapi/linux/fuse.h#L988-L989>
639        let entsize = crate::util::round_up(entlen, mem::size_of::<u64>()); // 64bit align
640
641        let padlen = entsize.overflow_sub(entlen);
642        if self.data.len().overflow_add(entsize) > self.data.capacity() {
643            return true;
644        }
645
646        // # Safety
647        // The `fuse_dir_ent_in_raw` call is safe here, because:
648        // 1. The `dirent` is just built above, as a in-stack allocated object.
649        //    Therefore, `&dirent` is a valid reference.
650        // 2. `dirent.namelen` is evaluated from `name_bytes`, and `name_bytes` is to be
651        //    written into `self.data`, which is a `Vec<u8>`, after `dirent_bytes` is
652        //    written.
653        let dirent_bytes = unsafe { fuse_dir_ent_in_raw(&dirent) };
654        // Write dirent
655        self.data.extend_from_slice(dirent_bytes);
656
657        // write name
658        self.data.extend_from_slice(name_bytes);
659
660        // write zero padding
661        self.data.extend(std::iter::repeat(0).take(padlen));
662
663        false
664    }
665
666    /// Reply to a request with the filled directory buffer
667    pub async fn ok(self) -> nix::Result<usize> {
668        self.reply.send(self.data).await
669    }
670}
671
672/// Get the underlying raw part of a `FuseDirEnt`, represented in `&[u8]`.
673///
674/// # Safety
675/// Behavior is undefined if any of the following conditions are violated:
676/// - `from` must be a valid reference to `FuseDirEnt`.
677/// - The `namelen` field in `from` must represent a valid length of the nearly
678///   following data of the `FuseDirEnt` struct, i.e., the caller of this
679///   function takes the responsibility to build a raw string (`[u8]`) with
680///   length of `from.namelen` and place it nearly following the `FuseDirEnt` in
681///   a continuous, single allocated space (for example, a `Vec<u8>`).
682unsafe fn fuse_dir_ent_in_raw(from: &FuseDirEnt) -> &[u8] {
683    let base: *const u8 = <*const FuseDirEnt>::cast(from);
684    unsafe {
685        let bytes = slice::from_raw_parts(base, mem::size_of::<FuseDirEnt>());
686        bytes
687    }
688}
689
690/// FUSE extended attribute response
691#[derive(Debug)]
692pub struct ReplyXAttr<'a> {
693    /// The inner raw reply
694    reply: ReplyRaw<'a>,
695}
696
697impl ReplyXAttr<'_> {
698    /// Reply to a request with the size of the xattr.
699    #[allow(dead_code)]
700    pub async fn size(self, size: u32) -> nix::Result<usize> {
701        self.reply.send(FuseGetXAttrOut { size, padding: 0 }).await
702    }
703
704    /// Reply to a request with the data in the xattr.
705    #[allow(dead_code)]
706    pub async fn data(self, bytes: FuseGetXAttrOut) -> nix::Result<usize> {
707        self.reply.send(bytes).await
708    }
709}
710
711#[cfg(feature = "abi-7-18")]
712impl AsIoSlice for CString {
713    fn as_io_slice(&self) -> IoSlice<'_> {
714        IoSlice::new(self.as_bytes_with_nul())
715    }
716
717    fn can_convert(&self) -> bool {
718        true
719    }
720
721    fn len(&self) -> usize {
722        self.as_bytes_with_nul().len()
723    }
724
725    // CString cannot be empty
726    fn is_empty(&self) -> bool {
727        false
728    }
729}
730/// Fuse delete notification
731#[cfg(feature = "abi-7-18")]
732#[derive(Debug)]
733pub struct FuseDeleteNotification<'a> {
734    /// The inner raw reply
735    reply: ReplyRaw<'a>,
736}
737
738#[cfg(feature = "abi-7-18")]
739impl<'a> FuseDeleteNotification<'a> {
740    /// Create `FuseDeleteNotification`
741    #[must_use]
742    pub fn new(file: &'a mut File) -> Self {
743        Self {
744            reply: ReplyRaw::new(0, file),
745        }
746    }
747
748    /// Notify kernel
749    pub async fn notify(self, parent: u64, child: u64, name: String) -> nix::Result<usize> {
750        let notify_delete = FuseNotifyDeleteOut {
751            parent,
752            child,
753            namelen: name.len().cast(),
754            padding: 0,
755        };
756        let file_name = CString::new(name.clone())
757            .unwrap_or_else(|e| panic!("failed to create CString for {name}, error is {e:?}"));
758        #[allow(clippy::as_conversions)] // allow this for enum
759        self.reply
760            .send_raw_message(FUSE_NOTIFY_DELETE as i32, (notify_delete, file_name))
761    }
762}
763
764#[cfg(test)]
765mod test {
766    use std::fs::File;
767    use std::os::unix::io::FromRawFd;
768    use std::time::Duration;
769
770    use aligned_utils::bytes::AlignedBytes;
771    use anyhow::Context;
772    use nix::fcntl::{self, OFlag};
773    use nix::sys::stat::Mode;
774    use nix::unistd;
775    use tokio::io::{AsyncReadExt, AsyncSeekExt};
776
777    use super::super::de::Deserializer;
778    use super::super::protocol::{FuseAttr, FuseAttrOut, FuseOutHeader};
779    use super::ReplyAttr;
780
781    #[test]
782    fn test_slice() {
783        let s = [1_i32, 2_i32, 3_i32, 4_i32, 5_i32, 6_i32];
784        let v = s.to_owned();
785        println!("{v:?}");
786        let v1 = s.to_vec();
787        println!("{v1:?}");
788
789        let s1 = [1_i32, 2_i32, 3_i32];
790        let s2 = [4_i32, 5_i32, 6_i32];
791        let s3 = [7_i32, 8_i32, 9_i32];
792        let l1 = [&s1];
793        let l2 = [&s2, &s3];
794        let mut v1 = l1.to_vec();
795        v1.extend(&l2);
796
797        println!("{l1:?}");
798        println!("{v1:?}");
799    }
800    #[tokio::test(flavor = "multi_thread")]
801    async fn test_reply_output() -> anyhow::Result<()> {
802        let file_name = "fuse_reply.log";
803        let fd = tokio::task::spawn_blocking(move || {
804            fcntl::open(
805                file_name,
806                OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_RDWR,
807                Mode::all(),
808            )
809        })
810        .await??;
811        tokio::task::spawn_blocking(move || unistd::unlink(file_name)).await??;
812
813        let ino = 64;
814        let size = 64;
815        let blocks = 64;
816        let a_time = 64;
817        let m_time = 64;
818        let c_time = 64;
819        let a_timensec = 32;
820        let m_timensec = 32;
821        let c_timensec = 32;
822        let mode = 32;
823        let nlink = 32;
824        let uid = 32;
825        let g_id = 32;
826        let rdev = 32;
827        #[cfg(feature = "abi-7-9")]
828        let blksize = 32;
829        #[cfg(feature = "abi-7-9")]
830        let padding = 32;
831        let attr = FuseAttr {
832            ino,
833            size,
834            blocks,
835            atime: a_time,
836            mtime: m_time,
837            ctime: c_time,
838            atimensec: a_timensec,
839            mtimensec: m_timensec,
840            ctimensec: c_timensec,
841            mode,
842            nlink,
843            uid,
844            gid: g_id,
845            rdev,
846            #[cfg(feature = "abi-7-9")]
847            blksize,
848            #[cfg(feature = "abi-7-9")]
849            padding,
850        };
851
852        let unique = 12345;
853        // SAFETY: `fd` is just opened
854        let mut file = unsafe { File::from_raw_fd(fd) };
855        let reply_attr = ReplyAttr::new(unique, &mut file);
856        reply_attr.attr(Duration::from_secs(1), attr).await?;
857
858        let mut file =
859            tokio::task::spawn_blocking(move || unsafe { tokio::fs::File::from_raw_fd(fd) })
860                .await?;
861        file.seek(std::io::SeekFrom::Start(0)).await?;
862        let mut bytes = Vec::new();
863        file.read_to_end(&mut bytes).await?;
864
865        let mut aligned_bytes = AlignedBytes::new_zeroed(bytes.len(), 4096);
866        aligned_bytes.copy_from_slice(&bytes);
867
868        let mut de = Deserializer::new(&aligned_bytes);
869        let _foh: &FuseOutHeader = de.fetch_ref().context("failed to fetch FuseOutHeader")?;
870        let fao: &FuseAttrOut = de.fetch_ref().context("failed to fetch FuseAttrOut")?;
871
872        debug_assert_eq!(fao.attr.ino, ino);
873        debug_assert_eq!(fao.attr.size, size);
874        debug_assert_eq!(fao.attr.blocks, blocks);
875        debug_assert_eq!(fao.attr.mtime, m_time);
876        debug_assert_eq!(fao.attr.atime, a_time);
877        debug_assert_eq!(fao.attr.ctime, c_time);
878        Ok(())
879    }
880}