fuse/
session.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use super::Fuse;
5use super::Mapper;
6use super::protocol::*;
7use super::reply::ReplySender;
8use super::request::FuseOperation;
9use super::request::Request;
10use super::request::RequestReader;
11use parking_lot::RwLock;
12use std::io;
13use std::sync::atomic;
14use thiserror::Error;
15use zerocopy::FromZeros;
16use zerocopy::Immutable;
17use zerocopy::KnownLayout;
18
19// These are the flags that libfuse enables by default when calling init.
20const DEFAULT_FLAGS: u32 = FUSE_ASYNC_READ
21    | FUSE_PARALLEL_DIROPS
22    | FUSE_AUTO_INVAL_DATA
23    | FUSE_HANDLE_KILLPRIV
24    | FUSE_ASYNC_DIO
25    | FUSE_ATOMIC_O_TRUNC
26    | FUSE_BIG_WRITES;
27
28const DEFAULT_MAX_PAGES: u32 = 256;
29
30// Page size is currently hardcoded. While it could be determined from the OS, in the case of
31// virtio-fs it's not clear whether the host's or guest's page size should be used, if there's
32// a difference.
33const PAGE_SIZE: u32 = 4096;
34
35/// A FUSE session for a file system.
36///
37/// Handles negotiation and dispatching requests to the file system.
38pub struct Session {
39    fs: Box<dyn Fuse + Send + Sync>,
40    // Initialized provides a quick way to check if FUSE_INIT is expected without having to take
41    // a lock, since operations mostly don't need to access the SessionInfo.
42    initialized: atomic::AtomicBool,
43    info: RwLock<SessionInfo>,
44}
45
46impl Session {
47    /// Create a new `Session`.
48    pub fn new<T>(fs: T) -> Self
49    where
50        T: 'static + Fuse + Send + Sync,
51    {
52        Self {
53            fs: Box::new(fs),
54            initialized: atomic::AtomicBool::new(false),
55            info: RwLock::new(SessionInfo::default()),
56        }
57    }
58
59    /// Indicates whether the session has received an init request.
60    ///
61    /// Also returns `false` after the session received a destroy request.
62    pub fn is_initialized(&self) -> bool {
63        self.initialized.load(atomic::Ordering::Acquire)
64    }
65
66    /// Dispatch a FUSE request to the file system.
67    pub fn dispatch(
68        &self,
69        request: Request,
70        sender: &mut impl ReplySender,
71        mapper: Option<&dyn Mapper>,
72    ) {
73        let unique = request.unique();
74        let result = if self.is_initialized() {
75            self.dispatch_helper(request, sender, mapper)
76        } else {
77            self.dispatch_init(request, sender)
78        };
79
80        match result {
81            Err(OperationError::FsError(e)) => {
82                if let Err(e) = sender.send_error(unique, e.value()) {
83                    tracing::error!(
84                        unique,
85                        error = &e as &dyn std::error::Error,
86                        "Failed to send reply",
87                    );
88                }
89            }
90            Err(OperationError::SendError(e)) => {
91                if e.kind() == io::ErrorKind::NotFound {
92                    tracing::trace!(unique, "Request was interrupted.");
93                } else {
94                    tracing::error!(
95                        unique,
96                        error = &e as &dyn std::error::Error,
97                        "Failed to send reply",
98                    );
99                }
100            }
101            Ok(_) => (),
102        }
103    }
104
105    /// End the session.
106    ///
107    /// This puts the session in a state where it can accept another FUSE_INIT message. This allows
108    /// a virtiofs file system to be remounted after unmount.
109    ///
110    /// This invokes the file system's destroy callback if it hadn't been called already.
111    pub fn destroy(&self) {
112        if self.initialized.swap(false, atomic::Ordering::AcqRel) {
113            self.fs.destroy();
114        }
115    }
116
117    /// Perform the actual dispatch. This allows the caller to send an error reply if any operation
118    /// encounters an error.
119    fn dispatch_helper(
120        &self,
121        request: Request,
122        sender: &mut impl ReplySender,
123        mapper: Option<&dyn Mapper>,
124    ) -> Result<(), OperationError> {
125        request.log();
126
127        match request.operation() {
128            FuseOperation::Invalid => {
129                // This indicates the header could be parsed but the rest of the request could not,
130                // so send an error reply.
131                return Err(lx::Error::EIO.into());
132            }
133            FuseOperation::Lookup { name } => {
134                let out = self.fs.lookup(&request, name)?;
135                sender.send_arg(request.unique(), out)?;
136            }
137            FuseOperation::Forget { arg } => {
138                self.fs.forget(request.node_id(), arg.nlookup);
139            }
140            FuseOperation::GetAttr { arg } => {
141                let out = self.fs.get_attr(&request, arg.getattr_flags, arg.fh)?;
142                sender.send_arg(request.unique(), out)?;
143            }
144            FuseOperation::SetAttr { arg } => {
145                let out = self.fs.set_attr(&request, arg)?;
146                sender.send_arg(request.unique(), out)?;
147            }
148            FuseOperation::ReadLink {} => {
149                let out = self.fs.read_link(&request)?;
150                sender.send_string(request.unique(), out)?;
151            }
152            FuseOperation::Symlink { name, target } => {
153                let out = self.fs.symlink(&request, name, target)?;
154                sender.send_arg(request.unique(), out)?;
155            }
156            FuseOperation::MkNod { arg, name } => {
157                let out = self.fs.mknod(&request, name, arg)?;
158                sender.send_arg(request.unique(), out)?;
159            }
160            FuseOperation::MkDir { arg, name } => {
161                let out = self.fs.mkdir(&request, name, arg)?;
162                sender.send_arg(request.unique(), out)?;
163            }
164            FuseOperation::Unlink { name } => {
165                self.fs.unlink(&request, name)?;
166                sender.send_empty(request.unique())?;
167            }
168            FuseOperation::RmDir { name } => {
169                self.fs.rmdir(&request, name)?;
170                sender.send_empty(request.unique())?;
171            }
172            FuseOperation::Rename {
173                arg,
174                name,
175                new_name,
176            } => {
177                self.fs.rename(&request, name, arg.newdir, new_name, 0)?;
178
179                sender.send_empty(request.unique())?;
180            }
181            FuseOperation::Link { arg, name } => {
182                let out = self.fs.link(&request, name, arg.oldnodeid)?;
183                sender.send_arg(request.unique(), out)?;
184            }
185            FuseOperation::Open { arg } => {
186                let out = self.fs.open(&request, arg.flags)?;
187                self.send_release_if_interrupted(&request, sender, out.fh, arg.flags, out, false)?;
188            }
189            FuseOperation::Read { arg } => {
190                let out = self.fs.read(&request, arg)?;
191                Self::send_max_size(sender, request.unique(), &out, arg.size)?;
192            }
193            FuseOperation::Write { arg, data } => {
194                let out = self.fs.write(&request, arg, data)?;
195                sender.send_arg(
196                    request.unique(),
197                    fuse_write_out {
198                        size: out.try_into().unwrap(),
199                        padding: 0,
200                    },
201                )?;
202            }
203            FuseOperation::StatFs {} => {
204                let out = self.fs.statfs(&request)?;
205                sender.send_arg(request.unique(), fuse_statfs_out { st: out })?;
206            }
207            FuseOperation::Release { arg } => {
208                self.fs.release(&request, arg)?;
209                sender.send_empty(request.unique())?;
210            }
211            FuseOperation::FSync { arg } => {
212                self.fs.fsync(&request, arg.fh, arg.fsync_flags)?;
213                sender.send_empty(request.unique())?;
214            }
215            FuseOperation::SetXAttr { arg, name, value } => {
216                self.fs.set_xattr(&request, name, value, arg.flags)?;
217                sender.send_empty(request.unique())?;
218            }
219            FuseOperation::GetXAttr { arg, name } => {
220                if arg.size == 0 {
221                    let out = self.fs.get_xattr_size(&request, name)?;
222                    sender.send_arg(
223                        request.unique(),
224                        fuse_getxattr_out {
225                            size: out,
226                            padding: 0,
227                        },
228                    )?;
229                } else {
230                    let out = self.fs.get_xattr(&request, name, arg.size)?;
231                    Self::send_max_size(sender, request.unique(), &out, arg.size)?;
232                }
233            }
234            FuseOperation::ListXAttr { arg } => {
235                if arg.size == 0 {
236                    let out = self.fs.list_xattr_size(&request)?;
237                    sender.send_arg(
238                        request.unique(),
239                        fuse_getxattr_out {
240                            size: out,
241                            padding: 0,
242                        },
243                    )?;
244                } else {
245                    let out = self.fs.list_xattr(&request, arg.size)?;
246                    Self::send_max_size(sender, request.unique(), &out, arg.size)?;
247                }
248            }
249            FuseOperation::RemoveXAttr { name } => {
250                self.fs.remove_xattr(&request, name)?;
251                sender.send_empty(request.unique())?;
252            }
253            FuseOperation::Flush { arg } => {
254                self.fs.flush(&request, arg)?;
255                sender.send_empty(request.unique())?;
256            }
257            FuseOperation::Init { arg: _ } => {
258                tracing::warn!("Duplicate init message.");
259                return Err(lx::Error::EIO.into());
260            }
261            FuseOperation::OpenDir { arg } => {
262                let out = self.fs.open_dir(&request, arg.flags)?;
263                self.send_release_if_interrupted(&request, sender, out.fh, arg.flags, out, true)?;
264            }
265            FuseOperation::ReadDir { arg } => {
266                let out = self.fs.read_dir(&request, arg)?;
267                Self::send_max_size(sender, request.unique(), &out, arg.size)?;
268            }
269            FuseOperation::ReleaseDir { arg } => {
270                self.fs.release_dir(&request, arg)?;
271                sender.send_empty(request.unique())?;
272            }
273            FuseOperation::FSyncDir { arg } => {
274                self.fs.fsync_dir(&request, arg.fh, arg.fsync_flags)?;
275                sender.send_empty(request.unique())?;
276            }
277            FuseOperation::GetLock { arg } => {
278                let out = self.fs.get_lock(&request, arg)?;
279                sender.send_arg(request.unique(), out)?;
280            }
281            FuseOperation::SetLock { arg } => {
282                self.fs.set_lock(&request, arg, false)?;
283                sender.send_empty(request.unique())?;
284            }
285            FuseOperation::SetLockSleep { arg } => {
286                self.fs.set_lock(&request, arg, true)?;
287                sender.send_empty(request.unique())?;
288            }
289            FuseOperation::Access { arg } => {
290                self.fs.access(&request, arg.mask)?;
291                sender.send_empty(request.unique())?;
292            }
293            FuseOperation::Create { arg, name } => {
294                let out = self.fs.create(&request, name, arg)?;
295                self.send_release_if_interrupted(
296                    &request,
297                    sender,
298                    out.open.fh,
299                    arg.flags,
300                    out,
301                    false,
302                )?;
303            }
304            FuseOperation::Interrupt { arg: _ } => {
305                // Interrupt is potentially complicated, and none of the sample file systems seem
306                // to use it, so it's left as TODO for now.
307                tracing::warn!("FUSE_INTERRUPT not supported.");
308                return Err(lx::Error::ENOSYS.into());
309            }
310            FuseOperation::BMap { arg } => {
311                let out = self.fs.block_map(&request, arg.block, arg.blocksize)?;
312                sender.send_arg(request.unique(), fuse_bmap_out { block: out })?;
313            }
314            FuseOperation::Destroy {} => {
315                if let Some(mapper) = mapper {
316                    mapper.clear();
317                }
318                self.destroy();
319            }
320            FuseOperation::Ioctl { arg, data } => {
321                let out = self.fs.ioctl(&request, arg, data)?;
322                if out.1.len() > arg.out_size as usize {
323                    return Err(lx::Error::EINVAL.into());
324                }
325
326                // As far as I can tell, the fields other than result are only used for CUSE.
327                sender.send_arg_data(
328                    request.unique(),
329                    fuse_ioctl_out {
330                        result: out.0,
331                        flags: 0,
332                        in_iovs: 0,
333                        out_iovs: 0,
334                    },
335                    data,
336                )?;
337            }
338            FuseOperation::Poll { arg: _ } => {
339                // Poll is not currently needed, and complicated to support. It appears to have some
340                // way of registering for later notifications, but I can't figure out how that
341                // works without libfuse source.
342                tracing::warn!("FUSE_POLL not supported.");
343                return Err(lx::Error::ENOSYS.into());
344            }
345            FuseOperation::NotifyReply { arg: _, data: _ } => {
346                // Not sure what this is. It has something to do with poll, I think.
347                tracing::warn!("FUSE_NOTIFY_REPLY not supported.");
348                return Err(lx::Error::ENOSYS.into());
349            }
350            FuseOperation::BatchForget { arg, nodes } => {
351                self.batch_forget(arg.count, nodes);
352            }
353            FuseOperation::FAllocate { arg } => {
354                self.fs.fallocate(&request, arg)?;
355                sender.send_empty(request.unique())?;
356            }
357            FuseOperation::ReadDirPlus { arg } => {
358                let out = self.fs.read_dir_plus(&request, arg)?;
359                Self::send_max_size(sender, request.unique(), &out, arg.size)?;
360            }
361            FuseOperation::Rename2 {
362                arg,
363                name,
364                new_name,
365            } => {
366                self.fs
367                    .rename(&request, name, arg.newdir, new_name, arg.flags)?;
368
369                sender.send_empty(request.unique())?;
370            }
371            FuseOperation::LSeek { arg } => {
372                let out = self.fs.lseek(&request, arg.fh, arg.offset, arg.whence)?;
373                sender.send_arg(request.unique(), fuse_lseek_out { offset: out })?;
374            }
375            FuseOperation::CopyFileRange { arg } => {
376                let out = self.fs.copy_file_range(&request, arg)?;
377                sender.send_arg(
378                    request.unique(),
379                    fuse_write_out {
380                        size: out.try_into().unwrap(),
381                        padding: 0,
382                    },
383                )?;
384            }
385            FuseOperation::SetupMapping { arg } => {
386                if let Some(mapper) = mapper {
387                    self.fs.setup_mapping(&request, mapper, arg)?;
388                    sender.send_empty(request.unique())?;
389                } else {
390                    return Err(lx::Error::ENOSYS.into());
391                }
392            }
393            FuseOperation::RemoveMapping { arg, mappings } => {
394                if let Some(mapper) = mapper {
395                    self.remove_mapping(&request, mapper, arg.count, mappings)?;
396                    sender.send_empty(request.unique())?;
397                } else {
398                    return Err(lx::Error::ENOSYS.into());
399                }
400            }
401            FuseOperation::SyncFs { _arg } => {
402                // Rely on host file system to sync data
403                sender.send_empty(request.unique())?;
404            }
405            FuseOperation::CanonicalPath {} => {
406                // Android-specific opcode used to return a guest accessible
407                // path to the file location being proxied by the fuse
408                // implementation.
409                tracing::trace!("Unsupported opcode FUSE_CANONICAL_PATH");
410                sender.send_error(request.unique(), lx::Error::ENOSYS.value())?;
411            }
412        }
413
414        Ok(())
415    }
416
417    /// Dispatch the init message.
418    fn dispatch_init(
419        &self,
420        request: Request,
421        sender: &mut impl ReplySender,
422    ) -> Result<(), OperationError> {
423        request.log();
424        let init: &fuse_init_in = if let FuseOperation::Init { arg } = request.operation() {
425            arg
426        } else {
427            tracing::error!(opcode = request.opcode(), "Expected FUSE_INIT");
428            return Err(lx::Error::EIO.into());
429        };
430
431        let mut info = self.info.write();
432        if self.is_initialized() {
433            tracing::error!("Racy FUSE_INIT requests.");
434            return Err(lx::Error::EIO.into());
435        }
436
437        let mut out = fuse_init_out::new_zeroed();
438        out.major = FUSE_KERNEL_VERSION;
439        out.minor = FUSE_KERNEL_MINOR_VERSION;
440
441        // According to the docs, if the kernel reports a higher version, the response should have
442        // only the desired version set and the kernel will resend FUSE_INIT with that version.
443        if init.major > FUSE_KERNEL_VERSION {
444            sender.send_arg(request.unique(), out)?;
445            return Ok(());
446        }
447
448        // Don't bother supporting old versions. Version 7.27 is what kernel 4.19 uses, and can
449        // be supported without needing to change the daemon's behavior for compatibility.
450        if init.major < FUSE_KERNEL_VERSION || init.minor < 27 {
451            tracing::error!(
452                major = init.major,
453                minor = init.minor,
454                "Got unsupported kernel version",
455            );
456            return Err(lx::Error::EIO.into());
457        }
458
459        // Prepare the session info and call the file system to negotiate.
460        info.major = init.major;
461        info.minor = init.minor;
462        info.max_readahead = init.max_readahead;
463        info.capable = init.flags;
464        info.want = DEFAULT_FLAGS & init.flags;
465        info.time_gran = 1;
466        info.max_write = DEFAULT_MAX_PAGES * PAGE_SIZE;
467        self.fs.init(&mut info);
468
469        assert!(info.want & !info.capable == 0);
470
471        // Report the negotiated values back to the client.
472        // TODO: Set map_alignment for DAX.
473        out.max_readahead = info.max_readahead;
474        out.flags = info.want;
475        out.max_background = info.max_background;
476        out.congestion_threshold = info.congestion_threshold;
477        out.max_write = info.max_write;
478        out.time_gran = info.time_gran;
479        out.max_pages = ((info.max_write - 1) / PAGE_SIZE - 1).try_into().unwrap();
480
481        sender.send_arg(request.unique(), out)?;
482
483        // Indicate other requests can be received now.
484        self.initialized.store(true, atomic::Ordering::Release);
485        Ok(())
486    }
487
488    /// Send a reply and call the release method if the reply was interrupted.
489    fn send_release_if_interrupted<
490        TArg: zerocopy::IntoBytes + std::fmt::Debug + Immutable + KnownLayout,
491    >(
492        &self,
493        request: &Request,
494        sender: &mut impl ReplySender,
495        fh: u64,
496        flags: u32,
497        arg: TArg,
498        dir: bool,
499    ) -> lx::Result<()> {
500        if let Err(e) = sender.send_arg(request.unique(), arg) {
501            // ENOENT means the request was interrupted, and the kernel will not call
502            // release, so do it now.
503            if e.kind() == io::ErrorKind::NotFound {
504                let arg = fuse_release_in {
505                    fh,
506                    flags,
507                    release_flags: 0,
508                    lock_owner: 0,
509                };
510
511                if dir {
512                    self.fs.release_dir(request, &arg)?;
513                } else {
514                    self.fs.release(request, &arg)?;
515                }
516            } else {
517                return Err(e.into());
518            }
519        }
520
521        Ok(())
522    }
523
524    /// Send a reply, validating it doesn't exceed the requested size.
525    ///
526    /// If it exceeds the maximum size, this causes a panic because that's a bug in the file system.
527    fn send_max_size(
528        sender: &mut impl ReplySender,
529        unique: u64,
530        data: &[u8],
531        max_size: u32,
532    ) -> Result<(), OperationError> {
533        assert!(data.len() <= max_size as usize);
534        sender.send_data(unique, data)?;
535        Ok(())
536    }
537
538    /// Process `FUSE_BATCH_FORGET` by repeatedly calling `forget`.
539    fn batch_forget(&self, count: u32, mut nodes: &[u8]) {
540        for _ in 0..count {
541            let forget: fuse_forget_one = match nodes.read_type() {
542                Ok(f) => f,
543                Err(_) => break,
544            };
545
546            self.fs.forget(forget.nodeid, forget.nlookup);
547        }
548    }
549
550    /// Remove multiple DAX mappings.
551    fn remove_mapping(
552        &self,
553        request: &Request,
554        mapper: &dyn Mapper,
555        count: u32,
556        mut mappings: &[u8],
557    ) -> lx::Result<()> {
558        for _ in 0..count {
559            let mapping: fuse_removemapping_one = mappings.read_type()?;
560            self.fs
561                .remove_mapping(request, mapper, mapping.moffset, mapping.len)?;
562        }
563
564        Ok(())
565    }
566}
567
568/// Provides information about a session. Public fields may be modified during `init`.
569#[derive(Default)]
570pub struct SessionInfo {
571    major: u32,
572    minor: u32,
573    pub max_readahead: u32,
574    capable: u32,
575    pub want: u32,
576    pub max_background: u16,
577    pub congestion_threshold: u16,
578    pub max_write: u32,
579    pub time_gran: u32,
580}
581
582impl SessionInfo {
583    pub fn major(&self) -> u32 {
584        self.major
585    }
586
587    pub fn minor(&self) -> u32 {
588        self.minor
589    }
590
591    pub fn capable(&self) -> u32 {
592        self.capable
593    }
594}
595
596#[derive(Debug, Error)]
597enum OperationError {
598    #[error("File system error")]
599    FsError(#[from] lx::Error),
600    #[error("Send error")]
601    SendError(#[from] io::Error),
602}
603
604#[cfg(test)]
605mod tests {
606    use super::*;
607    use crate::request::tests::*;
608    use parking_lot::Mutex;
609    use std::sync::Arc;
610
611    #[test]
612    fn dispatch() {
613        let mut sender = MockSender::default();
614        let fs = TestFs::default();
615        let state = fs.state.clone();
616        let session = Session::new(fs);
617        assert!(!session.is_initialized());
618        let request = Request::new(FUSE_INIT_REQUEST).unwrap();
619        session.dispatch(request, &mut sender, None);
620        assert_eq!(state.lock().called, INIT_CALLED);
621        assert!(session.is_initialized());
622        session.dispatch(
623            Request::new(FUSE_GETATTR_REQUEST).unwrap(),
624            &mut sender,
625            None,
626        );
627        assert_eq!(state.lock().called, INIT_CALLED | GETATTR_CALLED);
628
629        session.dispatch(
630            Request::new(FUSE_LOOKUP_REQUEST).unwrap(),
631            &mut sender,
632            None,
633        );
634        assert_eq!(
635            state.lock().called,
636            INIT_CALLED | GETATTR_CALLED | LOOKUP_CALLED
637        );
638    }
639
640    #[derive(Default)]
641    struct State {
642        called: u32,
643    }
644
645    #[derive(Default)]
646    struct TestFs {
647        state: Arc<Mutex<State>>,
648    }
649
650    impl Fuse for TestFs {
651        fn init(&self, info: &mut SessionInfo) {
652            assert_eq!(self.state.lock().called & INIT_CALLED, 0);
653            assert_eq!(info.major(), 7);
654            assert_eq!(info.minor(), 27);
655            assert_eq!(info.capable(), 0x3FFFFB);
656            assert_eq!(info.want, 0xC9029);
657            assert_eq!(info.max_readahead, 131072);
658            assert_eq!(info.max_background, 0);
659            assert_eq!(info.max_write, 1048576);
660            assert_eq!(info.congestion_threshold, 0);
661            assert_eq!(info.time_gran, 1);
662            self.state.lock().called |= INIT_CALLED;
663        }
664
665        fn get_attr(&self, request: &Request, flags: u32, fh: u64) -> lx::Result<fuse_attr_out> {
666            assert_eq!(self.state.lock().called & GETATTR_CALLED, 0);
667            assert_eq!(request.node_id(), 1);
668            assert_eq!(flags, 0);
669            assert_eq!(fh, 0);
670            let mut attr = fuse_attr_out::new_zeroed();
671            attr.attr.ino = 1;
672            attr.attr.mode = lx::S_IFDIR | 0o755;
673            attr.attr.nlink = 2;
674            attr.attr_valid = 1;
675            self.state.lock().called |= GETATTR_CALLED;
676            Ok(attr)
677        }
678
679        fn lookup(&self, request: &Request, name: &lx::LxStr) -> lx::Result<fuse_entry_out> {
680            assert_eq!(self.state.lock().called & LOOKUP_CALLED, 0);
681            assert_eq!(request.node_id(), 1);
682            assert_eq!(name, "hello");
683            self.state.lock().called |= LOOKUP_CALLED;
684            let mut attr = fuse_attr::new_zeroed();
685            attr.ino = 2;
686            attr.mode = lx::S_IFREG | 0o644;
687            attr.nlink = 1;
688            attr.size = 13;
689            Ok(fuse_entry_out {
690                nodeid: 2,
691                generation: 0,
692                entry_valid: 1,
693                entry_valid_nsec: 0,
694                attr_valid: 1,
695                attr_valid_nsec: 0,
696                attr,
697            })
698        }
699    }
700
701    #[derive(Default)]
702    struct MockSender {
703        state: u32,
704    }
705
706    impl ReplySender for MockSender {
707        fn send(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<()> {
708            let flat: Vec<u8> = bufs.iter().flat_map(|s| s.iter()).copied().collect();
709            match self.state {
710                0 => assert_eq!(flat, INIT_REPLY),
711                1 => assert_eq!(flat, GETATTR_REPLY),
712                2 => assert_eq!(flat, LOOKUP_REPLY),
713                _ => panic!("Unexpected send."),
714            }
715
716            self.state += 1;
717            Ok(())
718        }
719    }
720
721    const INIT_CALLED: u32 = 0x1;
722    const GETATTR_CALLED: u32 = 0x2;
723    const LOOKUP_CALLED: u32 = 0x4;
724
725    const INIT_REPLY: &[u8] = &[
726        80, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 31, 0, 0, 0, 0, 0, 2, 0, 41,
727        144, 12, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 0, 0, 0, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
728        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
729    ];
730
731    const GETATTR_REPLY: &[u8] = &[
732        120, 0, 0, 0, 0, 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,
733        0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
734        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
735        0, 0, 237, 65, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
736        0,
737    ];
738
739    const LOOKUP_REPLY: &[u8] = &[
740        144, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
741        0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0,
742        0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
743        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 0,
744        0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
745    ];
746}