summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--devices/Cargo.toml1
-rw-r--r--devices/src/virtio/fs/server.rs1295
3 files changed, 1297 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 558de5a..2b03635 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -164,6 +164,7 @@ dependencies = [
  "resources 0.1.0",
  "sync 0.1.0",
  "sys_util 0.1.0",
+ "syscall_defines 0.1.0",
  "tempfile 3.0.7",
  "tpm2 0.1.0",
  "usb_util 0.1.0",
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 18a203a..7ea90b5 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -34,6 +34,7 @@ remain = "*"
 resources = { path = "../resources" }
 sync = { path = "../sync" }
 sys_util = { path = "../sys_util" }
+syscall_defines = { path = "../syscall_defines" }
 tpm2 = { path = "../tpm2", optional = true }
 usb_util = { path = "../usb_util" }
 vfio_sys = { path = "../vfio_sys" }
diff --git a/devices/src/virtio/fs/server.rs b/devices/src/virtio/fs/server.rs
new file mode 100644
index 0000000..3c1c7e7
--- /dev/null
+++ b/devices/src/virtio/fs/server.rs
@@ -0,0 +1,1295 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::ffi::CStr;
+use std::fs::File;
+use std::io::{self, Read, Write};
+use std::mem::size_of;
+
+use data_model::DataInit;
+use libc;
+use sys_util::error;
+
+use crate::virtio::fs::filesystem::{
+    Context, DirEntry, Entry, FileSystem, GetxattrReply, ListxattrReply, ZeroCopyReader,
+    ZeroCopyWriter,
+};
+use crate::virtio::fs::fuse::*;
+use crate::virtio::fs::{Error, Result};
+use crate::virtio::{Reader, Writer};
+
+const MAX_BUFFER_SIZE: u32 = (1 << 20);
+const DIRENT_PADDING: [u8; 8] = [0; 8];
+
+struct ZCReader<'a>(Reader<'a>);
+
+impl<'a> ZeroCopyReader for ZCReader<'a> {
+    fn read_to(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
+        self.0.read_to_at(f, count, off)
+    }
+}
+
+impl<'a> io::Read for ZCReader<'a> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.0.read(buf)
+    }
+}
+
+struct ZCWriter<'a>(Writer<'a>);
+
+impl<'a> ZeroCopyWriter for ZCWriter<'a> {
+    fn write_from(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
+        self.0.write_from_at(f, count, off)
+    }
+}
+
+impl<'a> io::Write for ZCWriter<'a> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.0.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.0.flush()
+    }
+}
+
+pub struct Server<F: FileSystem + Sync> {
+    fs: F,
+}
+
+impl<F: FileSystem + Sync> Server<F> {
+    pub fn new(fs: F) -> Server<F> {
+        Server { fs }
+    }
+
+    pub fn handle_message(&self, mut r: Reader, w: Writer) -> Result<usize> {
+        let in_header: InHeader = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        if in_header.len > MAX_BUFFER_SIZE {
+            return reply_error(
+                io::Error::from_raw_os_error(libc::ENOMEM),
+                in_header.unique,
+                w,
+            );
+        }
+        match Opcode::n(in_header.opcode) {
+            Some(Opcode::Lookup) => self.lookup(in_header, r, w),
+            Some(Opcode::Forget) => self.forget(in_header, r), // No reply.
+            Some(Opcode::Getattr) => self.getattr(in_header, r, w),
+            Some(Opcode::Setattr) => self.setattr(in_header, r, w),
+            Some(Opcode::Readlink) => self.readlink(in_header, w),
+            Some(Opcode::Symlink) => self.symlink(in_header, r, w),
+            Some(Opcode::Mknod) => self.mknod(in_header, r, w),
+            Some(Opcode::Mkdir) => self.mkdir(in_header, r, w),
+            Some(Opcode::Unlink) => self.unlink(in_header, r, w),
+            Some(Opcode::Rmdir) => self.rmdir(in_header, r, w),
+            Some(Opcode::Rename) => self.rename(in_header, r, w),
+            Some(Opcode::Link) => self.link(in_header, r, w),
+            Some(Opcode::Open) => self.open(in_header, r, w),
+            Some(Opcode::Read) => self.read(in_header, r, w),
+            Some(Opcode::Write) => self.write(in_header, r, w),
+            Some(Opcode::Statfs) => self.statfs(in_header, w),
+            Some(Opcode::Release) => self.release(in_header, r, w),
+            Some(Opcode::Fsync) => self.fsync(in_header, r, w),
+            Some(Opcode::Setxattr) => self.setxattr(in_header, r, w),
+            Some(Opcode::Getxattr) => self.getxattr(in_header, r, w),
+            Some(Opcode::Listxattr) => self.listxattr(in_header, r, w),
+            Some(Opcode::Removexattr) => self.removexattr(in_header, r, w),
+            Some(Opcode::Flush) => self.flush(in_header, r, w),
+            Some(Opcode::Init) => self.init(in_header, r, w),
+            Some(Opcode::Opendir) => self.opendir(in_header, r, w),
+            Some(Opcode::Readdir) => self.readdir(in_header, r, w),
+            Some(Opcode::Releasedir) => self.releasedir(in_header, r, w),
+            Some(Opcode::Fsyncdir) => self.fsyncdir(in_header, r, w),
+            Some(Opcode::Getlk) => self.getlk(in_header, r, w),
+            Some(Opcode::Setlk) => self.setlk(in_header, r, w),
+            Some(Opcode::Setlkw) => self.setlkw(in_header, r, w),
+            Some(Opcode::Access) => self.access(in_header, r, w),
+            Some(Opcode::Create) => self.create(in_header, r, w),
+            Some(Opcode::Interrupt) => self.interrupt(in_header),
+            Some(Opcode::Bmap) => self.bmap(in_header, r, w),
+            Some(Opcode::Destroy) => self.destroy(),
+            Some(Opcode::Ioctl) => self.ioctl(in_header, r, w),
+            Some(Opcode::Poll) => self.poll(in_header, r, w),
+            Some(Opcode::NotifyReply) => self.notify_reply(in_header, r, w),
+            Some(Opcode::BatchForget) => self.batch_forget(in_header, r, w),
+            Some(Opcode::Fallocate) => self.fallocate(in_header, r, w),
+            Some(Opcode::Readdirplus) => self.readdirplus(in_header, r, w),
+            Some(Opcode::Rename2) => self.rename2(in_header, r, w),
+            Some(Opcode::Lseek) => self.lseek(in_header, r, w),
+            None => reply_error(
+                io::Error::from_raw_os_error(libc::ENOSYS),
+                in_header.unique,
+                w,
+            ),
+        }
+    }
+
+    fn lookup(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .ok_or(Error::InvalidHeaderLength)?;
+
+        let mut buf = Vec::with_capacity(namelen);
+        buf.resize(namelen, 0);
+
+        r.read_exact(&mut buf).map_err(Error::DecodeMessage)?;
+
+        let name = bytes_to_cstr(&buf)?;
+
+        match self
+            .fs
+            .lookup(Context::from(in_header), in_header.nodeid.into(), &name)
+        {
+            Ok(entry) => {
+                let out = EntryOut::from(entry);
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn forget(&self, in_header: InHeader, mut r: Reader) -> Result<usize> {
+        let ForgetIn { nlookup } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        self.fs
+            .forget(Context::from(in_header), in_header.nodeid.into(), nlookup);
+
+        // There is no reply for forget messages.
+        Ok(0)
+    }
+
+    fn getattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let GetattrIn {
+            flags,
+            dummy: _,
+            fh,
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let handle = if (flags & GETATTR_FH) != 0 {
+            Some(fh.into())
+        } else {
+            None
+        };
+
+        match self
+            .fs
+            .getattr(Context::from(in_header), in_header.nodeid.into(), handle)
+        {
+            Ok((st, timeout)) => {
+                let out = AttrOut {
+                    attr_valid: timeout.as_secs(),
+                    attr_valid_nsec: timeout.subsec_nanos(),
+                    dummy: 0,
+                    attr: st.into(),
+                };
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn setattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let setattr_in: SetattrIn = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let handle = if setattr_in.valid & FATTR_FH != 0 {
+            Some(setattr_in.fh.into())
+        } else {
+            None
+        };
+
+        let valid = SetattrValid::from_bits_truncate(setattr_in.valid);
+
+        let st: libc::stat64 = setattr_in.into();
+
+        match self.fs.setattr(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            st,
+            handle,
+            valid,
+        ) {
+            Ok((st, timeout)) => {
+                let out = AttrOut {
+                    attr_valid: timeout.as_secs(),
+                    attr_valid_nsec: timeout.subsec_nanos(),
+                    dummy: 0,
+                    attr: st.into(),
+                };
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn readlink(&self, in_header: InHeader, w: Writer) -> Result<usize> {
+        match self
+            .fs
+            .readlink(Context::from(in_header), in_header.nodeid.into())
+        {
+            Ok(linkname) => {
+                // We need to disambiguate the option type here even though it is `None`.
+                reply_ok(None::<u8>, Some(&linkname), in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn symlink(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        // Unfortunately the name and linkname are encoded one after another and
+        // separated by a nul character.
+        let len = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut buf = Vec::with_capacity(len);
+        buf.resize(len, 0);
+
+        r.read_exact(&mut buf).map_err(Error::DecodeMessage)?;
+
+        // We want to include the '\0' byte in the first slice.
+        let split_pos = buf
+            .iter()
+            .position(|c| *c == b'\0')
+            .map(|p| p + 1)
+            .ok_or(Error::MissingParameter)?;
+
+        let (name, linkname) = buf.split_at(split_pos);
+
+        match self.fs.symlink(
+            Context::from(in_header),
+            bytes_to_cstr(linkname)?,
+            in_header.nodeid.into(),
+            bytes_to_cstr(name)?,
+        ) {
+            Ok(entry) => {
+                let out = EntryOut::from(entry);
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn mknod(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let MknodIn {
+            mode, rdev, umask, ..
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .and_then(|l| l.checked_sub(size_of::<MknodIn>()))
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut name = Vec::with_capacity(namelen);
+        name.resize(namelen, 0);
+
+        r.read_exact(&mut name).map_err(Error::DecodeMessage)?;
+
+        match self.fs.mknod(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            bytes_to_cstr(&name)?,
+            mode,
+            rdev,
+            umask,
+        ) {
+            Ok(entry) => {
+                let out = EntryOut::from(entry);
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn mkdir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let MkdirIn { mode, umask } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .and_then(|l| l.checked_sub(size_of::<MkdirIn>()))
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut name = Vec::with_capacity(namelen);
+        name.resize(namelen, 0);
+
+        r.read_exact(&mut name).map_err(Error::DecodeMessage)?;
+
+        match self.fs.mkdir(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            bytes_to_cstr(&name)?,
+            mode,
+            umask,
+        ) {
+            Ok(entry) => {
+                let out = EntryOut::from(entry);
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn unlink(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut name = Vec::with_capacity(namelen);
+        name.resize(namelen, 0);
+
+        r.read_exact(&mut name).map_err(Error::DecodeMessage)?;
+
+        match self.fs.unlink(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            bytes_to_cstr(&name)?,
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn rmdir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut name = Vec::with_capacity(namelen);
+        name.resize(namelen, 0);
+
+        r.read_exact(&mut name).map_err(Error::DecodeMessage)?;
+
+        match self.fs.rmdir(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            bytes_to_cstr(&name)?,
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn do_rename(
+        &self,
+        in_header: InHeader,
+        msg_size: usize,
+        newdir: u64,
+        flags: u32,
+        mut r: Reader,
+        w: Writer,
+    ) -> Result<usize> {
+        let buflen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .and_then(|l| l.checked_sub(msg_size))
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut buf = Vec::with_capacity(buflen);
+        buf.resize(buflen, 0);
+
+        r.read_exact(&mut buf).map_err(Error::DecodeMessage)?;
+
+        // We want to include the '\0' byte in the first slice.
+        let split_pos = buf
+            .iter()
+            .position(|c| *c == b'\0')
+            .map(|p| p + 1)
+            .ok_or(Error::MissingParameter)?;
+
+        let (oldname, newname) = buf.split_at(split_pos);
+
+        match self.fs.rename(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            bytes_to_cstr(oldname)?,
+            newdir.into(),
+            bytes_to_cstr(newname)?,
+            flags,
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn rename(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let RenameIn { newdir } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        self.do_rename(in_header, size_of::<RenameIn>(), newdir, 0, r, w)
+    }
+
+    fn rename2(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let Rename2In { newdir, flags, .. } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let flags = flags & (libc::RENAME_EXCHANGE | libc::RENAME_NOREPLACE) as u32;
+
+        self.do_rename(in_header, size_of::<Rename2In>(), newdir, flags, r, w)
+    }
+
+    fn link(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let LinkIn { oldnodeid } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .and_then(|l| l.checked_sub(size_of::<LinkIn>()))
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut name = Vec::with_capacity(namelen);
+        name.resize(namelen, 0);
+
+        r.read_exact(&mut name).map_err(Error::DecodeMessage)?;
+
+        match self.fs.link(
+            Context::from(in_header),
+            oldnodeid.into(),
+            in_header.nodeid.into(),
+            bytes_to_cstr(&name)?,
+        ) {
+            Ok(entry) => {
+                let out = EntryOut::from(entry);
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn open(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let OpenIn { flags, .. } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        match self
+            .fs
+            .open(Context::from(in_header), in_header.nodeid.into(), flags)
+        {
+            Ok((handle, opts)) => {
+                let out = OpenOut {
+                    fh: handle.map(Into::into).unwrap_or(0),
+                    open_flags: opts.bits(),
+                    ..Default::default()
+                };
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn read(&self, in_header: InHeader, mut r: Reader, mut w: Writer) -> Result<usize> {
+        let ReadIn {
+            fh,
+            offset,
+            size,
+            read_flags,
+            lock_owner,
+            flags,
+            ..
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        if size > MAX_BUFFER_SIZE {
+            return reply_error(
+                io::Error::from_raw_os_error(libc::ENOMEM),
+                in_header.unique,
+                w,
+            );
+        }
+
+        let owner = if read_flags & READ_LOCKOWNER != 0 {
+            Some(lock_owner)
+        } else {
+            None
+        };
+
+        // Split the writer into 2 pieces: one for the `OutHeader` and the rest for the data.
+        let data_writer = ZCWriter(
+            w.split_at(size_of::<OutHeader>())
+                .map_err(Error::InvalidDescriptorChain)?,
+        );
+
+        match self.fs.read(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            fh.into(),
+            data_writer,
+            size,
+            offset,
+            owner,
+            flags,
+        ) {
+            Ok(count) => {
+                // Don't use `reply_ok` because we need to set a custom size length for the
+                // header.
+                let out = OutHeader {
+                    len: (size_of::<OutHeader>() + count) as u32,
+                    error: 0,
+                    unique: in_header.unique,
+                };
+
+                w.write_all(out.as_slice()).map_err(Error::EncodeMessage)?;
+                Ok(out.len as usize)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn write(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let WriteIn {
+            fh,
+            offset,
+            size,
+            write_flags,
+            lock_owner,
+            flags,
+            ..
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        if size > MAX_BUFFER_SIZE {
+            return reply_error(
+                io::Error::from_raw_os_error(libc::ENOMEM),
+                in_header.unique,
+                w,
+            );
+        }
+
+        let owner = if write_flags & WRITE_LOCKOWNER != 0 {
+            Some(lock_owner)
+        } else {
+            None
+        };
+
+        let delayed_write = write_flags & WRITE_CACHE != 0;
+
+        let data_reader = ZCReader(r);
+
+        match self.fs.write(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            fh.into(),
+            data_reader,
+            size,
+            offset,
+            owner,
+            delayed_write,
+            flags,
+        ) {
+            Ok(count) => {
+                let out = WriteOut {
+                    size: count as u32,
+                    ..Default::default()
+                };
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn statfs(&self, in_header: InHeader, w: Writer) -> Result<usize> {
+        match self
+            .fs
+            .statfs(Context::from(in_header), in_header.nodeid.into())
+        {
+            Ok(st) => reply_ok(Some(Kstatfs::from(st)), None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn release(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let ReleaseIn {
+            fh,
+            flags,
+            release_flags,
+            lock_owner,
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let flush = release_flags & RELEASE_FLUSH != 0;
+        let flock_release = release_flags & RELEASE_FLOCK_UNLOCK != 0;
+        let lock_owner = if flush || flock_release {
+            Some(lock_owner)
+        } else {
+            None
+        };
+
+        match self.fs.release(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            flags,
+            fh.into(),
+            flush,
+            flock_release,
+            lock_owner,
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn fsync(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let FsyncIn {
+            fh, fsync_flags, ..
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+        let datasync = fsync_flags & 0x1 != 0;
+
+        match self.fs.fsync(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            datasync,
+            fh.into(),
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn setxattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let SetxattrIn { size, flags } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        // The name and value and encoded one after another and separated by a '\0' character.
+        let len = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .and_then(|l| l.checked_sub(size_of::<SetxattrIn>()))
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut buf = Vec::with_capacity(len);
+        buf.resize(len, 0);
+
+        r.read_exact(&mut buf).map_err(Error::DecodeMessage)?;
+
+        // We want to include the '\0' byte in the first slice.
+        let split_pos = buf
+            .iter()
+            .position(|c| *c == b'\0')
+            .map(|p| p + 1)
+            .ok_or(Error::MissingParameter)?;
+
+        let (name, value) = buf.split_at(split_pos);
+
+        if size != value.len() as u32 {
+            return Err(Error::InvalidXattrSize((size, value.len())));
+        }
+
+        match self.fs.setxattr(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            bytes_to_cstr(name)?,
+            value,
+            flags,
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn getxattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let GetxattrIn { size, .. } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .and_then(|l| l.checked_sub(size_of::<GetxattrIn>()))
+            .ok_or(Error::InvalidHeaderLength)?;
+        let mut name = Vec::with_capacity(namelen);
+        name.resize(namelen, 0);
+
+        r.read_exact(&mut name).map_err(Error::DecodeMessage)?;
+
+        if size > MAX_BUFFER_SIZE {
+            return reply_error(
+                io::Error::from_raw_os_error(libc::ENOMEM),
+                in_header.unique,
+                w,
+            );
+        }
+
+        match self.fs.getxattr(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            bytes_to_cstr(&name)?,
+            size,
+        ) {
+            Ok(GetxattrReply::Value(val)) => reply_ok(None::<u8>, Some(&val), in_header.unique, w),
+            Ok(GetxattrReply::Count(count)) => {
+                let out = GetxattrOut {
+                    size: count,
+                    ..Default::default()
+                };
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn listxattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let GetxattrIn { size, .. } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        if size > MAX_BUFFER_SIZE {
+            return reply_error(
+                io::Error::from_raw_os_error(libc::ENOMEM),
+                in_header.unique,
+                w,
+            );
+        }
+
+        match self
+            .fs
+            .listxattr(Context::from(in_header), in_header.nodeid.into(), size)
+        {
+            Ok(ListxattrReply::Names(val)) => reply_ok(None::<u8>, Some(&val), in_header.unique, w),
+            Ok(ListxattrReply::Count(count)) => {
+                let out = GetxattrOut {
+                    size: count,
+                    ..Default::default()
+                };
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn removexattr(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .ok_or(Error::InvalidHeaderLength)?;
+
+        let mut buf = Vec::with_capacity(namelen);
+        buf.resize(namelen, 0);
+
+        r.read_exact(&mut buf).map_err(Error::DecodeMessage)?;
+
+        let name = bytes_to_cstr(&buf)?;
+
+        match self
+            .fs
+            .removexattr(Context::from(in_header), in_header.nodeid.into(), name)
+        {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn flush(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let FlushIn {
+            fh,
+            unused: _,
+            padding: _,
+            lock_owner,
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        match self.fs.flush(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            fh.into(),
+            lock_owner,
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn init(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let InitIn {
+            major,
+            minor,
+            max_readahead,
+            flags,
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        if major < KERNEL_VERSION {
+            error!("Unsupported fuse protocol version: {}.{}", major, minor);
+            return reply_error(
+                io::Error::from_raw_os_error(libc::EPROTO),
+                in_header.unique,
+                w,
+            );
+        }
+
+        if major > KERNEL_VERSION {
+            // Wait for the kernel to reply back with a 7.X version.
+            let out = InitOut {
+                major: KERNEL_VERSION,
+                minor: KERNEL_MINOR_VERSION,
+                ..Default::default()
+            };
+
+            return reply_ok(Some(out), None, in_header.unique, w);
+        }
+
+        if minor < KERNEL_MINOR_VERSION {
+            error!(
+                "Unsupported fuse protocol minor version: {}.{}",
+                major, minor
+            );
+            return reply_error(
+                io::Error::from_raw_os_error(libc::EPROTO),
+                in_header.unique,
+                w,
+            );
+        }
+
+        // These fuse features are supported by this server by default.
+        let supported = FsOptions::ASYNC_READ
+            | FsOptions::PARALLEL_DIROPS
+            | FsOptions::BIG_WRITES
+            | FsOptions::AUTO_INVAL_DATA
+            | FsOptions::HANDLE_KILLPRIV
+            | FsOptions::ASYNC_DIO
+            | FsOptions::HAS_IOCTL_DIR
+            | FsOptions::ATOMIC_O_TRUNC;
+
+        let capable = FsOptions::from_bits_truncate(flags);
+
+        match self.fs.init(capable) {
+            Ok(want) => {
+                let enabled = capable & (want | supported);
+
+                let out = InitOut {
+                    major: KERNEL_VERSION,
+                    minor: KERNEL_MINOR_VERSION,
+                    max_readahead,
+                    flags: enabled.bits(),
+                    max_background: ::std::u16::MAX,
+                    congestion_threshold: (::std::u16::MAX / 4) * 3,
+                    max_write: MAX_BUFFER_SIZE,
+                    time_gran: 1, // nanoseconds
+                    ..Default::default()
+                };
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn opendir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let OpenIn { flags, .. } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        match self
+            .fs
+            .opendir(Context::from(in_header), in_header.nodeid.into(), flags)
+        {
+            Ok((handle, opts)) => {
+                let out = OpenOut {
+                    fh: handle.map(Into::into).unwrap_or(0),
+                    open_flags: opts.bits(),
+                    ..Default::default()
+                };
+
+                reply_ok(Some(out), None, in_header.unique, w)
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn do_readdir(
+        &self,
+        in_header: InHeader,
+        mut r: Reader,
+        mut w: Writer,
+        plus: bool,
+    ) -> Result<usize> {
+        let ReadIn {
+            fh, offset, size, ..
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        if size > MAX_BUFFER_SIZE {
+            return reply_error(
+                io::Error::from_raw_os_error(libc::ENOMEM),
+                in_header.unique,
+                w,
+            );
+        }
+
+        let available_bytes = w.available_bytes().map_err(Error::InvalidDescriptorChain)?;
+        if available_bytes < size as usize {
+            return reply_error(
+                io::Error::from_raw_os_error(libc::ENOMEM),
+                in_header.unique,
+                w,
+            );
+        }
+
+        // Skip over enough bytes for the header.
+        let mut cursor = w
+            .split_at(size_of::<OutHeader>())
+            .map_err(Error::InvalidDescriptorChain)?;
+
+        let res = if plus {
+            self.fs.readdirplus(
+                Context::from(in_header),
+                in_header.nodeid.into(),
+                fh.into(),
+                size,
+                offset,
+                |d, e| add_dirent(&mut cursor, size, d, Some(e)),
+            )
+        } else {
+            self.fs.readdir(
+                Context::from(in_header),
+                in_header.nodeid.into(),
+                fh.into(),
+                size,
+                offset,
+                |d| add_dirent(&mut cursor, size, d, None),
+            )
+        };
+
+        if let Err(e) = res {
+            reply_error(e, in_header.unique, w)
+        } else {
+            // Don't use `reply_ok` because we need to set a custom size length for the
+            // header.
+            let out = OutHeader {
+                len: (size_of::<OutHeader>() + cursor.bytes_written()) as u32,
+                error: 0,
+                unique: in_header.unique,
+            };
+
+            w.write_all(out.as_slice()).map_err(Error::EncodeMessage)?;
+            Ok(out.len as usize)
+        }
+    }
+
+    fn readdir(&self, in_header: InHeader, r: Reader, w: Writer) -> Result<usize> {
+        self.do_readdir(in_header, r, w, false)
+    }
+
+    fn readdirplus(&self, in_header: InHeader, r: Reader, w: Writer) -> Result<usize> {
+        self.do_readdir(in_header, r, w, true)
+    }
+
+    fn releasedir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let ReleaseIn { fh, flags, .. } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        match self.fs.releasedir(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            flags,
+            fh.into(),
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn fsyncdir(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let FsyncIn {
+            fh, fsync_flags, ..
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+        let datasync = fsync_flags & 0x1 != 0;
+
+        match self.fs.fsyncdir(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            datasync,
+            fh.into(),
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn getlk(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result<usize> {
+        if let Err(e) = self.fs.getlk() {
+            reply_error(e, in_header.unique, w)
+        } else {
+            Ok(0)
+        }
+    }
+
+    fn setlk(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result<usize> {
+        if let Err(e) = self.fs.setlk() {
+            reply_error(e, in_header.unique, w)
+        } else {
+            Ok(0)
+        }
+    }
+
+    fn setlkw(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result<usize> {
+        if let Err(e) = self.fs.setlkw() {
+            reply_error(e, in_header.unique, w)
+        } else {
+            Ok(0)
+        }
+    }
+
+    fn access(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let AccessIn { mask, .. } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        match self
+            .fs
+            .access(Context::from(in_header), in_header.nodeid.into(), mask)
+        {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn create(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let CreateIn {
+            flags, mode, umask, ..
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        let namelen = (in_header.len as usize)
+            .checked_sub(size_of::<InHeader>())
+            .and_then(|l| l.checked_sub(size_of::<CreateIn>()))
+            .ok_or(Error::InvalidHeaderLength)?;
+
+        let mut buf = Vec::with_capacity(namelen);
+        buf.resize(namelen, 0);
+
+        r.read_exact(&mut buf).map_err(Error::DecodeMessage)?;
+
+        let name = bytes_to_cstr(&buf)?;
+
+        match self.fs.create(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            name,
+            mode,
+            flags,
+            umask,
+        ) {
+            Ok((entry, handle, opts)) => {
+                let entry_out = EntryOut {
+                    nodeid: entry.inode,
+                    generation: entry.generation,
+                    entry_valid: entry.entry_timeout.as_secs(),
+                    attr_valid: entry.attr_timeout.as_secs(),
+                    entry_valid_nsec: entry.entry_timeout.subsec_nanos(),
+                    attr_valid_nsec: entry.attr_timeout.subsec_nanos(),
+                    attr: entry.attr.into(),
+                };
+                let open_out = OpenOut {
+                    fh: handle.map(Into::into).unwrap_or(0),
+                    open_flags: opts.bits(),
+                    ..Default::default()
+                };
+
+                // Kind of a hack to write both structs.
+                reply_ok(
+                    Some(entry_out),
+                    Some(open_out.as_slice()),
+                    in_header.unique,
+                    w,
+                )
+            }
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn interrupt(&self, _in_header: InHeader) -> Result<usize> {
+        Ok(0)
+    }
+
+    fn bmap(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result<usize> {
+        if let Err(e) = self.fs.bmap() {
+            reply_error(e, in_header.unique, w)
+        } else {
+            Ok(0)
+        }
+    }
+
+    fn destroy(&self) -> Result<usize> {
+        // No reply to this function.
+        self.fs.destroy();
+
+        Ok(0)
+    }
+
+    fn ioctl(&self, in_header: InHeader, _r: Reader, w: Writer) -> Result<usize> {
+        if let Err(e) = self.fs.ioctl() {
+            reply_error(e, in_header.unique, w)
+        } else {
+            Ok(0)
+        }
+    }
+
+    fn poll(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result<usize> {
+        if let Err(e) = self.fs.poll() {
+            reply_error(e, in_header.unique, w)
+        } else {
+            Ok(0)
+        }
+    }
+
+    fn notify_reply(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result<usize> {
+        if let Err(e) = self.fs.notify_reply() {
+            reply_error(e, in_header.unique, w)
+        } else {
+            Ok(0)
+        }
+    }
+
+    fn batch_forget(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let BatchForgetIn { count, .. } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        if let Some(size) = (count as usize).checked_mul(size_of::<ForgetOne>()) {
+            if size > MAX_BUFFER_SIZE as usize {
+                return reply_error(
+                    io::Error::from_raw_os_error(libc::ENOMEM),
+                    in_header.unique,
+                    w,
+                );
+            }
+        } else {
+            return reply_error(
+                io::Error::from_raw_os_error(libc::EOVERFLOW),
+                in_header.unique,
+                w,
+            );
+        }
+
+        let mut requests = Vec::with_capacity(count as usize);
+        for _ in 0..count {
+            requests.push(
+                r.read_obj::<ForgetOne>()
+                    .map(|f| (f.nodeid.into(), f.nlookup))
+                    .map_err(Error::DecodeMessage)?,
+            );
+        }
+
+        self.fs.batch_forget(Context::from(in_header), requests);
+
+        // No reply for forget messages.
+        Ok(0)
+    }
+
+    fn fallocate(&self, in_header: InHeader, mut r: Reader, w: Writer) -> Result<usize> {
+        let FallocateIn {
+            fh,
+            offset,
+            length,
+            mode,
+            ..
+        } = r.read_obj().map_err(Error::DecodeMessage)?;
+
+        match self.fs.fallocate(
+            Context::from(in_header),
+            in_header.nodeid.into(),
+            fh.into(),
+            mode,
+            offset,
+            length,
+        ) {
+            Ok(()) => reply_ok(None::<u8>, None, in_header.unique, w),
+            Err(e) => reply_error(e, in_header.unique, w),
+        }
+    }
+
+    fn lseek(&self, in_header: InHeader, mut _r: Reader, w: Writer) -> Result<usize> {
+        if let Err(e) = self.fs.lseek() {
+            reply_error(e, in_header.unique, w)
+        } else {
+            Ok(0)
+        }
+    }
+}
+
+fn reply_ok<T: DataInit>(
+    out: Option<T>,
+    data: Option<&[u8]>,
+    unique: u64,
+    mut w: Writer,
+) -> Result<usize> {
+    let mut len = size_of::<OutHeader>();
+
+    if out.is_some() {
+        len += size_of::<T>();
+    }
+
+    if let Some(ref data) = data {
+        len += data.len();
+    }
+
+    let header = OutHeader {
+        len: len as u32,
+        error: 0,
+        unique,
+    };
+
+    w.write_all(header.as_slice())
+        .map_err(Error::EncodeMessage)?;
+
+    if let Some(out) = out {
+        w.write_all(out.as_slice()).map_err(Error::EncodeMessage)?;
+    }
+
+    if let Some(data) = data {
+        w.write_all(data).map_err(Error::EncodeMessage)?;
+    }
+
+    debug_assert_eq!(len, w.bytes_written());
+    Ok(w.bytes_written())
+}
+
+fn reply_error(e: io::Error, unique: u64, mut w: Writer) -> Result<usize> {
+    let header = OutHeader {
+        len: size_of::<OutHeader>() as u32,
+        error: -e.raw_os_error().unwrap_or(libc::EIO),
+        unique,
+    };
+
+    w.write_all(header.as_slice())
+        .map_err(Error::EncodeMessage)?;
+
+    debug_assert_eq!(header.len as usize, w.bytes_written());
+    Ok(w.bytes_written())
+}
+
+fn bytes_to_cstr(buf: &[u8]) -> Result<&CStr> {
+    // Convert to a `CStr` first so that we can drop the '\0' byte at the end
+    // and make sure there are no interior '\0' bytes.
+    CStr::from_bytes_with_nul(buf).map_err(Error::InvalidCString)
+}
+
+fn add_dirent(
+    cursor: &mut Writer,
+    max: u32,
+    d: DirEntry,
+    entry: Option<Entry>,
+) -> io::Result<usize> {
+    if d.name.len() > ::std::u32::MAX as usize {
+        return Err(io::Error::from_raw_os_error(libc::EOVERFLOW));
+    }
+
+    let dirent_len = size_of::<Dirent>()
+        .checked_add(d.name.len())
+        .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
+
+    // Directory entries must be padded to 8-byte alignment.  If adding 7 causes
+    // an overflow then this dirent cannot be properly padded.
+    let padded_dirent_len = dirent_len
+        .checked_add(7)
+        .map(|l| l & !7)
+        .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
+
+    let total_len = if entry.is_some() {
+        padded_dirent_len
+            .checked_add(size_of::<EntryOut>())
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?
+    } else {
+        padded_dirent_len
+    };
+
+    if (max as usize).saturating_sub(cursor.bytes_written()) < total_len {
+        Ok(0)
+    } else {
+        if let Some(entry) = entry {
+            cursor.write_all(EntryOut::from(entry).as_slice())?;
+        }
+
+        let dirent = Dirent {
+            ino: d.ino,
+            off: d.offset,
+            namelen: d.name.len() as u32,
+            type_: d.type_,
+        };
+
+        cursor.write_all(dirent.as_slice())?;
+        cursor.write_all(d.name)?;
+
+        // We know that `dirent_len` <= `padded_dirent_len` due to the check above
+        // so there's no need for checked arithmetic.
+        let padding = padded_dirent_len - dirent_len;
+        if padding > 0 {
+            cursor.write_all(&DIRENT_PADDING[..padding])?;
+        }
+
+        Ok(total_len)
+    }
+}