Import 'p9' crate

Request Document: go/android-rust-importing-crates
For CL Reviewers: go/android3p#cl-review
For Build Team: go/ab-third-party-imports

Bug: 317282512
Test: n/a
Change-Id: Ia4db439643269ab1f32943b252b718f944dac3fb
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..31fd63b
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "68f2a1caea21b7707f5c6a992fe5e71f2e89513a"
+  },
+  "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eb5a316
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b16bd94
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# How to contribute
+
+We'd love to accept your patches and contributions to this project.
+
+## Before you begin
+
+### Sign our Contributor License Agreement
+
+Contributions to this project must be accompanied by a
+[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
+You (or your employer) retain the copyright to your contribution; this simply
+gives us permission to use and redistribute your contributions as part of the
+project.
+
+If you or your current employer have already signed the Google CLA (even if it
+was for a different project), you probably don't need to do it again.
+
+Visit <https://cla.developers.google.com/> to see your current agreements or to
+sign a new one.
+
+### Review our community guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
+
+## Contribution process
+
+### Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..1635172
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,33 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "p9"
+version = "0.2.3"
+authors = ["The ChromiumOS Authors"]
+description = "Server implementation of the 9p file system protocol"
+readme = "README.md"
+license = "BSD-3-Clause"
+repository = "https://github.com/google/rust-p9"
+
+[features]
+trace = []
+
+[target."cfg(unix)".dependencies.libc]
+version = "0.2"
+
+[target."cfg(unix)".dependencies.p9_wire_format_derive]
+version = "0.2.3"
+
+[target."cfg(unix)".dependencies.serde]
+version = "1.0"
+features = ["derive"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..c7cedd5
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,19 @@
+[package]
+name = "p9"
+version = "0.2.3"
+authors = ["The ChromiumOS Authors"]
+edition = "2021"
+license = "BSD-3-Clause"
+description = "Server implementation of the 9p file system protocol"
+repository = "https://github.com/google/rust-p9"
+
+[target.'cfg(unix)'.dependencies]
+libc = "0.2"
+serde = { version = "1.0", features = ["derive"] }
+p9_wire_format_derive = { path = "p9_wire_format_derive", version = "0.2.3" }
+
+[features]
+trace = []
+
+[workspace]
+members = ["p9_wire_format_derive"]
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f6d41c6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2017 The ChromiumOS Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..228cef0
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "p9"
+description: "Server implementation of the 9p file system protocol"
+third_party {
+  identifier {
+    type: "crates.io"
+    value: "https://crates.io/crates/p9"
+  }
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/p9/p9-0.2.3.crate"
+  }
+  version: "0.2.3"
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2024
+    month: 1
+    day: 17
+  }
+}
diff --git a/MODULE_LICENSE_BSD_LIKE b/MODULE_LICENSE_BSD_LIKE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD_LIKE
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..41179fc
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
+fmayle@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..52cd4e6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+# p9 - Server implementation of the [9p] file system protocol
+
+This directory contains the protocol definition and a server implementation of the [9p] file system
+protocol.
+
+- [wire_format_derive] - A [procedural macro] that derives the serialization and de-serialization
+  implementation for a struct into the [9p] wire format.
+- [src/protocol] - Defines all the messages used in the [9p] protocol. Also implements serialization
+  and de-serialization for some base types (integers, strings, vectors) that form the foundation of
+  all [9p] messages. Wire format implementations for all other messages are derived using the
+  `wire_format_derive` macro.
+- [src/server.rs] - Implements a full [9p] server, carrying out file system requests on behalf of
+  clients.
+
+[9p]: http://man.cat-v.org/plan_9/5/intro
+[procedural macro]: https://doc.rust-lang.org/proc_macro/index.html
+[src/protocol]: src/protocol/
+[src/server.rs]: src/server.rs
+[wire_format_derive]: wire_format_derive/
diff --git a/src/fuzzing.rs b/src/fuzzing.rs
new file mode 100644
index 0000000..0c2fc70
--- /dev/null
+++ b/src/fuzzing.rs
@@ -0,0 +1,14 @@
+// Copyright 2018 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Cursor;
+
+use crate::protocol::Tframe;
+use crate::protocol::WireFormat;
+
+pub fn tframe_decode(bytes: &[u8]) {
+    let mut cursor = Cursor::new(bytes);
+
+    while Tframe::decode(&mut cursor).is_ok() {}
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..7c283c4
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,29 @@
+// Copyright 2018 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![cfg(unix)]
+
+extern crate libc;
+
+#[macro_use]
+extern crate p9_wire_format_derive;
+
+mod protocol;
+mod server;
+
+pub mod fuzzing;
+
+pub use server::*;
+
+#[macro_export]
+macro_rules! syscall {
+    ($e:expr) => {{
+        let res = $e;
+        if res < 0 {
+            Err(std::io::Error::last_os_error())
+        } else {
+            Ok(res)
+        }
+    }};
+}
diff --git a/src/protocol/messages.rs b/src/protocol/messages.rs
new file mode 100644
index 0000000..106f136
--- /dev/null
+++ b/src/protocol/messages.rs
@@ -0,0 +1,863 @@
+// Copyright 2018 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io;
+use std::io::ErrorKind;
+use std::io::Read;
+use std::io::Write;
+use std::mem;
+use std::string::String;
+use std::vec::Vec;
+
+use crate::protocol::wire_format::Data;
+use crate::protocol::wire_format::WireFormat;
+
+// Message type constants.  Taken from "include/net/9p/9p.h" in the linux kernel
+// tree.  The protocol specifies each R* message to be the corresponding T*
+// message plus one.
+const TLERROR: u8 = 6;
+const RLERROR: u8 = TLERROR + 1;
+const TSTATFS: u8 = 8;
+const RSTATFS: u8 = TSTATFS + 1;
+const TLOPEN: u8 = 12;
+const RLOPEN: u8 = TLOPEN + 1;
+const TLCREATE: u8 = 14;
+const RLCREATE: u8 = TLCREATE + 1;
+const TSYMLINK: u8 = 16;
+const RSYMLINK: u8 = TSYMLINK + 1;
+const TMKNOD: u8 = 18;
+const RMKNOD: u8 = TMKNOD + 1;
+const TRENAME: u8 = 20;
+const RRENAME: u8 = TRENAME + 1;
+const TREADLINK: u8 = 22;
+const RREADLINK: u8 = TREADLINK + 1;
+const TGETATTR: u8 = 24;
+const RGETATTR: u8 = TGETATTR + 1;
+const TSETATTR: u8 = 26;
+const RSETATTR: u8 = TSETATTR + 1;
+const TXATTRWALK: u8 = 30;
+const RXATTRWALK: u8 = TXATTRWALK + 1;
+const TXATTRCREATE: u8 = 32;
+const RXATTRCREATE: u8 = TXATTRCREATE + 1;
+const TREADDIR: u8 = 40;
+const RREADDIR: u8 = TREADDIR + 1;
+const TFSYNC: u8 = 50;
+const RFSYNC: u8 = TFSYNC + 1;
+const TLOCK: u8 = 52;
+const RLOCK: u8 = TLOCK + 1;
+const TGETLOCK: u8 = 54;
+const RGETLOCK: u8 = TGETLOCK + 1;
+const TLINK: u8 = 70;
+const RLINK: u8 = TLINK + 1;
+const TMKDIR: u8 = 72;
+const RMKDIR: u8 = TMKDIR + 1;
+const TRENAMEAT: u8 = 74;
+const RRENAMEAT: u8 = TRENAMEAT + 1;
+const TUNLINKAT: u8 = 76;
+const RUNLINKAT: u8 = TUNLINKAT + 1;
+const TVERSION: u8 = 100;
+const RVERSION: u8 = TVERSION + 1;
+const TAUTH: u8 = 102;
+const RAUTH: u8 = TAUTH + 1;
+const TATTACH: u8 = 104;
+const RATTACH: u8 = TATTACH + 1;
+const _TERROR: u8 = 106;
+const _RERROR: u8 = _TERROR + 1;
+const TFLUSH: u8 = 108;
+const RFLUSH: u8 = TFLUSH + 1;
+const TWALK: u8 = 110;
+const RWALK: u8 = TWALK + 1;
+const _TOPEN: u8 = 112;
+const _ROPEN: u8 = _TOPEN + 1;
+const _TCREATE: u8 = 114;
+const _RCREATE: u8 = _TCREATE + 1;
+const TREAD: u8 = 116;
+const RREAD: u8 = TREAD + 1;
+const TWRITE: u8 = 118;
+const RWRITE: u8 = TWRITE + 1;
+const TCLUNK: u8 = 120;
+const RCLUNK: u8 = TCLUNK + 1;
+const TREMOVE: u8 = 122;
+const RREMOVE: u8 = TREMOVE + 1;
+const _TSTAT: u8 = 124;
+const _RSTAT: u8 = _TSTAT + 1;
+const _TWSTAT: u8 = 126;
+const _RWSTAT: u8 = _TWSTAT + 1;
+
+/// A message sent from a 9P client to a 9P server.
+#[derive(Debug)]
+pub enum Tmessage {
+    Version(Tversion),
+    Flush(Tflush),
+    Walk(Twalk),
+    Read(Tread),
+    Write(Twrite),
+    Clunk(Tclunk),
+    Remove(Tremove),
+    Attach(Tattach),
+    Auth(Tauth),
+    Statfs(Tstatfs),
+    Lopen(Tlopen),
+    Lcreate(Tlcreate),
+    Symlink(Tsymlink),
+    Mknod(Tmknod),
+    Rename(Trename),
+    Readlink(Treadlink),
+    GetAttr(Tgetattr),
+    SetAttr(Tsetattr),
+    XattrWalk(Txattrwalk),
+    XattrCreate(Txattrcreate),
+    Readdir(Treaddir),
+    Fsync(Tfsync),
+    Lock(Tlock),
+    GetLock(Tgetlock),
+    Link(Tlink),
+    Mkdir(Tmkdir),
+    RenameAt(Trenameat),
+    UnlinkAt(Tunlinkat),
+}
+
+#[derive(Debug)]
+pub struct Tframe {
+    pub tag: u16,
+    pub msg: io::Result<Tmessage>,
+}
+
+impl WireFormat for Tframe {
+    fn byte_size(&self) -> u32 {
+        let msg = self
+            .msg
+            .as_ref()
+            .expect("tried to encode Tframe with invalid msg");
+        let msg_size = match msg {
+            Tmessage::Version(ref version) => version.byte_size(),
+            Tmessage::Flush(ref flush) => flush.byte_size(),
+            Tmessage::Walk(ref walk) => walk.byte_size(),
+            Tmessage::Read(ref read) => read.byte_size(),
+            Tmessage::Write(ref write) => write.byte_size(),
+            Tmessage::Clunk(ref clunk) => clunk.byte_size(),
+            Tmessage::Remove(ref remove) => remove.byte_size(),
+            Tmessage::Attach(ref attach) => attach.byte_size(),
+            Tmessage::Auth(ref auth) => auth.byte_size(),
+            Tmessage::Statfs(ref statfs) => statfs.byte_size(),
+            Tmessage::Lopen(ref lopen) => lopen.byte_size(),
+            Tmessage::Lcreate(ref lcreate) => lcreate.byte_size(),
+            Tmessage::Symlink(ref symlink) => symlink.byte_size(),
+            Tmessage::Mknod(ref mknod) => mknod.byte_size(),
+            Tmessage::Rename(ref rename) => rename.byte_size(),
+            Tmessage::Readlink(ref readlink) => readlink.byte_size(),
+            Tmessage::GetAttr(ref getattr) => getattr.byte_size(),
+            Tmessage::SetAttr(ref setattr) => setattr.byte_size(),
+            Tmessage::XattrWalk(ref xattrwalk) => xattrwalk.byte_size(),
+            Tmessage::XattrCreate(ref xattrcreate) => xattrcreate.byte_size(),
+            Tmessage::Readdir(ref readdir) => readdir.byte_size(),
+            Tmessage::Fsync(ref fsync) => fsync.byte_size(),
+            Tmessage::Lock(ref lock) => lock.byte_size(),
+            Tmessage::GetLock(ref getlock) => getlock.byte_size(),
+            Tmessage::Link(ref link) => link.byte_size(),
+            Tmessage::Mkdir(ref mkdir) => mkdir.byte_size(),
+            Tmessage::RenameAt(ref renameat) => renameat.byte_size(),
+            Tmessage::UnlinkAt(ref unlinkat) => unlinkat.byte_size(),
+        };
+
+        // size + type + tag + message size
+        (mem::size_of::<u32>() + mem::size_of::<u8>() + mem::size_of::<u16>()) as u32 + msg_size
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        let msg = match self.msg.as_ref() {
+            Ok(msg) => msg,
+            Err(_) => {
+                return Err(io::Error::new(
+                    io::ErrorKind::InvalidData,
+                    "tried to encode Tframe with invalid msg",
+                ))
+            }
+        };
+
+        self.byte_size().encode(writer)?;
+
+        let ty = match msg {
+            Tmessage::Version(_) => TVERSION,
+            Tmessage::Flush(_) => TFLUSH,
+            Tmessage::Walk(_) => TWALK,
+            Tmessage::Read(_) => TREAD,
+            Tmessage::Write(_) => TWRITE,
+            Tmessage::Clunk(_) => TCLUNK,
+            Tmessage::Remove(_) => TREMOVE,
+            Tmessage::Attach(_) => TATTACH,
+            Tmessage::Auth(_) => TAUTH,
+            Tmessage::Statfs(_) => TSTATFS,
+            Tmessage::Lopen(_) => TLOPEN,
+            Tmessage::Lcreate(_) => TLCREATE,
+            Tmessage::Symlink(_) => TSYMLINK,
+            Tmessage::Mknod(_) => TMKNOD,
+            Tmessage::Rename(_) => TRENAME,
+            Tmessage::Readlink(_) => TREADLINK,
+            Tmessage::GetAttr(_) => TGETATTR,
+            Tmessage::SetAttr(_) => TSETATTR,
+            Tmessage::XattrWalk(_) => TXATTRWALK,
+            Tmessage::XattrCreate(_) => TXATTRCREATE,
+            Tmessage::Readdir(_) => TREADDIR,
+            Tmessage::Fsync(_) => TFSYNC,
+            Tmessage::Lock(_) => TLOCK,
+            Tmessage::GetLock(_) => TGETLOCK,
+            Tmessage::Link(_) => TLINK,
+            Tmessage::Mkdir(_) => TMKDIR,
+            Tmessage::RenameAt(_) => TRENAMEAT,
+            Tmessage::UnlinkAt(_) => TUNLINKAT,
+        };
+
+        ty.encode(writer)?;
+        self.tag.encode(writer)?;
+
+        match msg {
+            Tmessage::Version(ref version) => version.encode(writer),
+            Tmessage::Flush(ref flush) => flush.encode(writer),
+            Tmessage::Walk(ref walk) => walk.encode(writer),
+            Tmessage::Read(ref read) => read.encode(writer),
+            Tmessage::Write(ref write) => write.encode(writer),
+            Tmessage::Clunk(ref clunk) => clunk.encode(writer),
+            Tmessage::Remove(ref remove) => remove.encode(writer),
+            Tmessage::Attach(ref attach) => attach.encode(writer),
+            Tmessage::Auth(ref auth) => auth.encode(writer),
+            Tmessage::Statfs(ref statfs) => statfs.encode(writer),
+            Tmessage::Lopen(ref lopen) => lopen.encode(writer),
+            Tmessage::Lcreate(ref lcreate) => lcreate.encode(writer),
+            Tmessage::Symlink(ref symlink) => symlink.encode(writer),
+            Tmessage::Mknod(ref mknod) => mknod.encode(writer),
+            Tmessage::Rename(ref rename) => rename.encode(writer),
+            Tmessage::Readlink(ref readlink) => readlink.encode(writer),
+            Tmessage::GetAttr(ref getattr) => getattr.encode(writer),
+            Tmessage::SetAttr(ref setattr) => setattr.encode(writer),
+            Tmessage::XattrWalk(ref xattrwalk) => xattrwalk.encode(writer),
+            Tmessage::XattrCreate(ref xattrcreate) => xattrcreate.encode(writer),
+            Tmessage::Readdir(ref readdir) => readdir.encode(writer),
+            Tmessage::Fsync(ref fsync) => fsync.encode(writer),
+            Tmessage::Lock(ref lock) => lock.encode(writer),
+            Tmessage::GetLock(ref getlock) => getlock.encode(writer),
+            Tmessage::Link(ref link) => link.encode(writer),
+            Tmessage::Mkdir(ref mkdir) => mkdir.encode(writer),
+            Tmessage::RenameAt(ref renameat) => renameat.encode(writer),
+            Tmessage::UnlinkAt(ref unlinkat) => unlinkat.encode(writer),
+        }
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let byte_size: u32 = WireFormat::decode(reader)?;
+
+        // byte_size includes the size of byte_size so remove that from the
+        // expected length of the message.  Also make sure that byte_size is at least
+        // that long to begin with.
+        if byte_size < mem::size_of::<u32>() as u32 {
+            return Err(io::Error::new(
+                ErrorKind::InvalidData,
+                format!("byte_size(= {}) is less than 4 bytes", byte_size),
+            ));
+        }
+
+        let reader = &mut reader.take((byte_size - mem::size_of::<u32>() as u32) as u64);
+
+        let mut ty = [0u8];
+        reader.read_exact(&mut ty)?;
+
+        let tag: u16 = WireFormat::decode(reader)?;
+        let msg = Self::decode_message(reader, ty[0]);
+
+        Ok(Tframe { tag, msg })
+    }
+}
+
+impl Tframe {
+    fn decode_message<R: Read>(reader: &mut R, ty: u8) -> io::Result<Tmessage> {
+        match ty {
+            TVERSION => Ok(Tmessage::Version(WireFormat::decode(reader)?)),
+            TFLUSH => Ok(Tmessage::Flush(WireFormat::decode(reader)?)),
+            TWALK => Ok(Tmessage::Walk(WireFormat::decode(reader)?)),
+            TREAD => Ok(Tmessage::Read(WireFormat::decode(reader)?)),
+            TWRITE => Ok(Tmessage::Write(WireFormat::decode(reader)?)),
+            TCLUNK => Ok(Tmessage::Clunk(WireFormat::decode(reader)?)),
+            TREMOVE => Ok(Tmessage::Remove(WireFormat::decode(reader)?)),
+            TATTACH => Ok(Tmessage::Attach(WireFormat::decode(reader)?)),
+            TAUTH => Ok(Tmessage::Auth(WireFormat::decode(reader)?)),
+            TSTATFS => Ok(Tmessage::Statfs(WireFormat::decode(reader)?)),
+            TLOPEN => Ok(Tmessage::Lopen(WireFormat::decode(reader)?)),
+            TLCREATE => Ok(Tmessage::Lcreate(WireFormat::decode(reader)?)),
+            TSYMLINK => Ok(Tmessage::Symlink(WireFormat::decode(reader)?)),
+            TMKNOD => Ok(Tmessage::Mknod(WireFormat::decode(reader)?)),
+            TRENAME => Ok(Tmessage::Rename(WireFormat::decode(reader)?)),
+            TREADLINK => Ok(Tmessage::Readlink(WireFormat::decode(reader)?)),
+            TGETATTR => Ok(Tmessage::GetAttr(WireFormat::decode(reader)?)),
+            TSETATTR => Ok(Tmessage::SetAttr(WireFormat::decode(reader)?)),
+            TXATTRWALK => Ok(Tmessage::XattrWalk(WireFormat::decode(reader)?)),
+            TXATTRCREATE => Ok(Tmessage::XattrCreate(WireFormat::decode(reader)?)),
+            TREADDIR => Ok(Tmessage::Readdir(WireFormat::decode(reader)?)),
+            TFSYNC => Ok(Tmessage::Fsync(WireFormat::decode(reader)?)),
+            TLOCK => Ok(Tmessage::Lock(WireFormat::decode(reader)?)),
+            TGETLOCK => Ok(Tmessage::GetLock(WireFormat::decode(reader)?)),
+            TLINK => Ok(Tmessage::Link(WireFormat::decode(reader)?)),
+            TMKDIR => Ok(Tmessage::Mkdir(WireFormat::decode(reader)?)),
+            TRENAMEAT => Ok(Tmessage::RenameAt(WireFormat::decode(reader)?)),
+            TUNLINKAT => Ok(Tmessage::UnlinkAt(WireFormat::decode(reader)?)),
+            err => Err(io::Error::new(
+                ErrorKind::InvalidData,
+                format!("unknown message type {}", err),
+            )),
+        }
+    }
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tversion {
+    pub msize: u32,
+    pub version: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tflush {
+    pub oldtag: u16,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Twalk {
+    pub fid: u32,
+    pub newfid: u32,
+    pub wnames: Vec<String>,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tread {
+    pub fid: u32,
+    pub offset: u64,
+    pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Twrite {
+    pub fid: u32,
+    pub offset: u64,
+    pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tclunk {
+    pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tremove {
+    pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tauth {
+    pub afid: u32,
+    pub uname: String,
+    pub aname: String,
+    pub n_uname: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tattach {
+    pub fid: u32,
+    pub afid: u32,
+    pub uname: String,
+    pub aname: String,
+    pub n_uname: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tstatfs {
+    pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlopen {
+    pub fid: u32,
+    pub flags: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlcreate {
+    pub fid: u32,
+    pub name: String,
+    pub flags: u32,
+    pub mode: u32,
+    pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tsymlink {
+    pub fid: u32,
+    pub name: String,
+    pub symtgt: String,
+    pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tmknod {
+    pub dfid: u32,
+    pub name: String,
+    pub mode: u32,
+    pub major: u32,
+    pub minor: u32,
+    pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Trename {
+    pub fid: u32,
+    pub dfid: u32,
+    pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Treadlink {
+    pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tgetattr {
+    pub fid: u32,
+    pub request_mask: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tsetattr {
+    pub fid: u32,
+    pub valid: u32,
+    pub mode: u32,
+    pub uid: u32,
+    pub gid: u32,
+    pub size: u64,
+    pub atime_sec: u64,
+    pub atime_nsec: u64,
+    pub mtime_sec: u64,
+    pub mtime_nsec: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Txattrwalk {
+    pub fid: u32,
+    pub newfid: u32,
+    pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Txattrcreate {
+    pub fid: u32,
+    pub name: String,
+    pub attr_size: u64,
+    pub flags: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Treaddir {
+    pub fid: u32,
+    pub offset: u64,
+    pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tfsync {
+    pub fid: u32,
+    pub datasync: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlock {
+    pub fid: u32,
+    pub type_: u8,
+    pub flags: u32,
+    pub start: u64,
+    pub length: u64,
+    pub proc_id: u32,
+    pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tgetlock {
+    pub fid: u32,
+    pub type_: u8,
+    pub start: u64,
+    pub length: u64,
+    pub proc_id: u32,
+    pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlink {
+    pub dfid: u32,
+    pub fid: u32,
+    pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tmkdir {
+    pub dfid: u32,
+    pub name: String,
+    pub mode: u32,
+    pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Trenameat {
+    pub olddirfid: u32,
+    pub oldname: String,
+    pub newdirfid: u32,
+    pub newname: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tunlinkat {
+    pub dirfd: u32,
+    pub name: String,
+    pub flags: u32,
+}
+
+/// A message sent from a 9P server to a 9P client in response to a request from
+/// that client.  Encapsulates a full frame.
+#[derive(Debug)]
+pub enum Rmessage {
+    Version(Rversion),
+    Flush,
+    Walk(Rwalk),
+    Read(Rread),
+    Write(Rwrite),
+    Clunk,
+    Remove,
+    Attach(Rattach),
+    Auth(Rauth),
+    Statfs(Rstatfs),
+    Lopen(Rlopen),
+    Lcreate(Rlcreate),
+    Symlink(Rsymlink),
+    Mknod(Rmknod),
+    Rename,
+    Readlink(Rreadlink),
+    GetAttr(Rgetattr),
+    SetAttr,
+    XattrWalk(Rxattrwalk),
+    XattrCreate,
+    Readdir(Rreaddir),
+    Fsync,
+    Lock(Rlock),
+    GetLock(Rgetlock),
+    Link,
+    Mkdir(Rmkdir),
+    RenameAt,
+    UnlinkAt,
+    Lerror(Rlerror),
+}
+
+#[derive(Debug)]
+pub struct Rframe {
+    pub tag: u16,
+    pub msg: Rmessage,
+}
+
+impl WireFormat for Rframe {
+    fn byte_size(&self) -> u32 {
+        let msg_size = match self.msg {
+            Rmessage::Version(ref version) => version.byte_size(),
+            Rmessage::Flush => 0,
+            Rmessage::Walk(ref walk) => walk.byte_size(),
+            Rmessage::Read(ref read) => read.byte_size(),
+            Rmessage::Write(ref write) => write.byte_size(),
+            Rmessage::Clunk => 0,
+            Rmessage::Remove => 0,
+            Rmessage::Attach(ref attach) => attach.byte_size(),
+            Rmessage::Auth(ref auth) => auth.byte_size(),
+            Rmessage::Statfs(ref statfs) => statfs.byte_size(),
+            Rmessage::Lopen(ref lopen) => lopen.byte_size(),
+            Rmessage::Lcreate(ref lcreate) => lcreate.byte_size(),
+            Rmessage::Symlink(ref symlink) => symlink.byte_size(),
+            Rmessage::Mknod(ref mknod) => mknod.byte_size(),
+            Rmessage::Rename => 0,
+            Rmessage::Readlink(ref readlink) => readlink.byte_size(),
+            Rmessage::GetAttr(ref getattr) => getattr.byte_size(),
+            Rmessage::SetAttr => 0,
+            Rmessage::XattrWalk(ref xattrwalk) => xattrwalk.byte_size(),
+            Rmessage::XattrCreate => 0,
+            Rmessage::Readdir(ref readdir) => readdir.byte_size(),
+            Rmessage::Fsync => 0,
+            Rmessage::Lock(ref lock) => lock.byte_size(),
+            Rmessage::GetLock(ref getlock) => getlock.byte_size(),
+            Rmessage::Link => 0,
+            Rmessage::Mkdir(ref mkdir) => mkdir.byte_size(),
+            Rmessage::RenameAt => 0,
+            Rmessage::UnlinkAt => 0,
+            Rmessage::Lerror(ref lerror) => lerror.byte_size(),
+        };
+
+        // size + type + tag + message size
+        (mem::size_of::<u32>() + mem::size_of::<u8>() + mem::size_of::<u16>()) as u32 + msg_size
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        self.byte_size().encode(writer)?;
+
+        let ty = match self.msg {
+            Rmessage::Version(_) => RVERSION,
+            Rmessage::Flush => RFLUSH,
+            Rmessage::Walk(_) => RWALK,
+            Rmessage::Read(_) => RREAD,
+            Rmessage::Write(_) => RWRITE,
+            Rmessage::Clunk => RCLUNK,
+            Rmessage::Remove => RREMOVE,
+            Rmessage::Attach(_) => RATTACH,
+            Rmessage::Auth(_) => RAUTH,
+            Rmessage::Statfs(_) => RSTATFS,
+            Rmessage::Lopen(_) => RLOPEN,
+            Rmessage::Lcreate(_) => RLCREATE,
+            Rmessage::Symlink(_) => RSYMLINK,
+            Rmessage::Mknod(_) => RMKNOD,
+            Rmessage::Rename => RRENAME,
+            Rmessage::Readlink(_) => RREADLINK,
+            Rmessage::GetAttr(_) => RGETATTR,
+            Rmessage::SetAttr => RSETATTR,
+            Rmessage::XattrWalk(_) => RXATTRWALK,
+            Rmessage::XattrCreate => RXATTRCREATE,
+            Rmessage::Readdir(_) => RREADDIR,
+            Rmessage::Fsync => RFSYNC,
+            Rmessage::Lock(_) => RLOCK,
+            Rmessage::GetLock(_) => RGETLOCK,
+            Rmessage::Link => RLINK,
+            Rmessage::Mkdir(_) => RMKDIR,
+            Rmessage::RenameAt => RRENAMEAT,
+            Rmessage::UnlinkAt => RUNLINKAT,
+            Rmessage::Lerror(_) => RLERROR,
+        };
+
+        ty.encode(writer)?;
+        self.tag.encode(writer)?;
+
+        match self.msg {
+            Rmessage::Version(ref version) => version.encode(writer),
+            Rmessage::Flush => Ok(()),
+            Rmessage::Walk(ref walk) => walk.encode(writer),
+            Rmessage::Read(ref read) => read.encode(writer),
+            Rmessage::Write(ref write) => write.encode(writer),
+            Rmessage::Clunk => Ok(()),
+            Rmessage::Remove => Ok(()),
+            Rmessage::Attach(ref attach) => attach.encode(writer),
+            Rmessage::Auth(ref auth) => auth.encode(writer),
+            Rmessage::Statfs(ref statfs) => statfs.encode(writer),
+            Rmessage::Lopen(ref lopen) => lopen.encode(writer),
+            Rmessage::Lcreate(ref lcreate) => lcreate.encode(writer),
+            Rmessage::Symlink(ref symlink) => symlink.encode(writer),
+            Rmessage::Mknod(ref mknod) => mknod.encode(writer),
+            Rmessage::Rename => Ok(()),
+            Rmessage::Readlink(ref readlink) => readlink.encode(writer),
+            Rmessage::GetAttr(ref getattr) => getattr.encode(writer),
+            Rmessage::SetAttr => Ok(()),
+            Rmessage::XattrWalk(ref xattrwalk) => xattrwalk.encode(writer),
+            Rmessage::XattrCreate => Ok(()),
+            Rmessage::Readdir(ref readdir) => readdir.encode(writer),
+            Rmessage::Fsync => Ok(()),
+            Rmessage::Lock(ref lock) => lock.encode(writer),
+            Rmessage::GetLock(ref getlock) => getlock.encode(writer),
+            Rmessage::Link => Ok(()),
+            Rmessage::Mkdir(ref mkdir) => mkdir.encode(writer),
+            Rmessage::RenameAt => Ok(()),
+            Rmessage::UnlinkAt => Ok(()),
+            Rmessage::Lerror(ref lerror) => lerror.encode(writer),
+        }
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let byte_size: u32 = WireFormat::decode(reader)?;
+
+        // byte_size includes the size of byte_size so remove that from the
+        // expected length of the message.
+        let reader = &mut reader.take((byte_size - mem::size_of::<u32>() as u32) as u64);
+
+        let mut ty = [0u8];
+        reader.read_exact(&mut ty)?;
+
+        let tag: u16 = WireFormat::decode(reader)?;
+
+        let msg = match ty[0] {
+            RVERSION => Ok(Rmessage::Version(WireFormat::decode(reader)?)),
+            RFLUSH => Ok(Rmessage::Flush),
+            RWALK => Ok(Rmessage::Walk(WireFormat::decode(reader)?)),
+            RREAD => Ok(Rmessage::Read(WireFormat::decode(reader)?)),
+            RWRITE => Ok(Rmessage::Write(WireFormat::decode(reader)?)),
+            RCLUNK => Ok(Rmessage::Clunk),
+            RREMOVE => Ok(Rmessage::Remove),
+            RATTACH => Ok(Rmessage::Attach(WireFormat::decode(reader)?)),
+            RAUTH => Ok(Rmessage::Auth(WireFormat::decode(reader)?)),
+            RSTATFS => Ok(Rmessage::Statfs(WireFormat::decode(reader)?)),
+            RLOPEN => Ok(Rmessage::Lopen(WireFormat::decode(reader)?)),
+            RLCREATE => Ok(Rmessage::Lcreate(WireFormat::decode(reader)?)),
+            RSYMLINK => Ok(Rmessage::Symlink(WireFormat::decode(reader)?)),
+            RMKNOD => Ok(Rmessage::Mknod(WireFormat::decode(reader)?)),
+            RRENAME => Ok(Rmessage::Rename),
+            RREADLINK => Ok(Rmessage::Readlink(WireFormat::decode(reader)?)),
+            RGETATTR => Ok(Rmessage::GetAttr(WireFormat::decode(reader)?)),
+            RSETATTR => Ok(Rmessage::SetAttr),
+            RXATTRWALK => Ok(Rmessage::XattrWalk(WireFormat::decode(reader)?)),
+            RXATTRCREATE => Ok(Rmessage::XattrCreate),
+            RREADDIR => Ok(Rmessage::Readdir(WireFormat::decode(reader)?)),
+            RFSYNC => Ok(Rmessage::Fsync),
+            RLOCK => Ok(Rmessage::Lock(WireFormat::decode(reader)?)),
+            RGETLOCK => Ok(Rmessage::GetLock(WireFormat::decode(reader)?)),
+            RLINK => Ok(Rmessage::Link),
+            RMKDIR => Ok(Rmessage::Mkdir(WireFormat::decode(reader)?)),
+            RRENAMEAT => Ok(Rmessage::RenameAt),
+            RUNLINKAT => Ok(Rmessage::UnlinkAt),
+            RLERROR => Ok(Rmessage::Lerror(WireFormat::decode(reader)?)),
+            err => Err(io::Error::new(
+                ErrorKind::InvalidData,
+                format!("unknown message type {}", err),
+            )),
+        }?;
+
+        Ok(Rframe { tag, msg })
+    }
+}
+
+#[derive(Debug, Copy, Clone, P9WireFormat)]
+pub struct Qid {
+    pub ty: u8,
+    pub version: u32,
+    pub path: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Dirent {
+    pub qid: Qid,
+    pub offset: u64,
+    pub ty: u8,
+    pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rversion {
+    pub msize: u32,
+    pub version: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rwalk {
+    pub wqids: Vec<Qid>,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rread {
+    pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rwrite {
+    pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rauth {
+    pub aqid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rattach {
+    pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlerror {
+    pub ecode: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rstatfs {
+    pub ty: u32,
+    pub bsize: u32,
+    pub blocks: u64,
+    pub bfree: u64,
+    pub bavail: u64,
+    pub files: u64,
+    pub ffree: u64,
+    pub fsid: u64,
+    pub namelen: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlopen {
+    pub qid: Qid,
+    pub iounit: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlcreate {
+    pub qid: Qid,
+    pub iounit: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rsymlink {
+    pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rmknod {
+    pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rreadlink {
+    pub target: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rgetattr {
+    pub valid: u64,
+    pub qid: Qid,
+    pub mode: u32,
+    pub uid: u32,
+    pub gid: u32,
+    pub nlink: u64,
+    pub rdev: u64,
+    pub size: u64,
+    pub blksize: u64,
+    pub blocks: u64,
+    pub atime_sec: u64,
+    pub atime_nsec: u64,
+    pub mtime_sec: u64,
+    pub mtime_nsec: u64,
+    pub ctime_sec: u64,
+    pub ctime_nsec: u64,
+    pub btime_sec: u64,
+    pub btime_nsec: u64,
+    pub gen: u64,
+    pub data_version: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rxattrwalk {
+    pub size: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rreaddir {
+    pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlock {
+    pub status: u8,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rgetlock {
+    pub type_: u8,
+    pub start: u64,
+    pub length: u64,
+    pub proc_id: u32,
+    pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rmkdir {
+    pub qid: Qid,
+}
diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs
new file mode 100644
index 0000000..d209cfc
--- /dev/null
+++ b/src/protocol/mod.rs
@@ -0,0 +1,10 @@
+// Copyright 2018 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod messages;
+mod wire_format;
+
+pub use self::messages::*;
+pub use self::wire_format::Data;
+pub use self::wire_format::WireFormat;
diff --git a/src/protocol/wire_format.rs b/src/protocol/wire_format.rs
new file mode 100644
index 0000000..52b0e86
--- /dev/null
+++ b/src/protocol/wire_format.rs
@@ -0,0 +1,703 @@
+// Copyright 2018 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::fmt;
+use std::io;
+use std::io::ErrorKind;
+use std::io::Read;
+use std::io::Write;
+use std::mem;
+use std::ops::Deref;
+use std::ops::DerefMut;
+use std::string::String;
+use std::vec::Vec;
+
+/// A type that can be encoded on the wire using the 9P protocol.
+pub trait WireFormat: std::marker::Sized {
+    /// Returns the number of bytes necessary to fully encode `self`.
+    fn byte_size(&self) -> u32;
+
+    /// Encodes `self` into `writer`.
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()>;
+
+    /// Decodes `Self` from `reader`.
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self>;
+}
+
+// This doesn't really _need_ to be a macro but unfortunately there is no trait bound to
+// express "can be casted to another type", which means we can't write `T as u8` in a trait
+// based implementation.  So instead we have this macro, which is implemented for all the
+// stable unsigned types with the added benefit of not being implemented for the signed
+// types which are not allowed by the protocol.
+macro_rules! uint_wire_format_impl {
+    ($Ty:ty) => {
+        impl WireFormat for $Ty {
+            fn byte_size(&self) -> u32 {
+                mem::size_of::<$Ty>() as u32
+            }
+
+            fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+                let mut buf = [0u8; mem::size_of::<$Ty>()];
+
+                // Encode the bytes into the buffer in little endian order.
+                for idx in 0..mem::size_of::<$Ty>() {
+                    buf[idx] = (self >> (8 * idx)) as u8;
+                }
+
+                writer.write_all(&buf)
+            }
+
+            fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+                let mut buf = [0u8; mem::size_of::<$Ty>()];
+                reader.read_exact(&mut buf)?;
+
+                // Read bytes from the buffer in little endian order.
+                let mut result = 0;
+                for idx in 0..mem::size_of::<$Ty>() {
+                    result |= (buf[idx] as $Ty) << (8 * idx);
+                }
+
+                Ok(result)
+            }
+        }
+    };
+}
+uint_wire_format_impl!(u8);
+uint_wire_format_impl!(u16);
+uint_wire_format_impl!(u32);
+uint_wire_format_impl!(u64);
+
+// The 9P protocol requires that strings are UTF-8 encoded.  The wire format is a u16
+// count |N|, encoded in little endian, followed by |N| bytes of UTF-8 data.
+impl WireFormat for String {
+    fn byte_size(&self) -> u32 {
+        (mem::size_of::<u16>() + self.len()) as u32
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        if self.len() > std::u16::MAX as usize {
+            return Err(io::Error::new(
+                ErrorKind::InvalidInput,
+                "string is too long",
+            ));
+        }
+
+        (self.len() as u16).encode(writer)?;
+        writer.write_all(self.as_bytes())
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let len: u16 = WireFormat::decode(reader)?;
+        let mut result = String::with_capacity(len as usize);
+        reader.take(len as u64).read_to_string(&mut result)?;
+        Ok(result)
+    }
+}
+
+// The wire format for repeated types is similar to that of strings: a little endian
+// encoded u16 |N|, followed by |N| instances of the given type.
+impl<T: WireFormat> WireFormat for Vec<T> {
+    fn byte_size(&self) -> u32 {
+        mem::size_of::<u16>() as u32 + self.iter().map(|elem| elem.byte_size()).sum::<u32>()
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        if self.len() > std::u16::MAX as usize {
+            return Err(io::Error::new(
+                ErrorKind::InvalidInput,
+                "too many elements in vector",
+            ));
+        }
+
+        (self.len() as u16).encode(writer)?;
+        for elem in self {
+            elem.encode(writer)?;
+        }
+
+        Ok(())
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let len: u16 = WireFormat::decode(reader)?;
+        let mut result = Vec::with_capacity(len as usize);
+
+        for _ in 0..len {
+            result.push(WireFormat::decode(reader)?);
+        }
+
+        Ok(result)
+    }
+}
+
+/// A type that encodes an arbitrary number of bytes of data.  Typically used for Rread
+/// Twrite messages.  This differs from a `Vec<u8>` in that it encodes the number of bytes
+/// using a `u32` instead of a `u16`.
+#[derive(PartialEq, Eq)]
+pub struct Data(pub Vec<u8>);
+
+// The maximum length of a data buffer that we support.  In practice the server's max message
+// size should prevent us from reading too much data so this check is mainly to ensure a
+// malicious client cannot trick us into allocating massive amounts of memory.
+const MAX_DATA_LENGTH: u32 = 32 * 1024 * 1024;
+
+impl fmt::Debug for Data {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        // There may be a lot of data and we don't want to spew it all out in a trace.  Instead
+        // just print out the number of bytes in the buffer.
+        write!(f, "Data({} bytes)", self.len())
+    }
+}
+
+// Implement Deref and DerefMut so that we don't have to use self.0 everywhere.
+impl Deref for Data {
+    type Target = Vec<u8>;
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+impl DerefMut for Data {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+// Same as Vec<u8> except that it encodes the length as a u32 instead of a u16.
+impl WireFormat for Data {
+    fn byte_size(&self) -> u32 {
+        mem::size_of::<u32>() as u32 + self.iter().map(|elem| elem.byte_size()).sum::<u32>()
+    }
+
+    fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+        if self.len() > std::u32::MAX as usize {
+            return Err(io::Error::new(ErrorKind::InvalidInput, "data is too large"));
+        }
+        (self.len() as u32).encode(writer)?;
+        writer.write_all(self)
+    }
+
+    fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+        let len: u32 = WireFormat::decode(reader)?;
+        if len > MAX_DATA_LENGTH {
+            return Err(io::Error::new(
+                ErrorKind::InvalidData,
+                format!("data length ({} bytes) is too large", len),
+            ));
+        }
+
+        let mut buf = Vec::with_capacity(len as usize);
+        reader.take(len as u64).read_to_end(&mut buf)?;
+
+        if buf.len() == len as usize {
+            Ok(Data(buf))
+        } else {
+            Err(io::Error::new(
+                ErrorKind::UnexpectedEof,
+                format!(
+                    "unexpected end of data: want: {} bytes, got: {} bytes",
+                    len,
+                    buf.len()
+                ),
+            ))
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::io::Cursor;
+    use std::mem;
+    use std::string::String;
+
+    use super::*;
+
+    #[test]
+    fn integer_byte_size() {
+        assert_eq!(1, 0u8.byte_size());
+        assert_eq!(2, 0u16.byte_size());
+        assert_eq!(4, 0u32.byte_size());
+        assert_eq!(8, 0u64.byte_size());
+    }
+
+    #[test]
+    fn integer_decode() {
+        let buf: [u8; 8] = [0xef, 0xbe, 0xad, 0xde, 0x0d, 0xf0, 0xad, 0x8b];
+
+        assert_eq!(0xef_u8, WireFormat::decode(&mut Cursor::new(&buf)).unwrap());
+        assert_eq!(0xbeef_u16, u16::decode(&mut Cursor::new(&buf)).unwrap());
+        assert_eq!(0xdeadbeef_u32, u32::decode(&mut Cursor::new(&buf)).unwrap());
+        assert_eq!(
+            0x8bad_f00d_dead_beef_u64,
+            u64::decode(&mut Cursor::new(&buf)).unwrap()
+        );
+    }
+
+    #[test]
+    fn integer_encode() {
+        let value: u64 = 0x8bad_f00d_dead_beef;
+        let expected: [u8; 8] = [0xef, 0xbe, 0xad, 0xde, 0x0d, 0xf0, 0xad, 0x8b];
+
+        let mut buf = vec![0; 8];
+
+        (value as u8).encode(&mut Cursor::new(&mut *buf)).unwrap();
+        assert_eq!(expected[0..1], buf[0..1]);
+
+        (value as u16).encode(&mut Cursor::new(&mut *buf)).unwrap();
+        assert_eq!(expected[0..2], buf[0..2]);
+
+        (value as u32).encode(&mut Cursor::new(&mut *buf)).unwrap();
+        assert_eq!(expected[0..4], buf[0..4]);
+
+        value.encode(&mut Cursor::new(&mut *buf)).unwrap();
+        assert_eq!(expected[0..8], buf[0..8]);
+    }
+
+    #[test]
+    fn string_byte_size() {
+        let values = [
+            String::from("Google Video"),
+            String::from("网页 图片 资讯更多 »"),
+            String::from("Παγκόσμιος Ιστός"),
+            String::from("Поиск страниц на русском"),
+            String::from("전체서비스"),
+        ];
+
+        let exp = values
+            .iter()
+            .map(|v| (mem::size_of::<u16>() + v.len()) as u32);
+
+        for (value, expected) in values.iter().zip(exp) {
+            assert_eq!(expected, value.byte_size());
+        }
+    }
+
+    #[test]
+    fn zero_length_string() {
+        let s = String::from("");
+        assert_eq!(s.byte_size(), mem::size_of::<u16>() as u32);
+
+        let mut buf = [0xffu8; 4];
+
+        s.encode(&mut Cursor::new(&mut buf[..]))
+            .expect("failed to encode empty string");
+        assert_eq!(&[0, 0, 0xff, 0xff], &buf);
+
+        assert_eq!(
+            s,
+            <String as WireFormat>::decode(&mut Cursor::new(&[0, 0, 0x61, 0x61][..]))
+                .expect("failed to decode empty string")
+        );
+    }
+
+    #[test]
+    fn string_encode() {
+        let values = [
+            String::from("Google Video"),
+            String::from("网页 图片 资讯更多 »"),
+            String::from("Παγκόσμιος Ιστός"),
+            String::from("Поиск страниц на русском"),
+            String::from("전체서비스"),
+        ];
+
+        let expected = values.iter().map(|v| {
+            let len = v.as_bytes().len();
+            let mut buf = Vec::with_capacity(len + mem::size_of::<u16>());
+
+            buf.push(len as u8);
+            buf.push((len >> 8) as u8);
+
+            buf.extend_from_slice(v.as_bytes());
+
+            buf
+        });
+
+        for (val, exp) in values.iter().zip(expected) {
+            let mut buf = vec![0; exp.len()];
+
+            WireFormat::encode(val, &mut Cursor::new(&mut *buf)).unwrap();
+            assert_eq!(exp, buf);
+        }
+    }
+
+    #[test]
+    fn string_decode() {
+        assert_eq!(
+            String::from("Google Video"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x0c, 0x00, 0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, 0x56, 0x69, 0x64, 0x65,
+                    0x6F,
+                ][..]
+            ))
+            .unwrap()
+        );
+        assert_eq!(
+            String::from("网页 图片 资讯更多 »"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x1d, 0x00, 0xE7, 0xBD, 0x91, 0xE9, 0xA1, 0xB5, 0x20, 0xE5, 0x9B, 0xBE, 0xE7,
+                    0x89, 0x87, 0x20, 0xE8, 0xB5, 0x84, 0xE8, 0xAE, 0xAF, 0xE6, 0x9B, 0xB4, 0xE5,
+                    0xA4, 0x9A, 0x20, 0xC2, 0xBB,
+                ][..]
+            ))
+            .unwrap()
+        );
+        assert_eq!(
+            String::from("Παγκόσμιος Ιστός"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x1f, 0x00, 0xCE, 0xA0, 0xCE, 0xB1, 0xCE, 0xB3, 0xCE, 0xBA, 0xCF, 0x8C, 0xCF,
+                    0x83, 0xCE, 0xBC, 0xCE, 0xB9, 0xCE, 0xBF, 0xCF, 0x82, 0x20, 0xCE, 0x99, 0xCF,
+                    0x83, 0xCF, 0x84, 0xCF, 0x8C, 0xCF, 0x82,
+                ][..]
+            ))
+            .unwrap()
+        );
+        assert_eq!(
+            String::from("Поиск страниц на русском"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x2d, 0x00, 0xD0, 0x9F, 0xD0, 0xBE, 0xD0, 0xB8, 0xD1, 0x81, 0xD0, 0xBA, 0x20,
+                    0xD1, 0x81, 0xD1, 0x82, 0xD1, 0x80, 0xD0, 0xB0, 0xD0, 0xBD, 0xD0, 0xB8, 0xD1,
+                    0x86, 0x20, 0xD0, 0xBD, 0xD0, 0xB0, 0x20, 0xD1, 0x80, 0xD1, 0x83, 0xD1, 0x81,
+                    0xD1, 0x81, 0xD0, 0xBA, 0xD0, 0xBE, 0xD0, 0xBC,
+                ][..]
+            ))
+            .unwrap()
+        );
+        assert_eq!(
+            String::from("전체서비스"),
+            <String as WireFormat>::decode(&mut Cursor::new(
+                &[
+                    0x0f, 0x00, 0xEC, 0xA0, 0x84, 0xEC, 0xB2, 0xB4, 0xEC, 0x84, 0x9C, 0xEB, 0xB9,
+                    0x84, 0xEC, 0x8A, 0xA4,
+                ][..]
+            ))
+            .unwrap()
+        );
+    }
+
+    #[test]
+    fn invalid_string_decode() {
+        let _ = <String as WireFormat>::decode(&mut Cursor::new(&[
+            0x06, 0x00, 0xed, 0xa0, 0x80, 0xed, 0xbf, 0xbf,
+        ]))
+        .expect_err("surrogate code point");
+
+        let _ = <String as WireFormat>::decode(&mut Cursor::new(&[
+            0x05, 0x00, 0xf8, 0x80, 0x80, 0x80, 0xbf,
+        ]))
+        .expect_err("overlong sequence");
+
+        let _ =
+            <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0xf4, 0x90, 0x80, 0x80]))
+                .expect_err("out of range");
+
+        let _ =
+            <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0x63, 0x61, 0x66, 0xe9]))
+                .expect_err("ISO-8859-1");
+
+        let _ =
+            <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0xb0, 0xa1, 0xb0, 0xa2]))
+                .expect_err("EUC-KR");
+    }
+
+    #[test]
+    fn vector_encode() {
+        let values: Vec<u32> = vec![291, 18_916, 2_497, 22, 797_162, 2_119_732, 3_213_929_716];
+        let mut expected: Vec<u8> =
+            Vec::with_capacity(values.len() * mem::size_of::<u32>() + mem::size_of::<u16>());
+        expected.push(values.len() as u8);
+        expected.push((values.len() >> 8) as u8);
+
+        const MASK: u32 = 0xff;
+        for val in &values {
+            expected.push((val & MASK) as u8);
+            expected.push(((val >> 8) & MASK) as u8);
+            expected.push(((val >> 16) & MASK) as u8);
+            expected.push(((val >> 24) & MASK) as u8);
+        }
+
+        let mut actual: Vec<u8> = vec![0; expected.len()];
+
+        WireFormat::encode(&values, &mut Cursor::new(&mut *actual))
+            .expect("failed to encode vector");
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn vector_decode() {
+        let expected: Vec<u32> = vec![
+            2_498,
+            24,
+            897,
+            4_097_789_579,
+            8_498_119,
+            684_279,
+            961_189_198,
+            7,
+        ];
+        let mut input: Vec<u8> =
+            Vec::with_capacity(expected.len() * mem::size_of::<u32>() + mem::size_of::<u16>());
+        input.push(expected.len() as u8);
+        input.push((expected.len() >> 8) as u8);
+
+        const MASK: u32 = 0xff;
+        for val in &expected {
+            input.push((val & MASK) as u8);
+            input.push(((val >> 8) & MASK) as u8);
+            input.push(((val >> 16) & MASK) as u8);
+            input.push(((val >> 24) & MASK) as u8);
+        }
+
+        assert_eq!(
+            expected,
+            <Vec<u32> as WireFormat>::decode(&mut Cursor::new(&*input))
+                .expect("failed to decode vector")
+        );
+    }
+
+    #[test]
+    fn data_encode() {
+        let values = Data(vec![169, 155, 79, 67, 182, 199, 25, 73, 129, 200]);
+        let mut expected: Vec<u8> =
+            Vec::with_capacity(values.len() * mem::size_of::<u8>() + mem::size_of::<u32>());
+        expected.push(values.len() as u8);
+        expected.push((values.len() >> 8) as u8);
+        expected.push((values.len() >> 16) as u8);
+        expected.push((values.len() >> 24) as u8);
+        expected.extend_from_slice(&values);
+
+        let mut actual: Vec<u8> = vec![0; expected.len()];
+
+        WireFormat::encode(&values, &mut Cursor::new(&mut *actual))
+            .expect("failed to encode datar");
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn data_decode() {
+        let expected = Data(vec![219, 15, 8, 155, 194, 129, 79, 91, 46, 53, 173]);
+        let mut input: Vec<u8> =
+            Vec::with_capacity(expected.len() * mem::size_of::<u8>() + mem::size_of::<u32>());
+        input.push(expected.len() as u8);
+        input.push((expected.len() >> 8) as u8);
+        input.push((expected.len() >> 16) as u8);
+        input.push((expected.len() >> 24) as u8);
+        input.extend_from_slice(&expected);
+
+        assert_eq!(
+            expected,
+            <Data as WireFormat>::decode(&mut Cursor::new(&mut *input))
+                .expect("failed to decode data")
+        );
+    }
+
+    #[test]
+    fn error_cases() {
+        // string is too long.
+        let mut long_str = String::with_capacity(std::u16::MAX as usize);
+        while long_str.len() < std::u16::MAX as usize {
+            long_str.push_str("long");
+        }
+        long_str.push('!');
+
+        let count = long_str.len() + mem::size_of::<u16>();
+        let mut buf = vec![0; count];
+
+        long_str
+            .encode(&mut Cursor::new(&mut *buf))
+            .expect_err("long string");
+
+        // vector is too long.
+        let mut long_vec: Vec<u32> = Vec::with_capacity(std::u16::MAX as usize);
+        while long_vec.len() < std::u16::MAX as usize {
+            long_vec.push(0x8bad_f00d);
+        }
+        long_vec.push(0x00ba_b10c);
+
+        let count = long_vec.len() * mem::size_of::<u32>();
+        let mut buf = vec![0; count];
+
+        WireFormat::encode(&long_vec, &mut Cursor::new(&mut *buf)).expect_err("long vector");
+    }
+
+    #[derive(Debug, PartialEq, P9WireFormat)]
+    struct Item {
+        a: u64,
+        b: String,
+        c: Vec<u16>,
+        buf: Data,
+    }
+
+    #[test]
+    fn struct_encode() {
+        let item = Item {
+            a: 0xdead_10cc_00ba_b10c,
+            b: String::from("冻住,不许走!"),
+            c: vec![359, 492, 8891],
+            buf: Data(vec![254, 129, 0, 62, 49, 172]),
+        };
+
+        let mut expected: Vec<u8> = vec![0x0c, 0xb1, 0xba, 0x00, 0xcc, 0x10, 0xad, 0xde];
+        let strlen = item.b.len() as u16;
+        expected.push(strlen as u8);
+        expected.push((strlen >> 8) as u8);
+        expected.extend_from_slice(item.b.as_bytes());
+
+        let veclen = item.c.len() as u16;
+        expected.push(veclen as u8);
+        expected.push((veclen >> 8) as u8);
+        for val in &item.c {
+            expected.push(*val as u8);
+            expected.push((val >> 8) as u8);
+        }
+
+        let buflen = item.buf.len() as u32;
+        expected.push(buflen as u8);
+        expected.push((buflen >> 8) as u8);
+        expected.push((buflen >> 16) as u8);
+        expected.push((buflen >> 24) as u8);
+        expected.extend_from_slice(&item.buf);
+
+        let mut actual = vec![0; expected.len()];
+
+        WireFormat::encode(&item, &mut Cursor::new(&mut *actual)).expect("failed to encode item");
+
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn struct_decode() {
+        let expected = Item {
+            a: 0xface_b00c_0404_4b1d,
+            b: String::from("Огонь по готовности!"),
+            c: vec![20067, 32449, 549, 4972, 77, 1987],
+            buf: Data(vec![126, 236, 79, 59, 6, 159]),
+        };
+
+        let mut input: Vec<u8> = vec![0x1d, 0x4b, 0x04, 0x04, 0x0c, 0xb0, 0xce, 0xfa];
+        let strlen = expected.b.len() as u16;
+        input.push(strlen as u8);
+        input.push((strlen >> 8) as u8);
+        input.extend_from_slice(expected.b.as_bytes());
+
+        let veclen = expected.c.len() as u16;
+        input.push(veclen as u8);
+        input.push((veclen >> 8) as u8);
+        for val in &expected.c {
+            input.push(*val as u8);
+            input.push((val >> 8) as u8);
+        }
+
+        let buflen = expected.buf.len() as u32;
+        input.push(buflen as u8);
+        input.push((buflen >> 8) as u8);
+        input.push((buflen >> 16) as u8);
+        input.push((buflen >> 24) as u8);
+        input.extend_from_slice(&expected.buf);
+
+        let actual: Item =
+            WireFormat::decode(&mut Cursor::new(input)).expect("failed to decode item");
+
+        assert_eq!(expected, actual);
+    }
+
+    #[derive(Debug, PartialEq, P9WireFormat)]
+    struct Nested {
+        item: Item,
+        val: Vec<u64>,
+    }
+
+    #[allow(clippy::vec_init_then_push)]
+    fn build_encoded_buffer(value: &Nested) -> Vec<u8> {
+        let mut result: Vec<u8> = Vec::new();
+
+        // encode a
+        result.push(value.item.a as u8);
+        result.push((value.item.a >> 8) as u8);
+        result.push((value.item.a >> 16) as u8);
+        result.push((value.item.a >> 24) as u8);
+        result.push((value.item.a >> 32) as u8);
+        result.push((value.item.a >> 40) as u8);
+        result.push((value.item.a >> 48) as u8);
+        result.push((value.item.a >> 56) as u8);
+
+        // encode b
+        result.push(value.item.b.len() as u8);
+        result.push((value.item.b.len() >> 8) as u8);
+        result.extend_from_slice(value.item.b.as_bytes());
+
+        // encode c
+        result.push(value.item.c.len() as u8);
+        result.push((value.item.c.len() >> 8) as u8);
+        for val in &value.item.c {
+            result.push((val & 0xffu16) as u8);
+            result.push(((val >> 8) & 0xffu16) as u8);
+        }
+
+        // encode buf
+        result.push(value.item.buf.len() as u8);
+        result.push((value.item.buf.len() >> 8) as u8);
+        result.push((value.item.buf.len() >> 16) as u8);
+        result.push((value.item.buf.len() >> 24) as u8);
+        result.extend_from_slice(&value.item.buf);
+
+        // encode val
+        result.push(value.val.len() as u8);
+        result.push((value.val.len() >> 8) as u8);
+        for val in &value.val {
+            result.push(*val as u8);
+            result.push((val >> 8) as u8);
+            result.push((val >> 16) as u8);
+            result.push((val >> 24) as u8);
+            result.push((val >> 32) as u8);
+            result.push((val >> 40) as u8);
+            result.push((val >> 48) as u8);
+            result.push((val >> 56) as u8);
+        }
+
+        result
+    }
+
+    #[test]
+    fn nested_encode() {
+        let value = Nested {
+            item: Item {
+                a: 0xcafe_d00d_8bad_f00d,
+                b: String::from("龍が我が敵を喰らう!"),
+                c: vec![2679, 55_919, 44, 38_819, 792],
+                buf: Data(vec![129, 55, 200, 93, 7, 68]),
+            },
+            val: vec![1954978, 59, 4519, 15679],
+        };
+
+        let expected = build_encoded_buffer(&value);
+
+        let mut actual = vec![0; expected.len()];
+
+        WireFormat::encode(&value, &mut Cursor::new(&mut *actual)).expect("failed to encode value");
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn nested_decode() {
+        let expected = Nested {
+            item: Item {
+                a: 0x0ff1ce,
+                b: String::from("龍神の剣を喰らえ!"),
+                c: vec![21687, 159, 55, 9217, 192],
+                buf: Data(vec![189, 22, 7, 59, 235]),
+            },
+            val: vec![15679, 8619196, 319746, 123957, 77, 0, 492],
+        };
+
+        let input = build_encoded_buffer(&expected);
+
+        assert_eq!(
+            expected,
+            <Nested as WireFormat>::decode(&mut Cursor::new(&*input))
+                .expect("failed to decode value")
+        );
+    }
+}
diff --git a/src/server/mod.rs b/src/server/mod.rs
new file mode 100644
index 0000000..7e6dcd5
--- /dev/null
+++ b/src/server/mod.rs
@@ -0,0 +1,1144 @@
+// Copyright 2018 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod read_dir;
+
+use std::cmp::min;
+use std::collections::btree_map;
+use std::collections::BTreeMap;
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::fs::File;
+use std::io;
+use std::io::Cursor;
+use std::io::Read;
+use std::io::Write;
+use std::mem;
+use std::mem::MaybeUninit;
+use std::ops::Deref;
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::fs::FileExt;
+use std::os::unix::io::AsRawFd;
+use std::os::unix::io::FromRawFd;
+use std::os::unix::io::RawFd;
+use std::path::Path;
+use std::str::FromStr;
+
+use read_dir::read_dir;
+use serde::Deserialize;
+use serde::Serialize;
+
+use crate::protocol::*;
+use crate::syscall;
+
+// Tlopen and Tlcreate flags.  Taken from "include/net/9p/9p.h" in the linux tree.
+const P9_RDONLY: u32 = 0o00000000;
+const P9_WRONLY: u32 = 0o00000001;
+const P9_RDWR: u32 = 0o00000002;
+const P9_NOACCESS: u32 = 0o00000003;
+const P9_CREATE: u32 = 0o00000100;
+const P9_EXCL: u32 = 0o00000200;
+const P9_NOCTTY: u32 = 0o00000400;
+const P9_TRUNC: u32 = 0o00001000;
+const P9_APPEND: u32 = 0o00002000;
+const P9_NONBLOCK: u32 = 0o00004000;
+const P9_DSYNC: u32 = 0o00010000;
+const P9_FASYNC: u32 = 0o00020000;
+const P9_DIRECT: u32 = 0o00040000;
+const P9_LARGEFILE: u32 = 0o00100000;
+const P9_DIRECTORY: u32 = 0o00200000;
+const P9_NOFOLLOW: u32 = 0o00400000;
+const P9_NOATIME: u32 = 0o01000000;
+const _P9_CLOEXEC: u32 = 0o02000000;
+const P9_SYNC: u32 = 0o04000000;
+
+// Mapping from 9P flags to libc flags.
+const MAPPED_FLAGS: [(u32, i32); 16] = [
+    (P9_WRONLY, libc::O_WRONLY),
+    (P9_RDWR, libc::O_RDWR),
+    (P9_CREATE, libc::O_CREAT),
+    (P9_EXCL, libc::O_EXCL),
+    (P9_NOCTTY, libc::O_NOCTTY),
+    (P9_TRUNC, libc::O_TRUNC),
+    (P9_APPEND, libc::O_APPEND),
+    (P9_NONBLOCK, libc::O_NONBLOCK),
+    (P9_DSYNC, libc::O_DSYNC),
+    (P9_FASYNC, 0), // Unsupported
+    (P9_DIRECT, libc::O_DIRECT),
+    (P9_LARGEFILE, libc::O_LARGEFILE),
+    (P9_DIRECTORY, libc::O_DIRECTORY),
+    (P9_NOFOLLOW, libc::O_NOFOLLOW),
+    (P9_NOATIME, libc::O_NOATIME),
+    (P9_SYNC, libc::O_SYNC),
+];
+
+// 9P Qid types.  Taken from "include/net/9p/9p.h" in the linux tree.
+const P9_QTDIR: u8 = 0x80;
+const _P9_QTAPPEND: u8 = 0x40;
+const _P9_QTEXCL: u8 = 0x20;
+const _P9_QTMOUNT: u8 = 0x10;
+const _P9_QTAUTH: u8 = 0x08;
+const _P9_QTTMP: u8 = 0x04;
+const P9_QTSYMLINK: u8 = 0x02;
+const _P9_QTLINK: u8 = 0x01;
+const P9_QTFILE: u8 = 0x00;
+
+// Bitmask values for the getattr request.
+const _P9_GETATTR_MODE: u64 = 0x00000001;
+const _P9_GETATTR_NLINK: u64 = 0x00000002;
+const _P9_GETATTR_UID: u64 = 0x00000004;
+const _P9_GETATTR_GID: u64 = 0x00000008;
+const _P9_GETATTR_RDEV: u64 = 0x00000010;
+const _P9_GETATTR_ATIME: u64 = 0x00000020;
+const _P9_GETATTR_MTIME: u64 = 0x00000040;
+const _P9_GETATTR_CTIME: u64 = 0x00000080;
+const _P9_GETATTR_INO: u64 = 0x00000100;
+const _P9_GETATTR_SIZE: u64 = 0x00000200;
+const _P9_GETATTR_BLOCKS: u64 = 0x00000400;
+
+const _P9_GETATTR_BTIME: u64 = 0x00000800;
+const _P9_GETATTR_GEN: u64 = 0x00001000;
+const _P9_GETATTR_DATA_VERSION: u64 = 0x00002000;
+
+const P9_GETATTR_BASIC: u64 = 0x000007ff; /* Mask for fields up to BLOCKS */
+const _P9_GETATTR_ALL: u64 = 0x00003fff; /* Mask for All fields above */
+
+// Bitmask values for the setattr request.
+const P9_SETATTR_MODE: u32 = 0x00000001;
+const P9_SETATTR_UID: u32 = 0x00000002;
+const P9_SETATTR_GID: u32 = 0x00000004;
+const P9_SETATTR_SIZE: u32 = 0x00000008;
+const P9_SETATTR_ATIME: u32 = 0x00000010;
+const P9_SETATTR_MTIME: u32 = 0x00000020;
+const P9_SETATTR_CTIME: u32 = 0x00000040;
+const P9_SETATTR_ATIME_SET: u32 = 0x00000080;
+const P9_SETATTR_MTIME_SET: u32 = 0x00000100;
+
+// 9p lock constants. Taken from "include/net/9p/9p.h" in the linux kernel.
+const _P9_LOCK_TYPE_RDLCK: u8 = 0;
+const _P9_LOCK_TYPE_WRLCK: u8 = 1;
+const P9_LOCK_TYPE_UNLCK: u8 = 2;
+const _P9_LOCK_FLAGS_BLOCK: u8 = 1;
+const _P9_LOCK_FLAGS_RECLAIM: u8 = 2;
+const P9_LOCK_SUCCESS: u8 = 0;
+const _P9_LOCK_BLOCKED: u8 = 1;
+const _P9_LOCK_ERROR: u8 = 2;
+const _P9_LOCK_GRACE: u8 = 3;
+
+// Minimum and maximum message size that we'll expect from the client.
+const MIN_MESSAGE_SIZE: u32 = 256;
+const MAX_MESSAGE_SIZE: u32 = 64 * 1024 + 24; // 64 KiB of payload plus some extra for the header
+
+#[derive(PartialEq, Eq)]
+enum FileType {
+    Regular,
+    Directory,
+    Other,
+}
+
+impl From<libc::mode_t> for FileType {
+    fn from(mode: libc::mode_t) -> Self {
+        match mode & libc::S_IFMT {
+            libc::S_IFREG => FileType::Regular,
+            libc::S_IFDIR => FileType::Directory,
+            _ => FileType::Other,
+        }
+    }
+}
+
+// Represents state that the server is holding on behalf of a client. Fids are somewhat like file
+// descriptors but are not restricted to open files and directories. Fids are identified by a unique
+// 32-bit number chosen by the client. Most messages sent by clients include a fid on which to
+// operate. The fid in a Tattach message represents the root of the file system tree that the client
+// is allowed to access. A client can create more fids by walking the directory tree from that fid.
+struct Fid {
+    path: File,
+    file: Option<File>,
+    filetype: FileType,
+}
+
+impl From<libc::stat64> for Qid {
+    fn from(st: libc::stat64) -> Qid {
+        let ty = match st.st_mode & libc::S_IFMT {
+            libc::S_IFDIR => P9_QTDIR,
+            libc::S_IFREG => P9_QTFILE,
+            libc::S_IFLNK => P9_QTSYMLINK,
+            _ => 0,
+        };
+
+        Qid {
+            ty,
+            // TODO: deal with the 2038 problem before 2038
+            version: st.st_mtime as u32,
+            path: st.st_ino,
+        }
+    }
+}
+
+fn statat(d: &File, name: &CStr, flags: libc::c_int) -> io::Result<libc::stat64> {
+    let mut st = MaybeUninit::<libc::stat64>::zeroed();
+
+    // Safe because the kernel will only write data in `st` and we check the return
+    // value.
+    let res = unsafe {
+        libc::fstatat64(
+            d.as_raw_fd(),
+            name.as_ptr(),
+            st.as_mut_ptr(),
+            flags | libc::AT_SYMLINK_NOFOLLOW,
+        )
+    };
+    if res >= 0 {
+        // Safe because the kernel guarantees that the struct is now fully initialized.
+        Ok(unsafe { st.assume_init() })
+    } else {
+        Err(io::Error::last_os_error())
+    }
+}
+
+fn stat(f: &File) -> io::Result<libc::stat64> {
+    // Safe because this is a constant value and a valid C string.
+    let pathname = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
+
+    statat(f, pathname, libc::AT_EMPTY_PATH)
+}
+
+fn string_to_cstring(s: String) -> io::Result<CString> {
+    CString::new(s).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))
+}
+
+fn error_to_rmessage(err: io::Error) -> Rmessage {
+    let errno = if let Some(errno) = err.raw_os_error() {
+        errno
+    } else {
+        // Make a best-effort guess based on the kind.
+        match err.kind() {
+            io::ErrorKind::NotFound => libc::ENOENT,
+            io::ErrorKind::PermissionDenied => libc::EPERM,
+            io::ErrorKind::ConnectionRefused => libc::ECONNREFUSED,
+            io::ErrorKind::ConnectionReset => libc::ECONNRESET,
+            io::ErrorKind::ConnectionAborted => libc::ECONNABORTED,
+            io::ErrorKind::NotConnected => libc::ENOTCONN,
+            io::ErrorKind::AddrInUse => libc::EADDRINUSE,
+            io::ErrorKind::AddrNotAvailable => libc::EADDRNOTAVAIL,
+            io::ErrorKind::BrokenPipe => libc::EPIPE,
+            io::ErrorKind::AlreadyExists => libc::EEXIST,
+            io::ErrorKind::WouldBlock => libc::EWOULDBLOCK,
+            io::ErrorKind::InvalidInput => libc::EINVAL,
+            io::ErrorKind::InvalidData => libc::EINVAL,
+            io::ErrorKind::TimedOut => libc::ETIMEDOUT,
+            io::ErrorKind::WriteZero => libc::EIO,
+            io::ErrorKind::Interrupted => libc::EINTR,
+            io::ErrorKind::Other => libc::EIO,
+            io::ErrorKind::UnexpectedEof => libc::EIO,
+            _ => libc::EIO,
+        }
+    };
+
+    Rmessage::Lerror(Rlerror {
+        ecode: errno as u32,
+    })
+}
+
+// Sigh.. Cow requires the underlying type to implement Clone.
+enum MaybeOwned<'b, T> {
+    Borrowed(&'b T),
+    Owned(T),
+}
+
+impl<'a, T> Deref for MaybeOwned<'a, T> {
+    type Target = T;
+    fn deref(&self) -> &Self::Target {
+        use MaybeOwned::*;
+        match *self {
+            Borrowed(borrowed) => borrowed,
+            Owned(ref owned) => owned,
+        }
+    }
+}
+
+impl<'a, T> AsRef<T> for MaybeOwned<'a, T> {
+    fn as_ref(&self) -> &T {
+        use MaybeOwned::*;
+        match self {
+            Borrowed(borrowed) => borrowed,
+            Owned(ref owned) => owned,
+        }
+    }
+}
+
+fn ebadf() -> io::Error {
+    io::Error::from_raw_os_error(libc::EBADF)
+}
+
+pub type ServerIdMap<T> = BTreeMap<T, T>;
+pub type ServerUidMap = ServerIdMap<libc::uid_t>;
+pub type ServerGidMap = ServerIdMap<libc::gid_t>;
+
+fn map_id_from_host<T: Clone + Ord>(map: &ServerIdMap<T>, id: T) -> T {
+    map.get(&id).map_or(id.clone(), |v| v.clone())
+}
+
+// Performs an ascii case insensitive lookup and returns an O_PATH fd for the entry, if found.
+fn ascii_casefold_lookup(proc: &File, parent: &File, name: &[u8]) -> io::Result<File> {
+    let mut dir = open_fid(proc, parent, P9_DIRECTORY)?;
+    let mut dirents = read_dir(&mut dir, 0)?;
+
+    while let Some(entry) = dirents.next().transpose()? {
+        if name.eq_ignore_ascii_case(entry.name.to_bytes()) {
+            return lookup(parent, entry.name);
+        }
+    }
+
+    Err(io::Error::from_raw_os_error(libc::ENOENT))
+}
+
+fn lookup(parent: &File, name: &CStr) -> io::Result<File> {
+    // Safe because this doesn't modify any memory and we check the return value.
+    let fd = syscall!(unsafe {
+        libc::openat64(
+            parent.as_raw_fd(),
+            name.as_ptr(),
+            libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+        )
+    })?;
+
+    // Safe because we just opened this fd.
+    Ok(unsafe { File::from_raw_fd(fd) })
+}
+
+fn do_walk(
+    proc: &File,
+    wnames: Vec<String>,
+    start: &File,
+    ascii_casefold: bool,
+    mds: &mut Vec<libc::stat64>,
+) -> io::Result<File> {
+    let mut current = MaybeOwned::Borrowed(start);
+
+    for wname in wnames {
+        let name = string_to_cstring(wname)?;
+        current = MaybeOwned::Owned(lookup(current.as_ref(), &name).or_else(|e| {
+            if ascii_casefold {
+                if let Some(libc::ENOENT) = e.raw_os_error() {
+                    return ascii_casefold_lookup(proc, current.as_ref(), name.to_bytes());
+                }
+            }
+
+            Err(e)
+        })?);
+        mds.push(stat(&current)?);
+    }
+
+    match current {
+        MaybeOwned::Owned(owned) => Ok(owned),
+        MaybeOwned::Borrowed(borrowed) => borrowed.try_clone(),
+    }
+}
+
+fn open_fid(proc: &File, path: &File, p9_flags: u32) -> io::Result<File> {
+    let pathname = string_to_cstring(format!("self/fd/{}", path.as_raw_fd()))?;
+
+    // We always open files with O_CLOEXEC.
+    let mut flags: i32 = libc::O_CLOEXEC;
+    for &(p9f, of) in &MAPPED_FLAGS {
+        if (p9_flags & p9f) != 0 {
+            flags |= of;
+        }
+    }
+
+    if p9_flags & P9_NOACCESS == P9_RDONLY {
+        flags |= libc::O_RDONLY;
+    }
+
+    // Safe because this doesn't modify any memory and we check the return value. We need to
+    // clear the O_NOFOLLOW flag because we want to follow the proc symlink.
+    let fd = syscall!(unsafe {
+        libc::openat64(
+            proc.as_raw_fd(),
+            pathname.as_ptr(),
+            flags & !libc::O_NOFOLLOW,
+        )
+    })?;
+
+    // Safe because we just opened this fd and we know it is valid.
+    Ok(unsafe { File::from_raw_fd(fd) })
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct Config {
+    pub root: Box<Path>,
+    pub msize: u32,
+
+    pub uid_map: ServerUidMap,
+    pub gid_map: ServerGidMap,
+
+    pub ascii_casefold: bool,
+}
+
+impl FromStr for Config {
+    type Err = &'static str;
+
+    fn from_str(params: &str) -> Result<Self, Self::Err> {
+        let mut cfg = Self::default();
+        if params.is_empty() {
+            return Ok(cfg);
+        }
+        for opt in params.split(':') {
+            let mut o = opt.splitn(2, '=');
+            let kind = o.next().ok_or("`cfg` options mut not be empty")?;
+            let value = o
+                .next()
+                .ok_or("`cfg` options must be of the form `kind=value`")?;
+            match kind {
+                "ascii_casefold" => {
+                    let ascii_casefold = value
+                        .parse()
+                        .map_err(|_| "`ascii_casefold` must be a boolean")?;
+                    cfg.ascii_casefold = ascii_casefold;
+                }
+                _ => return Err("unrecognized option for p9 config"),
+            }
+        }
+        Ok(cfg)
+    }
+}
+
+impl Default for Config {
+    fn default() -> Config {
+        Config {
+            root: Path::new("/").into(),
+            msize: MAX_MESSAGE_SIZE,
+            uid_map: Default::default(),
+            gid_map: Default::default(),
+            ascii_casefold: false,
+        }
+    }
+}
+pub struct Server {
+    fids: BTreeMap<u32, Fid>,
+    proc: File,
+    cfg: Config,
+}
+
+impl Server {
+    pub fn new<P: Into<Box<Path>>>(
+        root: P,
+        uid_map: ServerUidMap,
+        gid_map: ServerGidMap,
+    ) -> io::Result<Server> {
+        Server::with_config(Config {
+            root: root.into(),
+            msize: MAX_MESSAGE_SIZE,
+            uid_map,
+            gid_map,
+            ascii_casefold: false,
+        })
+    }
+
+    pub fn with_config(cfg: Config) -> io::Result<Server> {
+        // Safe because this is a valid c-string.
+        let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/proc\0") };
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let fd = syscall!(unsafe {
+            libc::openat64(
+                libc::AT_FDCWD,
+                proc_cstr.as_ptr(),
+                libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+            )
+        })?;
+
+        // Safe because we just opened this fd and we know it is valid.
+        let proc = unsafe { File::from_raw_fd(fd) };
+        Ok(Server {
+            fids: BTreeMap::new(),
+            proc,
+            cfg,
+        })
+    }
+
+    pub fn keep_fds(&self) -> Vec<RawFd> {
+        vec![self.proc.as_raw_fd()]
+    }
+
+    pub fn handle_message<R: Read, W: Write>(
+        &mut self,
+        reader: &mut R,
+        writer: &mut W,
+    ) -> io::Result<()> {
+        let Tframe { tag, msg } = WireFormat::decode(&mut reader.take(self.cfg.msize as u64))?;
+
+        let rmsg = match msg {
+            Ok(Tmessage::Version(ref version)) => self.version(version).map(Rmessage::Version),
+            Ok(Tmessage::Flush(ref flush)) => self.flush(flush).and(Ok(Rmessage::Flush)),
+            Ok(Tmessage::Walk(walk)) => self.walk(walk).map(Rmessage::Walk),
+            Ok(Tmessage::Read(ref read)) => self.read(read).map(Rmessage::Read),
+            Ok(Tmessage::Write(ref write)) => self.write(write).map(Rmessage::Write),
+            Ok(Tmessage::Clunk(ref clunk)) => self.clunk(clunk).and(Ok(Rmessage::Clunk)),
+            Ok(Tmessage::Remove(ref remove)) => self.remove(remove).and(Ok(Rmessage::Remove)),
+            Ok(Tmessage::Attach(ref attach)) => self.attach(attach).map(Rmessage::Attach),
+            Ok(Tmessage::Auth(ref auth)) => self.auth(auth).map(Rmessage::Auth),
+            Ok(Tmessage::Statfs(ref statfs)) => self.statfs(statfs).map(Rmessage::Statfs),
+            Ok(Tmessage::Lopen(ref lopen)) => self.lopen(lopen).map(Rmessage::Lopen),
+            Ok(Tmessage::Lcreate(lcreate)) => self.lcreate(lcreate).map(Rmessage::Lcreate),
+            Ok(Tmessage::Symlink(ref symlink)) => self.symlink(symlink).map(Rmessage::Symlink),
+            Ok(Tmessage::Mknod(ref mknod)) => self.mknod(mknod).map(Rmessage::Mknod),
+            Ok(Tmessage::Rename(ref rename)) => self.rename(rename).and(Ok(Rmessage::Rename)),
+            Ok(Tmessage::Readlink(ref readlink)) => self.readlink(readlink).map(Rmessage::Readlink),
+            Ok(Tmessage::GetAttr(ref get_attr)) => self.get_attr(get_attr).map(Rmessage::GetAttr),
+            Ok(Tmessage::SetAttr(ref set_attr)) => {
+                self.set_attr(set_attr).and(Ok(Rmessage::SetAttr))
+            }
+            Ok(Tmessage::XattrWalk(ref xattr_walk)) => {
+                self.xattr_walk(xattr_walk).map(Rmessage::XattrWalk)
+            }
+            Ok(Tmessage::XattrCreate(ref xattr_create)) => self
+                .xattr_create(xattr_create)
+                .and(Ok(Rmessage::XattrCreate)),
+            Ok(Tmessage::Readdir(ref readdir)) => self.readdir(readdir).map(Rmessage::Readdir),
+            Ok(Tmessage::Fsync(ref fsync)) => self.fsync(fsync).and(Ok(Rmessage::Fsync)),
+            Ok(Tmessage::Lock(ref lock)) => self.lock(lock).map(Rmessage::Lock),
+            Ok(Tmessage::GetLock(ref get_lock)) => self.get_lock(get_lock).map(Rmessage::GetLock),
+            Ok(Tmessage::Link(link)) => self.link(link).and(Ok(Rmessage::Link)),
+            Ok(Tmessage::Mkdir(mkdir)) => self.mkdir(mkdir).map(Rmessage::Mkdir),
+            Ok(Tmessage::RenameAt(rename_at)) => {
+                self.rename_at(rename_at).and(Ok(Rmessage::RenameAt))
+            }
+            Ok(Tmessage::UnlinkAt(unlink_at)) => {
+                self.unlink_at(unlink_at).and(Ok(Rmessage::UnlinkAt))
+            }
+            Err(e) => {
+                // The header was successfully decoded, but the body failed to decode - send an
+                // error response for this tag.
+                let error = format!("Tframe message decode failed: {}", e);
+                Err(io::Error::new(io::ErrorKind::InvalidData, error))
+            }
+        };
+
+        // Errors while handling requests are never fatal.
+        let response = Rframe {
+            tag,
+            msg: rmsg.unwrap_or_else(error_to_rmessage),
+        };
+
+        response.encode(writer)?;
+        writer.flush()
+    }
+
+    fn auth(&mut self, _auth: &Tauth) -> io::Result<Rauth> {
+        // Returning an error for the auth message means that the server does not require
+        // authentication.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn attach(&mut self, attach: &Tattach) -> io::Result<Rattach> {
+        // TODO: Check attach parameters
+        match self.fids.entry(attach.fid) {
+            btree_map::Entry::Vacant(entry) => {
+                let root = CString::new(self.cfg.root.as_os_str().as_bytes())
+                    .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
+
+                // Safe because this doesn't modify any memory and we check the return value.
+                let fd = syscall!(unsafe {
+                    libc::openat64(
+                        libc::AT_FDCWD,
+                        root.as_ptr(),
+                        libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+                    )
+                })?;
+
+                let root_path = unsafe { File::from_raw_fd(fd) };
+                let st = stat(&root_path)?;
+
+                let fid = Fid {
+                    // Safe because we just opened this fd.
+                    path: root_path,
+                    file: None,
+                    filetype: st.st_mode.into(),
+                };
+                let response = Rattach { qid: st.into() };
+                entry.insert(fid);
+                Ok(response)
+            }
+            btree_map::Entry::Occupied(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
+        }
+    }
+
+    fn version(&mut self, version: &Tversion) -> io::Result<Rversion> {
+        if version.msize < MIN_MESSAGE_SIZE {
+            return Err(io::Error::from_raw_os_error(libc::EINVAL));
+        }
+
+        // A Tversion request clunks all open fids and terminates any pending I/O.
+        self.fids.clear();
+        self.cfg.msize = min(self.cfg.msize, version.msize);
+
+        Ok(Rversion {
+            msize: self.cfg.msize,
+            version: if version.version == "9P2000.L" {
+                String::from("9P2000.L")
+            } else {
+                String::from("unknown")
+            },
+        })
+    }
+
+    #[allow(clippy::unnecessary_wraps)]
+    fn flush(&mut self, _flush: &Tflush) -> io::Result<()> {
+        // TODO: Since everything is synchronous we can't actually flush requests.
+        Ok(())
+    }
+
+    fn walk(&mut self, walk: Twalk) -> io::Result<Rwalk> {
+        // `newfid` must not currently be in use unless it is the same as `fid`.
+        if walk.fid != walk.newfid && self.fids.contains_key(&walk.newfid) {
+            return Err(io::Error::from_raw_os_error(libc::EBADF));
+        }
+
+        // We need to walk the tree.  First get the starting path.
+        let start = &self.fids.get(&walk.fid).ok_or_else(ebadf)?.path;
+
+        // Now walk the tree and break on the first error, if any.
+        let expected_len = walk.wnames.len();
+        let mut mds = Vec::with_capacity(expected_len);
+        match do_walk(
+            &self.proc,
+            walk.wnames,
+            start,
+            self.cfg.ascii_casefold,
+            &mut mds,
+        ) {
+            Ok(end) => {
+                // Store the new fid if the full walk succeeded.
+                if mds.len() == expected_len {
+                    let st = mds.last().copied().map(Ok).unwrap_or_else(|| stat(&end))?;
+                    self.fids.insert(
+                        walk.newfid,
+                        Fid {
+                            path: end,
+                            file: None,
+                            filetype: st.st_mode.into(),
+                        },
+                    );
+                }
+            }
+            Err(e) => {
+                // Only return an error if it occurred on the first component.
+                if mds.is_empty() {
+                    return Err(e);
+                }
+            }
+        }
+
+        Ok(Rwalk {
+            wqids: mds.into_iter().map(Qid::from).collect(),
+        })
+    }
+
+    fn read(&mut self, read: &Tread) -> io::Result<Rread> {
+        // Thankfully, `read` cannot be used to read directories in 9P2000.L.
+        let file = self
+            .fids
+            .get_mut(&read.fid)
+            .and_then(|fid| fid.file.as_mut())
+            .ok_or_else(ebadf)?;
+
+        // Use an empty Rread struct to figure out the overhead of the header.
+        let header_size = Rframe {
+            tag: 0,
+            msg: Rmessage::Read(Rread {
+                data: Data(Vec::new()),
+            }),
+        }
+        .byte_size();
+
+        let capacity = min(self.cfg.msize - header_size, read.count);
+        let mut buf = Data(vec![0u8; capacity as usize]);
+
+        let count = file.read_at(&mut buf, read.offset)?;
+        buf.truncate(count);
+
+        Ok(Rread { data: buf })
+    }
+
+    fn write(&mut self, write: &Twrite) -> io::Result<Rwrite> {
+        let file = self
+            .fids
+            .get_mut(&write.fid)
+            .and_then(|fid| fid.file.as_mut())
+            .ok_or_else(ebadf)?;
+
+        let count = file.write_at(&write.data, write.offset)?;
+        Ok(Rwrite {
+            count: count as u32,
+        })
+    }
+
+    fn clunk(&mut self, clunk: &Tclunk) -> io::Result<()> {
+        match self.fids.entry(clunk.fid) {
+            btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
+            btree_map::Entry::Occupied(entry) => {
+                entry.remove();
+                Ok(())
+            }
+        }
+    }
+
+    fn remove(&mut self, _remove: &Tremove) -> io::Result<()> {
+        // Since a file could be linked into multiple locations, there is no way to know exactly
+        // which path we are supposed to unlink. Linux uses unlink_at anyway, so we can just return
+        // an error here.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rstatfs> {
+        let fid = self.fids.get(&statfs.fid).ok_or_else(ebadf)?;
+        let mut buf = MaybeUninit::zeroed();
+
+        // Safe because this will only modify `out` and we check the return value.
+        syscall!(unsafe { libc::fstatfs64(fid.path.as_raw_fd(), buf.as_mut_ptr()) })?;
+
+        // Safe because this only has integer types and any value is valid.
+        let out = unsafe { buf.assume_init() };
+        Ok(Rstatfs {
+            ty: out.f_type as u32,
+            bsize: out.f_bsize as u32,
+            blocks: out.f_blocks,
+            bfree: out.f_bfree,
+            bavail: out.f_bavail,
+            files: out.f_files,
+            ffree: out.f_ffree,
+            // Safe because the fsid has only integer fields and the compiler will verify that is
+            // the same width as the `fsid` field in Rstatfs.
+            fsid: unsafe { mem::transmute(out.f_fsid) },
+            namelen: out.f_namelen as u32,
+        })
+    }
+
+    fn lopen(&mut self, lopen: &Tlopen) -> io::Result<Rlopen> {
+        let fid = self.fids.get_mut(&lopen.fid).ok_or_else(ebadf)?;
+
+        let file = open_fid(&self.proc, &fid.path, lopen.flags)?;
+        let st = stat(&file)?;
+
+        fid.file = Some(file);
+        Ok(Rlopen {
+            qid: st.into(),
+            iounit: 0, // Allow the client to send requests up to the negotiated max message size.
+        })
+    }
+
+    fn lcreate(&mut self, lcreate: Tlcreate) -> io::Result<Rlcreate> {
+        let fid = self.fids.get_mut(&lcreate.fid).ok_or_else(ebadf)?;
+
+        if fid.filetype != FileType::Directory {
+            return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
+        }
+
+        let mut flags: i32 = libc::O_CLOEXEC | libc::O_CREAT | libc::O_EXCL;
+        for &(p9f, of) in &MAPPED_FLAGS {
+            if (lcreate.flags & p9f) != 0 {
+                flags |= of;
+            }
+        }
+        if lcreate.flags & P9_NOACCESS == P9_RDONLY {
+            flags |= libc::O_RDONLY;
+        }
+
+        let name = string_to_cstring(lcreate.name)?;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        let fd = syscall!(unsafe {
+            libc::openat64(fid.path.as_raw_fd(), name.as_ptr(), flags, lcreate.mode)
+        })?;
+
+        // Safe because we just opened this fd and we know it is valid.
+        let file = unsafe { File::from_raw_fd(fd) };
+        let st = stat(&file)?;
+
+        fid.file = Some(file);
+        fid.filetype = FileType::Regular;
+
+        // This fid now refers to the newly created file so we need to update the O_PATH fd for it
+        // as well.
+        fid.path = lookup(&fid.path, &name)?;
+
+        Ok(Rlcreate {
+            qid: st.into(),
+            iounit: 0, // Allow the client to send requests up to the negotiated max message size.
+        })
+    }
+
+    fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink> {
+        // symlinks are not allowed.
+        Err(io::Error::from_raw_os_error(libc::EACCES))
+    }
+
+    fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod> {
+        // No nodes either.
+        Err(io::Error::from_raw_os_error(libc::EACCES))
+    }
+
+    fn rename(&mut self, _rename: &Trename) -> io::Result<()> {
+        // We cannot support this as an inode may be linked into multiple directories but we don't
+        // know which one the client wants us to rename. Linux uses rename_at anyway, so we don't
+        // need to worry about this.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn readlink(&mut self, readlink: &Treadlink) -> io::Result<Rreadlink> {
+        let fid = self.fids.get(&readlink.fid).ok_or_else(ebadf)?;
+
+        let mut link = vec![0; libc::PATH_MAX as usize];
+
+        // Safe because this will only modify `link` and we check the return value.
+        let len = syscall!(unsafe {
+            libc::readlinkat(
+                fid.path.as_raw_fd(),
+                [0].as_ptr(),
+                link.as_mut_ptr() as *mut libc::c_char,
+                link.len(),
+            )
+        })? as usize;
+        link.truncate(len);
+        let target = String::from_utf8(link)
+            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
+        Ok(Rreadlink { target })
+    }
+
+    #[allow(clippy::unnecessary_cast)] // nlink_t is u32 on 32-bit platforms
+    fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr> {
+        let fid = self.fids.get_mut(&get_attr.fid).ok_or_else(ebadf)?;
+
+        let st = stat(&fid.path)?;
+
+        Ok(Rgetattr {
+            valid: P9_GETATTR_BASIC,
+            qid: st.into(),
+            mode: st.st_mode,
+            uid: map_id_from_host(&self.cfg.uid_map, st.st_uid),
+            gid: map_id_from_host(&self.cfg.gid_map, st.st_gid),
+            nlink: st.st_nlink as u64,
+            rdev: st.st_rdev,
+            size: st.st_size as u64,
+            blksize: st.st_blksize as u64,
+            blocks: st.st_blocks as u64,
+            atime_sec: st.st_atime as u64,
+            atime_nsec: st.st_atime_nsec as u64,
+            mtime_sec: st.st_mtime as u64,
+            mtime_nsec: st.st_mtime_nsec as u64,
+            ctime_sec: st.st_ctime as u64,
+            ctime_nsec: st.st_ctime_nsec as u64,
+            btime_sec: 0,
+            btime_nsec: 0,
+            gen: 0,
+            data_version: 0,
+        })
+    }
+
+    fn set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<()> {
+        let fid = self.fids.get(&set_attr.fid).ok_or_else(ebadf)?;
+        let path = string_to_cstring(format!("self/fd/{}", fid.path.as_raw_fd()))?;
+
+        if set_attr.valid & P9_SETATTR_MODE != 0 {
+            // Safe because this doesn't modify any memory and we check the return value.
+            syscall!(unsafe {
+                libc::fchmodat(self.proc.as_raw_fd(), path.as_ptr(), set_attr.mode, 0)
+            })?;
+        }
+
+        if set_attr.valid & (P9_SETATTR_UID | P9_SETATTR_GID) != 0 {
+            let uid = if set_attr.valid & P9_SETATTR_UID != 0 {
+                set_attr.uid
+            } else {
+                -1i32 as u32
+            };
+            let gid = if set_attr.valid & P9_SETATTR_GID != 0 {
+                set_attr.gid
+            } else {
+                -1i32 as u32
+            };
+
+            // Safe because this doesn't modify any memory and we check the return value.
+            syscall!(unsafe { libc::fchownat(self.proc.as_raw_fd(), path.as_ptr(), uid, gid, 0) })?;
+        }
+
+        if set_attr.valid & P9_SETATTR_SIZE != 0 {
+            let file = if fid.filetype == FileType::Directory {
+                return Err(io::Error::from_raw_os_error(libc::EISDIR));
+            } else if let Some(ref file) = fid.file {
+                MaybeOwned::Borrowed(file)
+            } else {
+                MaybeOwned::Owned(open_fid(&self.proc, &fid.path, P9_NONBLOCK | P9_RDWR)?)
+            };
+
+            file.set_len(set_attr.size)?;
+        }
+
+        if set_attr.valid & (P9_SETATTR_ATIME | P9_SETATTR_MTIME) != 0 {
+            let times = [
+                libc::timespec {
+                    tv_sec: set_attr.atime_sec as _,
+                    tv_nsec: if set_attr.valid & P9_SETATTR_ATIME == 0 {
+                        libc::UTIME_OMIT
+                    } else if set_attr.valid & P9_SETATTR_ATIME_SET == 0 {
+                        libc::UTIME_NOW
+                    } else {
+                        set_attr.atime_nsec as _
+                    },
+                },
+                libc::timespec {
+                    tv_sec: set_attr.mtime_sec as _,
+                    tv_nsec: if set_attr.valid & P9_SETATTR_MTIME == 0 {
+                        libc::UTIME_OMIT
+                    } else if set_attr.valid & P9_SETATTR_MTIME_SET == 0 {
+                        libc::UTIME_NOW
+                    } else {
+                        set_attr.mtime_nsec as _
+                    },
+                },
+            ];
+
+            // Safe because file is valid and we have initialized times fully.
+            let ret = unsafe {
+                libc::utimensat(
+                    self.proc.as_raw_fd(),
+                    path.as_ptr(),
+                    &times as *const libc::timespec,
+                    0,
+                )
+            };
+            if ret < 0 {
+                return Err(io::Error::last_os_error());
+            }
+        }
+
+        // The ctime would have been updated by any of the above operations so we only
+        // need to change it if it was the only option given.
+        if set_attr.valid & P9_SETATTR_CTIME != 0 && set_attr.valid & (!P9_SETATTR_CTIME) == 0 {
+            // Setting -1 as the uid and gid will not actually change anything but will
+            // still update the ctime.
+            let ret = unsafe {
+                libc::fchownat(
+                    self.proc.as_raw_fd(),
+                    path.as_ptr(),
+                    libc::uid_t::max_value(),
+                    libc::gid_t::max_value(),
+                    0,
+                )
+            };
+            if ret < 0 {
+                return Err(io::Error::last_os_error());
+            }
+        }
+
+        Ok(())
+    }
+
+    fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rxattrwalk> {
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<()> {
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rreaddir> {
+        let fid = self.fids.get_mut(&readdir.fid).ok_or_else(ebadf)?;
+
+        if fid.filetype != FileType::Directory {
+            return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
+        }
+
+        // Use an empty Rreaddir struct to figure out the maximum number of bytes that
+        // can be returned.
+        let header_size = Rframe {
+            tag: 0,
+            msg: Rmessage::Readdir(Rreaddir {
+                data: Data(Vec::new()),
+            }),
+        }
+        .byte_size();
+        let count = min(self.cfg.msize - header_size, readdir.count);
+        let mut cursor = Cursor::new(Vec::with_capacity(count as usize));
+
+        let dir = fid.file.as_mut().ok_or_else(ebadf)?;
+        let mut dirents = read_dir(dir, readdir.offset as libc::off64_t)?;
+        while let Some(dirent) = dirents.next().transpose()? {
+            let st = statat(&fid.path, dirent.name, 0)?;
+
+            let name = dirent
+                .name
+                .to_str()
+                .map(String::from)
+                .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
+
+            let entry = Dirent {
+                qid: st.into(),
+                offset: dirent.offset,
+                ty: dirent.type_,
+                name,
+            };
+
+            let byte_size = entry.byte_size() as usize;
+
+            if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size {
+                // No more room in the buffer.
+                break;
+            }
+
+            entry.encode(&mut cursor)?;
+        }
+
+        Ok(Rreaddir {
+            data: Data(cursor.into_inner()),
+        })
+    }
+
+    fn fsync(&mut self, fsync: &Tfsync) -> io::Result<()> {
+        let file = self
+            .fids
+            .get(&fsync.fid)
+            .and_then(|fid| fid.file.as_ref())
+            .ok_or_else(ebadf)?;
+
+        if fsync.datasync == 0 {
+            file.sync_all()?;
+        } else {
+            file.sync_data()?;
+        }
+        Ok(())
+    }
+
+    /// Implement posix byte range locking code.
+    /// Our implementation mirrors that of QEMU/9p - that is to say,
+    /// we essentially punt on mirroring lock state between client/server
+    /// and defer lock semantics to the VFS layer on the client side. Aside
+    /// from fd existence check we always return success. QEMU reference:
+    /// <https://github.com/qemu/qemu/blob/754f756cc4c6d9d14b7230c62b5bb20f9d655888/hw/9pfs/9p.c#L3669>
+    ///
+    /// NOTE: this means that files locked on the client may be interefered with
+    /// from either the server's side, or from other clients (guests). This
+    /// tracks with QEMU implementation, and will be obviated if crosvm decides
+    /// to drop 9p in favor of virtio-fs. QEMU only allows for a single client,
+    /// and we leave it to users of the crate to provide actual lock handling.
+    fn lock(&mut self, lock: &Tlock) -> io::Result<Rlock> {
+        // Ensure fd passed in TLOCK request exists and has a mapping.
+        let fd = self
+            .fids
+            .get(&lock.fid)
+            .and_then(|fid| fid.file.as_ref())
+            .ok_or_else(ebadf)?
+            .as_raw_fd();
+
+        syscall!(unsafe {
+            // Safe because zero-filled libc::stat is a valid value, fstat
+            // populates the struct fields.
+            let mut stbuf: libc::stat64 = std::mem::zeroed();
+            // Safe because this doesn't modify memory and we check the return value.
+            libc::fstat64(fd, &mut stbuf)
+        })?;
+
+        Ok(Rlock {
+            status: P9_LOCK_SUCCESS,
+        })
+    }
+
+    ///
+    /// Much like lock(), defer locking semantics to VFS and return success.
+    ///
+    fn get_lock(&mut self, get_lock: &Tgetlock) -> io::Result<Rgetlock> {
+        // Ensure fd passed in GETTLOCK request exists and has a mapping.
+        let fd = self
+            .fids
+            .get(&get_lock.fid)
+            .and_then(|fid| fid.file.as_ref())
+            .ok_or_else(ebadf)?
+            .as_raw_fd();
+
+        // Safe because this doesn't modify memory and we check the return value.
+        syscall!(unsafe {
+            let mut stbuf: libc::stat64 = std::mem::zeroed();
+            libc::fstat64(fd, &mut stbuf)
+        })?;
+
+        Ok(Rgetlock {
+            type_: P9_LOCK_TYPE_UNLCK,
+            start: get_lock.start,
+            length: get_lock.length,
+            proc_id: get_lock.proc_id,
+            client_id: get_lock.client_id.clone(),
+        })
+    }
+
+    fn link(&mut self, link: Tlink) -> io::Result<()> {
+        let target = self.fids.get(&link.fid).ok_or_else(ebadf)?;
+        let path = string_to_cstring(format!("self/fd/{}", target.path.as_raw_fd()))?;
+
+        let dir = self.fids.get(&link.dfid).ok_or_else(ebadf)?;
+        let name = string_to_cstring(link.name)?;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        syscall!(unsafe {
+            libc::linkat(
+                self.proc.as_raw_fd(),
+                path.as_ptr(),
+                dir.path.as_raw_fd(),
+                name.as_ptr(),
+                libc::AT_SYMLINK_FOLLOW,
+            )
+        })?;
+        Ok(())
+    }
+
+    fn mkdir(&mut self, mkdir: Tmkdir) -> io::Result<Rmkdir> {
+        let fid = self.fids.get(&mkdir.dfid).ok_or_else(ebadf)?;
+        let name = string_to_cstring(mkdir.name)?;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        syscall!(unsafe { libc::mkdirat(fid.path.as_raw_fd(), name.as_ptr(), mkdir.mode) })?;
+        Ok(Rmkdir {
+            qid: statat(&fid.path, &name, 0).map(Qid::from)?,
+        })
+    }
+
+    fn rename_at(&mut self, rename_at: Trenameat) -> io::Result<()> {
+        let olddir = self.fids.get(&rename_at.olddirfid).ok_or_else(ebadf)?;
+        let oldname = string_to_cstring(rename_at.oldname)?;
+
+        let newdir = self.fids.get(&rename_at.newdirfid).ok_or_else(ebadf)?;
+        let newname = string_to_cstring(rename_at.newname)?;
+
+        // Safe because this doesn't modify any memory and we check the return value.
+        syscall!(unsafe {
+            libc::renameat(
+                olddir.path.as_raw_fd(),
+                oldname.as_ptr(),
+                newdir.path.as_raw_fd(),
+                newname.as_ptr(),
+            )
+        })?;
+
+        Ok(())
+    }
+
+    fn unlink_at(&mut self, unlink_at: Tunlinkat) -> io::Result<()> {
+        let dir = self.fids.get(&unlink_at.dirfd).ok_or_else(ebadf)?;
+        let name = string_to_cstring(unlink_at.name)?;
+
+        syscall!(unsafe {
+            libc::unlinkat(
+                dir.path.as_raw_fd(),
+                name.as_ptr(),
+                unlink_at.flags as libc::c_int,
+            )
+        })?;
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/src/server/read_dir.rs b/src/server/read_dir.rs
new file mode 100644
index 0000000..a053084
--- /dev/null
+++ b/src/server/read_dir.rs
@@ -0,0 +1,166 @@
+// Copyright 2020 The ChromiumOS Authors
+// 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::io::Result;
+use std::mem::size_of;
+use std::os::unix::io::AsRawFd;
+
+use crate::syscall;
+
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct LinuxDirent64 {
+    d_ino: libc::ino64_t,
+    d_off: libc::off64_t,
+    d_reclen: libc::c_ushort,
+    d_ty: libc::c_uchar,
+}
+
+impl LinuxDirent64 {
+    // Note: Taken from data_model::DataInit
+    fn from_slice(data: &[u8]) -> Option<&Self> {
+        // Early out to avoid an unneeded `align_to` call.
+        if data.len() != size_of::<Self>() {
+            return None;
+        }
+        // The `align_to` method ensures that we don't have any unaligned references.
+        // This aliases a pointer, but because the pointer is from a const slice reference,
+        // there are no mutable aliases.
+        // Finally, the reference returned can not outlive data because they have equal implicit
+        // lifetime constraints.
+        match unsafe { data.align_to::<Self>() } {
+            ([], [mid], []) => Some(mid),
+            _ => None,
+        }
+    }
+}
+
+pub struct DirEntry<'r> {
+    pub ino: libc::ino64_t,
+    pub offset: u64,
+    pub type_: u8,
+    pub name: &'r CStr,
+}
+
+pub struct ReadDir<'d, D> {
+    buf: [u8; 256],
+    dir: &'d mut D,
+    current: usize,
+    end: usize,
+}
+
+impl<'d, D: AsRawFd> ReadDir<'d, D> {
+    /// Return the next directory entry. This is implemented as a separate method rather than via
+    /// the `Iterator` trait because rust doesn't currently support generic associated types.
+    #[allow(clippy::should_implement_trait)]
+    pub fn next(&mut self) -> Option<Result<DirEntry>> {
+        if self.current >= self.end {
+            let res: Result<libc::c_long> = syscall!(unsafe {
+                libc::syscall(
+                    libc::SYS_getdents64,
+                    self.dir.as_raw_fd(),
+                    self.buf.as_mut_ptr() as *mut LinuxDirent64,
+                    self.buf.len() as libc::c_int,
+                )
+            });
+            match res {
+                Ok(end) => {
+                    self.current = 0;
+                    self.end = end as usize;
+                }
+                Err(e) => return Some(Err(e)),
+            }
+        }
+
+        let rem = &self.buf[self.current..self.end];
+        if rem.is_empty() {
+            return None;
+        }
+
+        // We only use debug asserts here because these values are coming from the kernel and we
+        // trust them implicitly.
+        debug_assert!(
+            rem.len() >= size_of::<LinuxDirent64>(),
+            "not enough space left in `rem`"
+        );
+
+        let (front, back) = rem.split_at(size_of::<LinuxDirent64>());
+
+        let dirent64 =
+            LinuxDirent64::from_slice(front).expect("unable to get LinuxDirent64 from slice");
+
+        let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>();
+        debug_assert!(namelen <= back.len(), "back is smaller than `namelen`");
+
+        // The kernel will pad the name with additional nul bytes until it is 8-byte aligned so
+        // we need to strip those off here.
+        let name = strip_padding(&back[..namelen]);
+        let entry = DirEntry {
+            ino: dirent64.d_ino,
+            offset: dirent64.d_off as u64,
+            type_: dirent64.d_ty,
+            name,
+        };
+
+        debug_assert!(
+            rem.len() >= dirent64.d_reclen as usize,
+            "rem is smaller than `d_reclen`"
+        );
+        self.current += dirent64.d_reclen as usize;
+        Some(Ok(entry))
+    }
+}
+
+pub fn read_dir<D: AsRawFd>(dir: &mut D, offset: libc::off64_t) -> Result<ReadDir<D>> {
+    // Safe because this doesn't modify any memory and we check the return value.
+    syscall!(unsafe { libc::lseek64(dir.as_raw_fd(), offset, libc::SEEK_SET) })?;
+
+    Ok(ReadDir {
+        buf: [0u8; 256],
+        dir,
+        current: 0,
+        end: 0,
+    })
+}
+
+// Like `CStr::from_bytes_with_nul` but strips any bytes after the first '\0'-byte. Panics if `b`
+// doesn't contain any '\0' bytes.
+fn strip_padding(b: &[u8]) -> &CStr {
+    // It would be nice if we could use memchr here but that's locked behind an unstable gate.
+    let pos = b
+        .iter()
+        .position(|&c| c == 0)
+        .expect("`b` doesn't contain any nul bytes");
+
+    // Safe because we are creating this string with the first nul-byte we found so we can
+    // guarantee that it is nul-terminated and doesn't contain any interior nuls.
+    unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn padded_cstrings() {
+        assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b".");
+        assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b"..");
+        assert_eq!(
+            strip_padding(b"normal cstring\0").to_bytes(),
+            b"normal cstring"
+        );
+        assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b"");
+        assert_eq!(
+            strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(),
+            b"interior"
+        );
+    }
+
+    #[test]
+    #[should_panic(expected = "`b` doesn't contain any nul bytes")]
+    fn no_nul_byte() {
+        strip_padding(b"no nul bytes in string");
+    }
+}
diff --git a/src/server/tests.rs b/src/server/tests.rs
new file mode 100644
index 0000000..0c0e73d
--- /dev/null
+++ b/src/server/tests.rs
@@ -0,0 +1,1351 @@
+// Copyright 2019 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::borrow::Cow;
+use std::collections::HashSet;
+use std::collections::VecDeque;
+use std::env;
+use std::ffi::CString;
+use std::ffi::OsString;
+use std::fs;
+use std::fs::File;
+use std::io;
+use std::io::Cursor;
+use std::mem;
+use std::ops::Deref;
+use std::os::unix::ffi::OsStringExt;
+use std::os::unix::fs::symlink;
+use std::os::unix::fs::MetadataExt;
+use std::path::Component;
+use std::path::Path;
+use std::path::PathBuf;
+use std::u32;
+
+use super::*;
+
+// Used to indicate that there is no fid associated with this message.
+const P9_NOFID: u32 = u32::MAX;
+
+// The fid associated with the root directory of the server.
+const ROOT_FID: u32 = 1;
+
+// The pid of the server process, cannot be 1 since that's the kernel init
+const SERVER_PID: u32 = 5;
+
+// How big we want the default buffer to be when running tests.
+const DEFAULT_BUFFER_SIZE: u32 = 4096;
+
+// How big we want to make randomly generated files
+const LOCAL_FILE_LEN: u64 = 200;
+
+// Joins `path` to `buf`.  If `path` is '..', removes the last component from `buf`
+// only if `buf` != `root` but does nothing if `buf` == `root`.  Pushes `path` onto
+// `buf` if it is a normal path component.
+//
+// Returns an error if `path` is absolute, has more than one component, or contains
+// a '.' component.
+fn join_path<P: AsRef<Path>, R: AsRef<Path>>(
+    mut buf: PathBuf,
+    path: P,
+    root: R,
+) -> io::Result<PathBuf> {
+    let path = path.as_ref();
+    let root = root.as_ref();
+    debug_assert!(buf.starts_with(root));
+
+    if path.components().count() > 1 {
+        return Err(io::Error::from_raw_os_error(libc::EINVAL));
+    }
+
+    for component in path.components() {
+        match component {
+            // Prefix should only appear on windows systems.
+            Component::Prefix(_) => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+            // Absolute paths are not allowed.
+            Component::RootDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+            // '.' elements are not allowed.
+            Component::CurDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+            Component::ParentDir => {
+                // We only remove the parent path if we are not already at the root of the
+                // file system.
+                if buf != root {
+                    buf.pop();
+                }
+            }
+            Component::Normal(element) => buf.push(element),
+        }
+    }
+
+    Ok(buf)
+}
+
+// Automatically deletes the path it contains when it goes out of scope.
+struct ScopedPath<P: AsRef<Path>>(P);
+
+impl<P: AsRef<Path>> AsRef<Path> for ScopedPath<P> {
+    fn as_ref(&self) -> &Path {
+        self.0.as_ref()
+    }
+}
+
+impl<P: AsRef<Path>> Deref for ScopedPath<P> {
+    type Target = Path;
+
+    fn deref(&self) -> &Self::Target {
+        self.0.as_ref()
+    }
+}
+
+impl<P: AsRef<Path>> Drop for ScopedPath<P> {
+    fn drop(&mut self) {
+        if let Err(e) = fs::remove_dir_all(&**self) {
+            println!("Failed to remove {}: {}", self.display(), e);
+        }
+    }
+}
+
+enum DirEntry<'a> {
+    File {
+        name: &'a str,
+        content: &'a [u8],
+    },
+    Directory {
+        name: &'a str,
+        entries: &'a [DirEntry<'a>],
+    },
+    Symlink {
+        name: &'a str,
+        target: &'a str,
+    },
+}
+
+impl<'a> DirEntry<'a> {
+    // Creates `self` in the path given by `dir`.
+    // TODO(b/228627457): clippy is warning about the `Cow` below, but it is necessary
+    #[allow(clippy::ptr_arg)]
+    fn create(&self, dir: &mut Cow<Path>) {
+        match *self {
+            DirEntry::File { name, content } => {
+                let mut f = File::create(dir.join(name)).expect("failed to create file");
+                f.write_all(content).expect("failed to write file content");
+            }
+            DirEntry::Directory { name, entries } => {
+                dir.to_mut().push(name);
+
+                fs::create_dir_all(&**dir).expect("failed to create directory");
+                for e in entries {
+                    e.create(dir);
+                }
+
+                assert!(dir.to_mut().pop());
+            }
+            DirEntry::Symlink { name, target } => {
+                symlink(target, dir.join(name)).expect("failed to create symlink");
+            }
+        }
+    }
+}
+
+// Creates a file with `name` in `dir` and fills it with random
+// content.
+fn create_local_file<P: AsRef<Path>>(dir: P, name: &str) -> Vec<u8> {
+    let mut content = Vec::new();
+    File::open("/dev/urandom")
+        .and_then(|f| f.take(LOCAL_FILE_LEN).read_to_end(&mut content))
+        .expect("failed to read from /dev/urandom");
+
+    let f = DirEntry::File {
+        name,
+        content: &content,
+    };
+    f.create(&mut Cow::from(dir.as_ref()));
+
+    content
+}
+
+// Create a symlink named `name` that links to `target`.
+fn create_local_symlink<P: AsRef<Path>>(dir: P, name: &str, target: &str) {
+    let f = DirEntry::Symlink { name, target };
+    f.create(&mut Cow::from(dir.as_ref()));
+}
+
+fn check_qid(qid: &Qid, md: &fs::Metadata) {
+    let ty = if md.is_dir() {
+        P9_QTDIR
+    } else if md.is_file() {
+        P9_QTFILE
+    } else if md.file_type().is_symlink() {
+        P9_QTSYMLINK
+    } else {
+        panic!("unknown file type: {:?}", md.file_type());
+    };
+    assert_eq!(qid.ty, ty);
+    assert_eq!(qid.version, md.mtime() as u32);
+    assert_eq!(qid.path, md.ino());
+}
+
+fn check_attr(server: &mut Server, fid: u32, md: &fs::Metadata) {
+    let tgetattr = Tgetattr {
+        fid,
+        request_mask: P9_GETATTR_BASIC,
+    };
+
+    let rgetattr = server.get_attr(&tgetattr).expect("failed to call get_attr");
+
+    let ty = if md.is_dir() {
+        P9_QTDIR
+    } else if md.is_file() {
+        P9_QTFILE
+    } else if md.file_type().is_symlink() {
+        P9_QTSYMLINK
+    } else {
+        panic!("unknown file type: {:?}", md.file_type());
+    };
+    assert_eq!(rgetattr.valid, P9_GETATTR_BASIC);
+    assert_eq!(rgetattr.qid.ty, ty);
+    assert_eq!(rgetattr.qid.version, md.mtime() as u32);
+    assert_eq!(rgetattr.qid.path, md.ino());
+    assert_eq!(rgetattr.mode, md.mode());
+    assert_eq!(rgetattr.uid, md.uid());
+    assert_eq!(rgetattr.gid, md.gid());
+    assert_eq!(rgetattr.nlink, md.nlink());
+    assert_eq!(rgetattr.rdev, md.rdev());
+    assert_eq!(rgetattr.size, md.size());
+    assert_eq!(rgetattr.atime_sec, md.atime() as u64);
+    assert_eq!(rgetattr.atime_nsec, md.atime_nsec() as u64);
+    assert_eq!(rgetattr.mtime_sec, md.mtime() as u64);
+    assert_eq!(rgetattr.mtime_nsec, md.mtime_nsec() as u64);
+    assert_eq!(rgetattr.ctime_sec, md.ctime() as u64);
+    assert_eq!(rgetattr.ctime_nsec, md.ctime_nsec() as u64);
+    assert_eq!(rgetattr.btime_sec, 0);
+    assert_eq!(rgetattr.btime_nsec, 0);
+    assert_eq!(rgetattr.gen, 0);
+    assert_eq!(rgetattr.data_version, 0);
+}
+
+fn check_content(server: &mut Server, content: &[u8], fid: u32) {
+    for offset in 0..content.len() {
+        let tread = Tread {
+            fid,
+            offset: offset as u64,
+            count: DEFAULT_BUFFER_SIZE,
+        };
+
+        let rread = server.read(&tread).expect("failed to read file");
+        assert_eq!(content[offset..], rread.data[..]);
+    }
+}
+
+fn walk<P: Into<PathBuf>>(
+    server: &mut Server,
+    start: P,
+    fid: u32,
+    newfid: u32,
+    names: Vec<String>,
+) {
+    let mut mds = Vec::with_capacity(names.len());
+    let mut buf = start.into();
+    for name in &names {
+        buf.push(name);
+        mds.push(
+            buf.symlink_metadata()
+                .expect("failed to get metadata for path"),
+        );
+    }
+
+    let twalk = Twalk {
+        fid,
+        newfid,
+        wnames: names,
+    };
+
+    let rwalk = server.walk(twalk).expect("failed to walk directoy");
+    assert_eq!(mds.len(), rwalk.wqids.len());
+    for (md, qid) in mds.iter().zip(rwalk.wqids.iter()) {
+        check_qid(qid, md);
+    }
+}
+
+fn open<P: Into<PathBuf>>(
+    server: &mut Server,
+    dir: P,
+    dir_fid: u32,
+    name: &str,
+    fid: u32,
+    flags: u32,
+) -> io::Result<Rlopen> {
+    let wnames = if name.is_empty() {
+        vec![]
+    } else {
+        vec![String::from(name)]
+    };
+    walk(server, dir, dir_fid, fid, wnames);
+
+    let tlopen = Tlopen { fid, flags };
+
+    server.lopen(&tlopen)
+}
+
+fn write<P: AsRef<Path>>(server: &mut Server, dir: P, name: &str, fid: u32, flags: u32) {
+    let file_path = dir.as_ref().join(name);
+    let file_len = if file_path.exists() {
+        fs::symlink_metadata(&file_path)
+            .expect("unable to get metadata for file")
+            .len() as usize
+    } else {
+        0usize
+    };
+    let mut new_content = Vec::new();
+    File::open("/dev/urandom")
+        .and_then(|f| f.take(200).read_to_end(&mut new_content))
+        .expect("failed to read from /dev/urandom");
+
+    let twrite = Twrite {
+        fid,
+        offset: 0,
+        data: Data(new_content),
+    };
+
+    let rwrite = server.write(&twrite).expect("failed to write file");
+    assert_eq!(rwrite.count, twrite.data.len() as u32);
+
+    let tfsync = Tfsync { fid, datasync: 0 };
+    server.fsync(&tfsync).expect("failed to sync file contents");
+
+    let actual_content = fs::read(file_path).expect("failed to read back content from file");
+
+    // If the file was opened append-only, then the content should have been
+    // written to the end even though the offset was 0.
+    let idx = if flags & P9_APPEND == 0 { 0 } else { file_len };
+    assert_eq!(actual_content[idx..], twrite.data[..]);
+}
+
+fn create<P: Into<PathBuf>>(
+    server: &mut Server,
+    dir: P,
+    dir_fid: u32,
+    fid: u32,
+    name: &str,
+    flags: u32,
+    mode: u32,
+) -> io::Result<Rlcreate> {
+    // The `fid` in the lcreate call initially points to the directory
+    // but is supposed to point to the newly created file after the call
+    // completes.  Duplicate the fid so that we don't end up consuming the
+    // directory fid.
+    walk(server, dir, dir_fid, fid, Vec::new());
+
+    let tlcreate = Tlcreate {
+        fid,
+        name: String::from(name),
+        flags,
+        mode,
+        gid: 0,
+    };
+
+    server.lcreate(tlcreate)
+}
+
+struct Readdir<'a> {
+    server: &'a mut Server,
+    fid: u32,
+    offset: u64,
+    cursor: Cursor<Vec<u8>>,
+}
+
+impl<'a> Iterator for Readdir<'a> {
+    type Item = Dirent;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.cursor.position() >= self.cursor.get_ref().len() as u64 {
+            let treaddir = Treaddir {
+                fid: self.fid,
+                offset: self.offset,
+                count: DEFAULT_BUFFER_SIZE,
+            };
+
+            let Rreaddir { data } = self
+                .server
+                .readdir(&treaddir)
+                .expect("failed to read directory");
+            if data.is_empty() {
+                // No more entries.
+                return None;
+            }
+
+            mem::drop(mem::replace(&mut self.cursor, Cursor::new(data.0)));
+        }
+
+        let dirent: Dirent = WireFormat::decode(&mut self.cursor).expect("failed to decode dirent");
+        self.offset = dirent.offset;
+
+        Some(dirent)
+    }
+}
+
+fn readdir(server: &mut Server, fid: u32) -> Readdir {
+    Readdir {
+        server,
+        fid,
+        offset: 0,
+        cursor: Cursor::new(Vec::new()),
+    }
+}
+
+// Sets up the server to start handling messages.  Creates a new temporary
+// directory to act as the server root and sends an initial Tattach message.
+// At the end of setup, fid 1 points to the root of the server.
+fn setup<P: AsRef<Path>>(name: P) -> (ScopedPath<OsString>, Server) {
+    let mut test_dir = env::var_os("T")
+        .map(PathBuf::from)
+        .unwrap_or_else(env::temp_dir);
+    test_dir.push(name);
+
+    let mut os_str = OsString::from(test_dir);
+    os_str.push(".XXXXXX");
+
+    // Create a c string and release ownership.  This seems like the only way
+    // to get a *mut c_char.
+    let buf = CString::new(os_str.into_vec())
+        .expect("failed to create CString")
+        .into_raw();
+
+    // Safe because this will only modify the contents of `buf`.
+    let ret = unsafe { libc::mkdtemp(buf) };
+
+    // Take ownership of the buffer back before checking the result.  Safe because
+    // this was created by a call to into_raw() above and mkdtemp will not overwrite
+    // the trailing '\0'.
+    let buf = unsafe { CString::from_raw(buf) };
+
+    assert!(!ret.is_null());
+
+    let test_dir = ScopedPath(OsString::from_vec(buf.into_bytes()));
+
+    // Create a basic file system hierarchy.
+    let entries = [
+        DirEntry::Directory {
+            name: "subdir",
+            entries: &[
+                DirEntry::File {
+                    name: "b",
+                    content: b"hello, world!",
+                },
+                DirEntry::Directory {
+                    name: "nested",
+                    entries: &[DirEntry::File {
+                        name: "Огонь по готовности!",
+                        content: &[
+                            0xe9u8, 0xbeu8, 0x8du8, 0xe3u8, 0x81u8, 0x8cu8, 0xe6u8, 0x88u8, 0x91u8,
+                            0xe3u8, 0x81u8, 0x8cu8, 0xe6u8, 0x95u8, 0xb5u8, 0xe3u8, 0x82u8, 0x92u8,
+                            0xe5u8, 0x96u8, 0xb0u8, 0xe3u8, 0x82u8, 0x89u8, 0xe3u8, 0x81u8, 0x86u8,
+                            0x21u8,
+                        ],
+                    }],
+                },
+            ],
+        },
+        DirEntry::File {
+            name: "世界.txt",
+            content: &[
+                0xe3u8, 0x81u8, 0x93u8, 0xe3u8, 0x82u8, 0x93u8, 0xe3u8, 0x81u8, 0xabu8, 0xe3u8,
+                0x81u8, 0xa1u8, 0xe3u8, 0x81u8, 0xafu8,
+            ],
+        },
+    ];
+
+    for e in &entries {
+        e.create(&mut Cow::from(&*test_dir));
+    }
+
+    let md = test_dir
+        .symlink_metadata()
+        .expect("failed to get metadata for root dir");
+
+    let mut server = Server::new(&*test_dir, Default::default(), Default::default())
+        .expect("Failed to create server");
+
+    let tversion = Tversion {
+        msize: DEFAULT_BUFFER_SIZE,
+        version: String::from("9P2000.L"),
+    };
+
+    let rversion = server
+        .version(&tversion)
+        .expect("failed to get version from server");
+    assert_eq!(rversion.msize, DEFAULT_BUFFER_SIZE);
+    assert_eq!(rversion.version, "9P2000.L");
+
+    let tattach = Tattach {
+        fid: ROOT_FID,
+        afid: P9_NOFID,
+        uname: String::from("unittest"),
+        aname: String::from(""),
+        n_uname: 1000,
+    };
+
+    let rattach = server.attach(&tattach).expect("failed to attach to server");
+    check_qid(&rattach.qid, &md);
+
+    (test_dir, server)
+}
+
+#[test]
+fn path_joins() {
+    let root = PathBuf::from("/a/b/c");
+    let path = PathBuf::from("/a/b/c/d/e/f");
+
+    assert_eq!(
+        &join_path(path.clone(), "nested", &root).expect("normal"),
+        Path::new("/a/b/c/d/e/f/nested")
+    );
+
+    let p1 = join_path(path, "..", &root).expect("parent 1");
+    assert_eq!(&p1, Path::new("/a/b/c/d/e/"));
+
+    let p2 = join_path(p1, "..", &root).expect("parent 2");
+    assert_eq!(&p2, Path::new("/a/b/c/d/"));
+
+    let p3 = join_path(p2, "..", &root).expect("parent 3");
+    assert_eq!(&p3, Path::new("/a/b/c/"));
+
+    let p4 = join_path(p3, "..", &root).expect("parent of root");
+    assert_eq!(&p4, Path::new("/a/b/c/"));
+}
+
+#[test]
+fn invalid_joins() {
+    let root = PathBuf::from("/a");
+    let path = PathBuf::from("/a/b");
+
+    join_path(path.clone(), ".", &root).expect_err("current directory");
+    join_path(path.clone(), "c/d/e", &root).expect_err("too many components");
+    join_path(path, "/c/d/e", &root).expect_err("absolute path");
+}
+
+#[test]
+fn clunk() {
+    let (_test_dir, mut server) = setup("clunk");
+
+    let tclunk = Tclunk { fid: ROOT_FID };
+    server.clunk(&tclunk).expect("failed to clunk root fid");
+}
+
+#[test]
+fn get_attr() {
+    let (test_dir, mut server) = setup("get_attr");
+
+    let md = test_dir
+        .symlink_metadata()
+        .expect("failed to get metadata for test dir");
+
+    check_attr(&mut server, ROOT_FID, &md);
+}
+
+#[test]
+fn tree_walk() {
+    let (test_dir, mut server) = setup("readdir");
+
+    let mut next_fid = ROOT_FID + 1;
+
+    let mut dirs = VecDeque::new();
+    dirs.push_back(test_dir.to_path_buf());
+
+    while let Some(dir) = dirs.pop_front() {
+        let dfid = next_fid;
+        next_fid += 1;
+
+        let wnames: Vec<String> = dir
+            .strip_prefix(&test_dir)
+            .expect("test directory is not prefix of subdir")
+            .components()
+            .map(|c| Path::new(&c).to_string_lossy().to_string())
+            .collect();
+        walk(&mut server, &*test_dir, ROOT_FID, dfid, wnames);
+
+        let md = dir.symlink_metadata().expect("failed to get metadata");
+
+        check_attr(&mut server, dfid, &md);
+
+        let fid = next_fid;
+        next_fid += 1;
+        open(&mut server, &dir, dfid, "", fid, P9_DIRECTORY).expect("Failed to open directory");
+        for dirent in readdir(&mut server, fid) {
+            if dirent.name == "." || dirent.name == ".." {
+                continue;
+            }
+
+            let entry_path = dir.join(&dirent.name);
+            assert!(
+                entry_path.exists(),
+                "directory entry \"{}\" does not exist",
+                entry_path.display()
+            );
+            let md = fs::symlink_metadata(&entry_path).expect("failed to get metadata for entry");
+
+            let ty = if md.is_dir() {
+                dirs.push_back(dir.join(dirent.name));
+                libc::DT_DIR
+            } else if md.is_file() {
+                libc::DT_REG
+            } else if md.file_type().is_symlink() {
+                libc::DT_LNK
+            } else {
+                panic!("unknown file type: {:?}", md.file_type());
+            };
+
+            assert_eq!(dirent.ty, ty);
+            check_qid(&dirent.qid, &md);
+        }
+
+        let tclunk = Tclunk { fid };
+        server.clunk(&tclunk).expect("failed to clunk fid");
+    }
+}
+
+#[test]
+fn create_existing_file() {
+    let (test_dir, mut server) = setup("create_existing");
+
+    let name = "existing";
+    create_local_file(&test_dir, name);
+
+    let fid = ROOT_FID + 1;
+    create(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        fid,
+        name,
+        P9_APPEND,
+        0o644,
+    )
+    .expect_err("successfully created existing file");
+}
+
+enum SetAttrKind {
+    File,
+    Directory,
+}
+
+fn set_attr_test<F>(kind: SetAttrKind, set_fields: F) -> io::Result<fs::Metadata>
+where
+    F: FnOnce(&mut Tsetattr),
+{
+    let (test_dir, mut server) = setup("set_attr");
+
+    let name = "existing";
+    match kind {
+        SetAttrKind::File => {
+            create_local_file(&test_dir, name);
+        }
+        SetAttrKind::Directory => {
+            let tmkdir = Tmkdir {
+                dfid: ROOT_FID,
+                name: String::from(name),
+                mode: 0o755,
+                gid: 0,
+            };
+
+            let rmkdir = server.mkdir(tmkdir).expect("failed to create directory");
+            let md = fs::symlink_metadata(test_dir.join(name))
+                .expect("failed to get metadata for directory");
+
+            assert!(md.is_dir());
+            check_qid(&rmkdir.qid, &md);
+        }
+    };
+
+    let fid = ROOT_FID + 1;
+    walk(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        fid,
+        vec![String::from(name)],
+    );
+
+    let mut tsetattr = Tsetattr {
+        fid,
+        valid: 0,
+        mode: 0,
+        uid: 0,
+        gid: 0,
+        size: 0,
+        atime_sec: 0,
+        atime_nsec: 0,
+        mtime_sec: 0,
+        mtime_nsec: 0,
+    };
+
+    set_fields(&mut tsetattr);
+    server.set_attr(&tsetattr)?;
+
+    fs::symlink_metadata(test_dir.join(name))
+}
+
+#[test]
+fn set_len() {
+    let len = 661;
+    let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_SIZE;
+        tsetattr.size = len;
+    })
+    .expect("failed to run set length of file");
+
+    assert_eq!(md.size(), len);
+}
+
+#[test]
+fn set_file_mode() {
+    let mode = 0o640;
+    let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_MODE;
+        tsetattr.mode = mode;
+    })
+    .expect("failed to set mode");
+
+    assert_eq!(md.mode() & 0o777, mode);
+}
+
+#[test]
+fn set_file_mtime() {
+    let (secs, nanos) = (1245247825, 524617);
+    let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_MTIME | P9_SETATTR_MTIME_SET;
+        tsetattr.mtime_sec = secs;
+        tsetattr.mtime_nsec = nanos;
+    })
+    .expect("failed to set mtime");
+
+    assert_eq!(md.mtime() as u64, secs);
+    assert_eq!(md.mtime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_file_atime() {
+    let (secs, nanos) = (9247605, 4016);
+    let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_ATIME | P9_SETATTR_ATIME_SET;
+        tsetattr.atime_sec = secs;
+        tsetattr.atime_nsec = nanos;
+    })
+    .expect("failed to set atime");
+
+    assert_eq!(md.atime() as u64, secs);
+    assert_eq!(md.atime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_dir_mode() {
+    let mode = 0o640;
+    let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_MODE;
+        tsetattr.mode = mode;
+    })
+    .expect("failed to set mode");
+
+    assert_eq!(md.mode() & 0o777, mode);
+}
+
+#[test]
+fn set_dir_mtime() {
+    let (secs, nanos) = (1245247825, 524617);
+    let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_MTIME | P9_SETATTR_MTIME_SET;
+        tsetattr.mtime_sec = secs;
+        tsetattr.mtime_nsec = nanos;
+    })
+    .expect("failed to set mtime");
+
+    assert_eq!(md.mtime() as u64, secs);
+    assert_eq!(md.mtime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_dir_atime() {
+    let (secs, nanos) = (9247605, 4016);
+    let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+        tsetattr.valid = P9_SETATTR_ATIME | P9_SETATTR_ATIME_SET;
+        tsetattr.atime_sec = secs;
+        tsetattr.atime_nsec = nanos;
+    })
+    .expect("failed to set atime");
+
+    assert_eq!(md.atime() as u64, secs);
+    assert_eq!(md.atime_nsec() as u64, nanos);
+}
+
+#[test]
+fn huge_directory() {
+    let (test_dir, mut server) = setup("huge_directory");
+
+    let name = "newdir";
+    let newdir = test_dir.join(name);
+    fs::create_dir(&newdir).expect("failed to create directory");
+
+    let dfid = ROOT_FID + 1;
+    walk(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        dfid,
+        vec![String::from(name)],
+    );
+
+    // Create ~4K files in the directory and then attempt to read them all.
+    let mut filenames = HashSet::with_capacity(4096);
+    for i in 0..4096 {
+        let name = format!("file_{}", i);
+        create_local_file(&newdir, &name);
+        assert!(filenames.insert(name));
+    }
+
+    let fid = dfid + 1;
+    open(&mut server, &newdir, dfid, "", fid, P9_DIRECTORY).expect("Failed to open directory");
+    for f in readdir(&mut server, fid) {
+        let path = newdir.join(&f.name);
+
+        let md = fs::symlink_metadata(path).expect("failed to get metadata for path");
+        check_qid(&f.qid, &md);
+
+        if f.name == "." || f.name == ".." {
+            assert_eq!(f.ty, libc::DT_DIR);
+        } else {
+            assert_eq!(f.ty, libc::DT_REG);
+            assert!(filenames.remove(&f.name));
+        }
+    }
+
+    assert!(filenames.is_empty());
+}
+
+#[test]
+fn mkdir() {
+    let (test_dir, mut server) = setup("mkdir");
+
+    let name = "conan";
+    let tmkdir = Tmkdir {
+        dfid: ROOT_FID,
+        name: String::from(name),
+        mode: 0o755,
+        gid: 0,
+    };
+
+    let rmkdir = server.mkdir(tmkdir).expect("failed to create directory");
+    let md =
+        fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for directory");
+
+    assert!(md.is_dir());
+    check_qid(&rmkdir.qid, &md);
+}
+
+#[test]
+fn unlink_all() {
+    let (test_dir, mut server) = setup("readdir");
+
+    let mut next_fid = ROOT_FID + 1;
+
+    let mut dirs = VecDeque::new();
+    dirs.push_back((ROOT_FID, test_dir.to_path_buf()));
+
+    // First iterate over the whole directory.
+    let mut unlinks = VecDeque::new();
+    while let Some((dfid, dir)) = dirs.pop_front() {
+        let mut names = VecDeque::new();
+        for entry in fs::read_dir(dir).expect("failed to read directory") {
+            let entry = entry.expect("unable to iterate over directory");
+            let ft = entry
+                .file_type()
+                .expect("failed to get file type for entry");
+            if ft.is_dir() {
+                let fid = next_fid;
+                next_fid += 1;
+
+                let wnames: Vec<String> = entry
+                    .path()
+                    .strip_prefix(&test_dir)
+                    .expect("test directory is not prefix of subdir")
+                    .components()
+                    .map(|c| Path::new(&c).to_string_lossy().to_string())
+                    .collect();
+                walk(&mut server, &*test_dir, ROOT_FID, fid, wnames);
+                dirs.push_back((fid, entry.path()));
+            }
+
+            names.push_back((
+                entry
+                    .file_name()
+                    .into_string()
+                    .expect("failed to convert entry name to string"),
+                if ft.is_dir() {
+                    libc::AT_REMOVEDIR as u32
+                } else {
+                    0
+                },
+            ));
+        }
+
+        unlinks.push_back((dfid, names));
+    }
+
+    // Now remove everything in reverse order.
+    while let Some((dfid, names)) = unlinks.pop_back() {
+        for (name, flags) in names {
+            let tunlinkat = Tunlinkat {
+                dirfd: dfid,
+                name,
+                flags,
+            };
+
+            server.unlink_at(tunlinkat).expect("failed to unlink path");
+        }
+    }
+}
+
+#[test]
+fn rename_at() {
+    let (test_dir, mut server) = setup("rename");
+
+    let name = "oldfile";
+    let content = create_local_file(&test_dir, name);
+
+    let newname = "newfile";
+    let trename = Trenameat {
+        olddirfid: ROOT_FID,
+        oldname: String::from(name),
+        newdirfid: ROOT_FID,
+        newname: String::from(newname),
+    };
+
+    server.rename_at(trename).expect("failed to rename file");
+
+    assert!(!test_dir.join(name).exists());
+
+    let mut newcontent = Vec::with_capacity(content.len());
+    let size = File::open(test_dir.join(newname))
+        .expect("failed to open file")
+        .read_to_end(&mut newcontent)
+        .expect("failed to read new file content");
+    assert_eq!(size, content.len());
+    assert_eq!(newcontent, content);
+}
+
+fn setlk_tlock(fid: u32, len: u64, start: u64, type_: i32) -> Tlock {
+    Tlock {
+        fid,
+        type_: type_ as u8,
+        flags: 0,
+        start,
+        length: len,
+        proc_id: SERVER_PID,
+        client_id: String::from("test-server"),
+    }
+}
+
+fn getlk_tgetlock(fid: u32, type_: i32) -> Tgetlock {
+    Tgetlock {
+        fid,
+        type_: type_ as u8,
+        start: 0,
+        length: 0,
+        proc_id: SERVER_PID,
+        client_id: String::from("test-server"),
+    }
+}
+
+fn setup_simple_lock_no_open() -> Server {
+    let (test_dir, server) = setup("simple lock");
+
+    let filename = "file";
+    create_local_file(&test_dir, filename);
+
+    server
+}
+
+fn setup_simple_lock(flags: u32) -> Server {
+    let (test_dir, mut server) = setup("simple lock");
+
+    let filename = "file";
+    create_local_file(&test_dir, filename);
+
+    open(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        filename,
+        ROOT_FID + 1,
+        flags,
+    )
+    .expect("failed to open file");
+
+    server
+}
+
+#[test]
+fn lock_rdlck_no_open_file() {
+    let mut server = setup_simple_lock_no_open();
+
+    let tlock = setlk_tlock(ROOT_FID + 1, 8, 0, libc::F_RDLCK);
+
+    server.lock(&tlock).expect_err("Bad file descriptor");
+}
+
+#[test]
+fn lock_rdlck() {
+    let mut server = setup_simple_lock(P9_RDWR);
+
+    let tlock = setlk_tlock(ROOT_FID + 1, 8, 0, libc::F_RDLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+}
+#[test]
+fn lock_wrlck_no_open_file() {
+    let mut server = setup_simple_lock_no_open();
+
+    let tlock = setlk_tlock(ROOT_FID + 1, 8, 0, libc::F_WRLCK);
+
+    server.lock(&tlock).expect_err("Bad file descriptor");
+}
+#[test]
+fn lock_wrlck() {
+    let mut server = setup_simple_lock(P9_RDWR);
+
+    let tlock = setlk_tlock(ROOT_FID + 1, 8, 0, libc::F_WRLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+}
+
+#[test]
+fn lock_unlck_no_lock() {
+    let mut server = setup_simple_lock(P9_RDWR);
+
+    let tlock = setlk_tlock(ROOT_FID + 1, 0, 0, libc::F_UNLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+}
+
+#[test]
+fn lock_unlck() {
+    let mut server = setup_simple_lock(P9_RDWR);
+
+    let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_RDLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+
+    let tlock = setlk_tlock(ROOT_FID + 1, 0, 0, libc::F_UNLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+}
+
+#[test]
+fn lock_unlck_relock() {
+    let mut server = setup_simple_lock(P9_RDWR);
+
+    let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_RDLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+
+    let tlock = setlk_tlock(ROOT_FID + 1, 0, 0, libc::F_UNLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+
+    let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_RDLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+}
+
+#[test]
+fn getlock_rdlck_nolock() {
+    let mut server = setup_simple_lock(P9_RDWR);
+
+    let tgetlock = getlk_tgetlock(ROOT_FID + 1, libc::F_RDLCK);
+
+    server
+        .get_lock(&tgetlock)
+        .expect("failed to get lock on file");
+}
+
+#[test]
+fn getlock_wrlck() {
+    let mut server = setup_simple_lock(P9_RDWR);
+
+    let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_WRLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+
+    let tgetlock = getlk_tgetlock(ROOT_FID + 1, libc::F_WRLCK);
+
+    server
+        .get_lock(&tgetlock)
+        .expect("failed to get lock on file");
+}
+
+#[test]
+fn getlock_rdlck() {
+    let mut server = setup_simple_lock(P9_RDWR);
+
+    let tlock = setlk_tlock(ROOT_FID + 1, LOCAL_FILE_LEN / 2, 0, libc::F_RDLCK);
+
+    server.lock(&tlock).expect("failed to lock file");
+
+    let tgetlock = getlk_tgetlock(ROOT_FID + 1, libc::F_RDLCK);
+
+    server
+        .get_lock(&tgetlock)
+        .expect("failed to get lock on file");
+}
+
+macro_rules! open_test {
+    ($name:ident, $flags:expr) => {
+        #[test]
+        fn $name() {
+            let (test_dir, mut server) = setup("open");
+
+            let fid = ROOT_FID + 1;
+            let name = "test.txt";
+            let content = create_local_file(&test_dir, name);
+
+            let rlopen = open(&mut server, &*test_dir, ROOT_FID, name, fid, $flags as u32)
+                .expect("failed to open file");
+
+            let md =
+                fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file");
+            check_qid(&rlopen.qid, &md);
+            assert_eq!(rlopen.iounit, 0);
+
+            check_attr(&mut server, fid, &md);
+
+            // Check that the file has the proper contents as long as we didn't
+            // truncate it first.
+            if $flags & P9_TRUNC == 0 && $flags & P9_WRONLY == 0 {
+                check_content(&mut server, &content, fid);
+            }
+
+            // Check that we can write to the file.
+            if $flags & P9_RDWR != 0 || $flags & P9_WRONLY != 0 {
+                write(&mut server, &test_dir, name, fid, $flags);
+            }
+
+            let tclunk = Tclunk { fid };
+            server.clunk(&tclunk).expect("Unable to clunk file");
+        }
+    };
+    ($name:ident, $flags:expr, $expected_err:expr) => {
+        #[test]
+        fn $name() {
+            let (test_dir, mut server) = setup("open_fail");
+
+            let fid = ROOT_FID + 1;
+            let name = "test.txt";
+            create_local_file(&test_dir, name);
+
+            let err = open(&mut server, &*test_dir, ROOT_FID, name, fid, $flags as u32)
+                .expect_err("successfully opened file");
+            assert_eq!(err.kind(), $expected_err);
+
+            let tclunk = Tclunk { fid };
+            server.clunk(&tclunk).expect("Unable to clunk file");
+        }
+    };
+}
+
+open_test!(read_only_file_open, P9_RDONLY);
+open_test!(read_write_file_open, P9_RDWR);
+open_test!(write_only_file_open, P9_WRONLY);
+
+open_test!(create_read_only_file_open, P9_CREATE | P9_RDONLY);
+open_test!(create_read_write_file_open, P9_CREATE | P9_RDWR);
+open_test!(create_write_only_file_open, P9_CREATE | P9_WRONLY);
+
+open_test!(append_read_only_file_open, P9_APPEND | P9_RDONLY);
+open_test!(append_read_write_file_open, P9_APPEND | P9_RDWR);
+open_test!(append_write_only_file_open, P9_APPEND | P9_WRONLY);
+
+open_test!(trunc_read_only_file_open, P9_TRUNC | P9_RDONLY);
+open_test!(trunc_read_write_file_open, P9_TRUNC | P9_RDWR);
+open_test!(trunc_write_only_file_open, P9_TRUNC | P9_WRONLY);
+
+open_test!(
+    create_append_read_only_file_open,
+    P9_CREATE | P9_APPEND | P9_RDONLY
+);
+open_test!(
+    create_append_read_write_file_open,
+    P9_CREATE | P9_APPEND | P9_RDWR
+);
+open_test!(
+    create_append_wronly_file_open,
+    P9_CREATE | P9_APPEND | P9_WRONLY
+);
+
+open_test!(
+    create_trunc_read_only_file_open,
+    P9_CREATE | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+    create_trunc_read_write_file_open,
+    P9_CREATE | P9_TRUNC | P9_RDWR
+);
+open_test!(
+    create_trunc_wronly_file_open,
+    P9_CREATE | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+    append_trunc_read_only_file_open,
+    P9_APPEND | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+    append_trunc_read_write_file_open,
+    P9_APPEND | P9_TRUNC | P9_RDWR
+);
+open_test!(
+    append_trunc_wronly_file_open,
+    P9_APPEND | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+    create_append_trunc_read_only_file_open,
+    P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+    create_append_trunc_read_write_file_open,
+    P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDWR
+);
+open_test!(
+    create_append_trunc_wronly_file_open,
+    P9_CREATE | P9_APPEND | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+    create_excl_read_only_file_open,
+    P9_CREATE | P9_EXCL | P9_RDONLY,
+    io::ErrorKind::AlreadyExists
+);
+open_test!(
+    create_excl_read_write_file_open,
+    P9_CREATE | P9_EXCL | P9_RDWR,
+    io::ErrorKind::AlreadyExists
+);
+open_test!(
+    create_excl_wronly_file_open,
+    P9_CREATE | P9_EXCL | P9_WRONLY,
+    io::ErrorKind::AlreadyExists
+);
+
+macro_rules! create_test {
+    ($name:ident, $flags:expr, $mode:expr) => {
+        #[test]
+        fn $name() {
+            let (test_dir, mut server) = setup("create");
+
+            let name = "foo.txt";
+            let fid = ROOT_FID + 1;
+            let rlcreate = create(&mut server, &*test_dir, ROOT_FID, fid, name, $flags, $mode)
+                .expect("failed to create file");
+
+            let md =
+                fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file");
+            assert_eq!(rlcreate.iounit, 0);
+            check_qid(&rlcreate.qid, &md);
+            check_attr(&mut server, fid, &md);
+
+            // Check that we can write to the file.
+            if $flags & P9_RDWR != 0 || $flags & P9_WRONLY != 0 {
+                write(&mut server, &test_dir, name, fid, $flags);
+            }
+
+            let tclunk = Tclunk { fid };
+            server.clunk(&tclunk).expect("Unable to clunk file");
+        }
+    };
+    ($name:ident, $flags:expr, $mode:expr, $expected_err:expr) => {
+        #[test]
+        fn $name() {
+            let (test_dir, mut server) = setup("create_fail");
+
+            let name = "foo.txt";
+            // The `fid` in the lcreate call initially points to the directory
+            // but is supposed to point to the newly created file after the call
+            // completes.  Duplicate the fid so that we don't end up consuming the
+            // root fid.
+            let fid = ROOT_FID + 1;
+            let err = create(&mut server, &*test_dir, ROOT_FID, fid, name, $flags, $mode)
+                .expect_err("successfully created file");
+            assert_eq!(err.kind(), $expected_err);
+        }
+    };
+}
+
+create_test!(read_only_file_create, P9_RDONLY, 0o600u32);
+create_test!(read_write_file_create, P9_RDWR, 0o600u32);
+create_test!(write_only_file_create, P9_WRONLY, 0o600u32);
+
+create_test!(
+    append_read_only_file_create,
+    P9_APPEND | P9_RDONLY,
+    0o600u32
+);
+create_test!(append_read_write_file_create, P9_APPEND | P9_RDWR, 0o600u32);
+create_test!(append_wronly_file_create, P9_APPEND | P9_WRONLY, 0o600u32);
+
+#[test]
+fn lcreate_set_len() {
+    let (test_dir, mut server) = setup("lcreate_set_len");
+
+    let name = "foo.txt";
+    let fid = ROOT_FID + 1;
+    create(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        fid,
+        name,
+        P9_RDWR,
+        0o600u32,
+    )
+    .expect("failed to create file");
+
+    let tsetattr = Tsetattr {
+        fid,
+        valid: 0x8, // P9_SETATTR_SIZE
+        size: 100,
+        // The other fields are not used because the relevant flags aren't set in `valid`.
+        mode: 0,
+        uid: 0,
+        gid: 0,
+        atime_sec: 0,
+        atime_nsec: 0,
+        mtime_sec: 0,
+        mtime_nsec: 0,
+    };
+    server
+        .set_attr(&tsetattr)
+        .expect("failed to set file length after lcreate");
+
+    let tclunk = Tclunk { fid };
+    server.clunk(&tclunk).expect("Unable to clunk file");
+}
+
+#[test]
+fn readlink() {
+    let (test_dir, mut server) = setup("readlink");
+    create_local_symlink(&test_dir, "symlink", "target/of/symlink");
+
+    let fid = ROOT_FID + 1;
+    walk(
+        &mut server,
+        &*test_dir,
+        ROOT_FID,
+        fid,
+        vec!["symlink".into()],
+    );
+
+    let treadlink = Treadlink { fid };
+
+    let rreadlink = server.readlink(&treadlink).expect("failed to readlink");
+
+    assert_eq!(rreadlink.target, "target/of/symlink");
+}