summary refs log tree commit diff
path: root/p9
diff options
context:
space:
mode:
authorChirantan Ekbote <chirantan@chromium.org>2018-04-04 18:16:43 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-06-15 10:56:39 -0700
commitb657603f10daf62fa6fe18d56adbe35f6caf8587 (patch)
tree31148a8e1d69f1e8a3c5ed60748498e09e9617a6 /p9
parenta7b0a712042dcc60baf88a48d1220633ed4a192f (diff)
downloadcrosvm-b657603f10daf62fa6fe18d56adbe35f6caf8587.tar
crosvm-b657603f10daf62fa6fe18d56adbe35f6caf8587.tar.gz
crosvm-b657603f10daf62fa6fe18d56adbe35f6caf8587.tar.bz2
crosvm-b657603f10daf62fa6fe18d56adbe35f6caf8587.tar.lz
crosvm-b657603f10daf62fa6fe18d56adbe35f6caf8587.tar.xz
crosvm-b657603f10daf62fa6fe18d56adbe35f6caf8587.tar.zst
crosvm-b657603f10daf62fa6fe18d56adbe35f6caf8587.zip
p9: Add server implementation
Implement all the server methods for the 9P protocol.

BUG=chromium:703939
TEST=bonnie++ -r 256

Change-Id: I6b1b5fe4fea4d4941db42e5c1a364a54d0827054
Signed-off-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1013203
Reviewed-by: Stephen Barber <smbarber@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to 'p9')
-rw-r--r--p9/Cargo.toml4
-rw-r--r--p9/src/lib.rs9
-rw-r--r--p9/src/server.rs940
3 files changed, 948 insertions, 5 deletions
diff --git a/p9/Cargo.toml b/p9/Cargo.toml
index 674df06..964cd1c 100644
--- a/p9/Cargo.toml
+++ b/p9/Cargo.toml
@@ -4,4 +4,8 @@ version = "0.1.0"
 authors = ["The Chromium OS Authors"]
 
 [dependencies]
+libc = "*"
 wire_format_derive = { path = "wire_format_derive" }
+
+[features]
+trace = []
\ No newline at end of file
diff --git a/p9/src/lib.rs b/p9/src/lib.rs
index 80f74d8..ef0744e 100644
--- a/p9/src/lib.rs
+++ b/p9/src/lib.rs
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+extern crate libc;
+
 #[macro_use]
 extern crate wire_format_derive;
 
 mod protocol;
+mod server;
 
-#[derive(P9WireFormat)]
-struct Test {
-    a: u32,
-    b: u16,
-}
+pub use server::Server;
diff --git a/p9/src/server.rs b/p9/src/server.rs
new file mode 100644
index 0000000..ee7266a
--- /dev/null
+++ b/p9/src/server.rs
@@ -0,0 +1,940 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use libc;
+
+use std::cmp::min;
+use std::collections::{btree_map, BTreeMap};
+use std::ffi::CString;
+use std::fs;
+use std::io::{self, Cursor, Read, Write};
+use std::mem;
+use std::os::linux::fs::MetadataExt;
+use std::os::unix::fs::{DirBuilderExt, FileExt, OpenOptionsExt};
+use std::os::unix::io::AsRawFd;
+use std::path::{Component, Path, PathBuf};
+
+use protocol::*;
+
+// 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); 10] = [
+    (P9_NOCTTY, libc::O_NOCTTY),
+    (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;
+
+// Minimum and maximum message size that we'll expect from the client.
+const MIN_MESSAGE_SIZE: u32 = 256;
+const MAX_MESSAGE_SIZE: u32 = ::std::u16::MAX as u32;
+
+// 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: Box<Path>,
+    metadata: fs::Metadata,
+    file: Option<fs::File>,
+    dirents: Option<Vec<Dirent>>,
+}
+
+fn metadata_to_qid(metadata: &fs::Metadata) -> Qid {
+    let ty = if metadata.is_dir() {
+        P9_QTDIR
+    } else if metadata.is_file() {
+        P9_QTFILE
+    } else {
+        // Unknown file type...
+        0
+    };
+
+    Qid {
+        ty: ty,
+        // TODO: deal with the 2038 problem before 2038
+        version: metadata.st_mtime() as u32,
+        path: metadata.st_ino(),
+    }
+}
+
+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,
+    })
+}
+
+// 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)
+}
+
+pub struct Server {
+    root: Box<Path>,
+    msize: u32,
+    fids: BTreeMap<u32, Fid>,
+}
+
+impl Server {
+    pub fn new<P: AsRef<Path>>(root: P) -> Server {
+        Server {
+            root: root.as_ref().into(),
+            msize: MAX_MESSAGE_SIZE,
+            fids: BTreeMap::new(),
+        }
+    }
+
+    pub fn handle_message<R: Read, W: Write>(
+        &mut self,
+        reader: &mut R,
+        writer: &mut W,
+    ) -> io::Result<()> {
+        let request: Tframe = WireFormat::decode(&mut reader.take(self.msize as u64))?;
+
+        if cfg!(feature = "trace") {
+            println!("{:?}", &request);
+        }
+
+        let rmsg = match request.msg {
+            Tmessage::Version(ref version) => self.version(version),
+            Tmessage::Flush(ref flush) => self.flush(flush),
+            Tmessage::Walk(ref walk) => self.walk(walk),
+            Tmessage::Read(ref read) => self.read(read),
+            Tmessage::Write(ref write) => self.write(write),
+            Tmessage::Clunk(ref clunk) => self.clunk(clunk),
+            Tmessage::Remove(ref remove) => self.remove(remove),
+            Tmessage::Attach(ref attach) => self.attach(attach),
+            Tmessage::Auth(ref auth) => self.auth(auth),
+            Tmessage::Statfs(ref statfs) => self.statfs(statfs),
+            Tmessage::Lopen(ref lopen) => self.lopen(lopen),
+            Tmessage::Lcreate(ref lcreate) => self.lcreate(lcreate),
+            Tmessage::Symlink(ref symlink) => self.symlink(symlink),
+            Tmessage::Mknod(ref mknod) => self.mknod(mknod),
+            Tmessage::Rename(ref rename) => self.rename(rename),
+            Tmessage::Readlink(ref readlink) => self.readlink(readlink),
+            Tmessage::GetAttr(ref get_attr) => self.get_attr(get_attr),
+            Tmessage::SetAttr(ref set_attr) => self.set_attr(set_attr),
+            Tmessage::XattrWalk(ref xattr_walk) => self.xattr_walk(xattr_walk),
+            Tmessage::XattrCreate(ref xattr_create) => self.xattr_create(xattr_create),
+            Tmessage::Readdir(ref readdir) => self.readdir(readdir),
+            Tmessage::Fsync(ref fsync) => self.fsync(fsync),
+            Tmessage::Lock(ref lock) => self.lock(lock),
+            Tmessage::GetLock(ref get_lock) => self.get_lock(get_lock),
+            Tmessage::Link(ref link) => self.link(link),
+            Tmessage::Mkdir(ref mkdir) => self.mkdir(mkdir),
+            Tmessage::RenameAt(ref rename_at) => self.rename_at(rename_at),
+            Tmessage::UnlinkAt(ref unlink_at) => self.unlink_at(unlink_at),
+        };
+
+        // Errors while handling requests are never fatal.
+        let response = Rframe {
+            tag: request.tag,
+            msg: rmsg.unwrap_or_else(error_to_rmessage),
+        };
+
+        if cfg!(feature = "trace") {
+            println!("{:?}", &response);
+        }
+
+        response.encode(writer)?;
+        writer.flush()
+    }
+
+    fn auth(&mut self, _auth: &Tauth) -> io::Result<Rmessage> {
+        // 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<Rmessage> {
+        // TODO: Check attach parameters
+        match self.fids.entry(attach.fid) {
+            btree_map::Entry::Vacant(entry) => {
+                let fid = Fid {
+                    path: self.root.to_path_buf().into_boxed_path(),
+                    metadata: fs::metadata(&self.root)?,
+                    file: None,
+                    dirents: None,
+                };
+                let response = Rattach {
+                    qid: metadata_to_qid(&fid.metadata),
+                };
+                entry.insert(fid);
+                Ok(Rmessage::Attach(response))
+            }
+            btree_map::Entry::Occupied(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
+        }
+    }
+
+    fn version(&mut self, version: &Tversion) -> io::Result<Rmessage> {
+        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.msize = min(MAX_MESSAGE_SIZE, version.msize);
+
+        Ok(Rmessage::Version(Rversion {
+            msize: self.msize,
+            version: if version.version == "9P2000.L" {
+                String::from("9P2000.L")
+            } else {
+                String::from("unknown")
+            },
+        }))
+    }
+
+    fn flush(&mut self, _flush: &Tflush) -> io::Result<Rmessage> {
+        // TODO: Since everything is synchronous we can't actually flush requests.
+        Ok(Rmessage::Flush)
+    }
+
+    fn do_walk(
+        &self,
+        wnames: &[String],
+        mut buf: PathBuf,
+        mds: &mut Vec<fs::Metadata>,
+    ) -> io::Result<PathBuf> {
+        for wname in wnames {
+            let name = Path::new(wname);
+            buf = join_path(buf, name, &*self.root)?;
+            mds.push(fs::metadata(&buf)?);
+        }
+
+        Ok(buf)
+    }
+
+    fn walk(&mut self, walk: &Twalk) -> io::Result<Rmessage> {
+        // `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 (buf, oldmd) = self.fids
+            .get(&walk.fid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))
+            .map(|fid| (fid.path.to_path_buf(), fid.metadata.clone()))?;
+
+        // Now walk the tree and break on the first error, if any.
+        let mut mds = Vec::with_capacity(walk.wnames.len());
+        match self.do_walk(&walk.wnames, buf, &mut mds) {
+            Ok(buf) => {
+                // Store the new fid if the full walk succeeded.
+                if mds.len() == walk.wnames.len() {
+                    // This could just be a duplication operation.
+                    let md = if let Some(md) = mds.last() {
+                        md.clone()
+                    } else {
+                        oldmd
+                    };
+
+                    self.fids.insert(
+                        walk.newfid,
+                        Fid {
+                            path: buf.into_boxed_path(),
+                            metadata: md,
+                            file: None,
+                            dirents: None,
+                        },
+                    );
+                }
+            }
+            Err(e) => {
+                // Only return an error if it occurred on the first component.
+                if mds.is_empty() {
+                    return Err(e);
+                }
+            }
+        }
+
+        Ok(Rmessage::Walk(Rwalk {
+            wqids: mds.iter().map(metadata_to_qid).collect(),
+        }))
+    }
+
+    fn read(&mut self, read: &Tread) -> io::Result<Rmessage> {
+        // 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(|| io::Error::from_raw_os_error(libc::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 count = min(self.msize - header_size, read.count);
+        let mut buf = Data(Vec::with_capacity(count as usize));
+
+        // Safe because `buf` is guaranteed to have a capacity of `read.count`.  We do
+        // this because we don't want to spend time zero-initializing a potentially
+        // large buffer.
+        unsafe {
+            buf.set_len(read.count as usize);
+        }
+        let count = file.read_at(&mut buf, read.offset)?;
+
+        // Safe because read_at guarantees that `count` bytes have been written
+        // into `buf` and that `count` is less than or equal to `buf.len()`.
+        unsafe {
+            buf.set_len(count);
+        }
+        Ok(Rmessage::Read(Rread { data: buf }))
+    }
+
+    fn write(&mut self, write: &Twrite) -> io::Result<Rmessage> {
+        let file = self.fids
+            .get_mut(&write.fid)
+            .and_then(|fid| fid.file.as_mut())
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+
+        let count = file.write_at(&write.data, write.offset)?;
+        Ok(Rmessage::Write(Rwrite {
+            count: count as u32,
+        }))
+    }
+
+    fn clunk(&mut self, clunk: &Tclunk) -> io::Result<Rmessage> {
+        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(Rmessage::Clunk)
+            }
+        }
+    }
+
+    fn remove(&mut self, remove: &Tremove) -> io::Result<Rmessage> {
+        match self.fids.entry(remove.fid) {
+            btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
+            btree_map::Entry::Occupied(o) => {
+                let (_, fid) = o.remove_entry();
+
+                if fid.metadata.is_dir() {
+                    fs::remove_dir(&fid.path)?;
+                } else {
+                    fs::remove_file(&fid.path)?;
+                }
+
+                Ok(Rmessage::Remove)
+            }
+        }
+    }
+
+    fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rmessage> {
+        let fid = self.fids
+            .get(&statfs.fid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+        let path = fid.path
+            .to_str()
+            .and_then(|path| CString::new(path).ok())
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
+
+        // Safe because we are zero-initializing a C struct with only primitive
+        // data members.
+        let mut out: libc::statfs64 = unsafe { mem::zeroed() };
+
+        // Safe because we know that `path` is valid and we have already initialized `out`.
+        let ret = unsafe { libc::statfs64(path.as_ptr(), &mut out) };
+        if ret != 0 {
+            return Err(io::Error::last_os_error());
+        }
+
+        Ok(Rmessage::Statfs(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,
+            fsid: 0, // No way to get the fields of a libc::fsid_t
+            namelen: out.f_namelen as u32,
+        }))
+    }
+
+    fn lopen(&mut self, lopen: &Tlopen) -> io::Result<Rmessage> {
+        let fid = self.fids
+            .get_mut(&lopen.fid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+        // We always open files with O_CLOEXEC.
+        let mut custom_flags: i32 = libc::O_CLOEXEC;
+        for &(p9f, of) in &MAPPED_FLAGS {
+            if (lopen.flags & p9f) != 0 {
+                custom_flags |= of;
+            }
+        }
+
+        let file = fs::OpenOptions::new()
+            .read((lopen.flags & P9_NOACCESS) == 0 || (lopen.flags & P9_RDWR) != 0)
+            .write((lopen.flags & P9_WRONLY) != 0 || (lopen.flags & P9_RDWR) != 0)
+            .append((lopen.flags & P9_APPEND) != 0)
+            .truncate((lopen.flags & P9_TRUNC) != 0)
+            .create((lopen.flags & P9_CREATE) != 0)
+            .create_new((lopen.flags & P9_CREATE) != 0 && (lopen.flags & P9_EXCL) != 0)
+            .custom_flags(custom_flags)
+            .open(&fid.path)?;
+
+        fid.metadata = file.metadata()?;
+        fid.file = Some(file);
+
+        Ok(Rmessage::Lopen(Rlopen {
+            qid: metadata_to_qid(&fid.metadata),
+            iounit: 0,
+        }))
+    }
+
+    fn lcreate(&mut self, lcreate: &Tlcreate) -> io::Result<Rmessage> {
+        let fid = self.fids
+            .get_mut(&lcreate.fid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+
+        if !fid.metadata.is_dir() {
+            return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
+        }
+
+        let name = Path::new(&lcreate.name);
+        let path = join_path(fid.path.to_path_buf(), name, &*self.root)?;
+
+        let mut custom_flags: i32 = libc::O_CLOEXEC;
+        for &(p9f, of) in &MAPPED_FLAGS {
+            if (lcreate.flags & p9f) != 0 {
+                custom_flags |= of;
+            }
+        }
+
+        let file = fs::OpenOptions::new()
+            .read(false)
+            .write(true)
+            .truncate(true)
+            .create(true)
+            .append((lcreate.flags & P9_APPEND) != 0)
+            .create_new((lcreate.flags & P9_EXCL) != 0)
+            .custom_flags(custom_flags)
+            .mode(lcreate.mode & 0o777)
+            .open(&path)?;
+
+        fid.metadata = file.metadata()?;
+        fid.file = Some(file);
+        fid.path = path.into_boxed_path();
+
+        Ok(Rmessage::Lcreate(Rlcreate {
+            qid: metadata_to_qid(&fid.metadata),
+            iounit: 0,
+        }))
+    }
+
+    fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rmessage> {
+        // symlinks are not allowed.
+        Err(io::Error::from_raw_os_error(libc::EACCES))
+    }
+
+    fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmessage> {
+        // No nodes either.
+        Err(io::Error::from_raw_os_error(libc::EACCES))
+    }
+
+    fn rename(&mut self, rename: &Trename) -> io::Result<Rmessage> {
+        let newname = Path::new(&rename.name);
+        let buf = self.fids
+            .get(&rename.dfid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))
+            .map(|dfid| dfid.path.to_path_buf())?;
+        let newpath = join_path(buf, newname, &*self.root)?;
+
+        let fid = self.fids
+            .get_mut(&rename.fid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
+
+        fs::rename(&fid.path, &newpath)?;
+
+        // TODO: figure out if the client expects |fid.path| to point to
+        // the renamed path.
+        fid.path = newpath.into_boxed_path();
+        Ok(Rmessage::Rename)
+    }
+
+    fn readlink(&mut self, _readlink: &Treadlink) -> io::Result<Rmessage> {
+        // symlinks are not allowed
+        Err(io::Error::from_raw_os_error(libc::EACCES))
+    }
+
+    fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rmessage> {
+        let fid = self.fids
+            .get_mut(&get_attr.fid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+
+        // Refresh the metadata since we were explicitly asked for it.
+        fid.metadata = fs::metadata(&fid.path)?;
+
+        Ok(Rmessage::GetAttr(Rgetattr {
+            valid: P9_GETATTR_BASIC,
+            qid: metadata_to_qid(&fid.metadata),
+            mode: fid.metadata.st_mode(),
+            uid: fid.metadata.st_uid(),
+            gid: fid.metadata.st_gid(),
+            nlink: fid.metadata.st_nlink(),
+            rdev: fid.metadata.st_rdev(),
+            size: fid.metadata.st_size(),
+            blksize: fid.metadata.st_blksize(),
+            blocks: fid.metadata.st_blocks(),
+            atime_sec: fid.metadata.st_atime() as u64,
+            atime_nsec: fid.metadata.st_atime_nsec() as u64,
+            mtime_sec: fid.metadata.st_mtime() as u64,
+            mtime_nsec: fid.metadata.st_mtime_nsec() as u64,
+            ctime_sec: fid.metadata.st_ctime() as u64,
+            ctime_nsec: fid.metadata.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<Rmessage> {
+        let blocked_ops = P9_SETATTR_MODE | P9_SETATTR_UID | P9_SETATTR_GID;
+        if set_attr.valid & blocked_ops != 0 {
+            return Err(io::Error::from_raw_os_error(libc::EPERM));
+        }
+
+        let fid = self.fids
+            .get_mut(&set_attr.fid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+        let file = fs::OpenOptions::new().write(true).open(&fid.path)?;
+
+        if set_attr.valid & P9_SETATTR_SIZE != 0 {
+            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::futimens(file.as_raw_fd(), &times as *const libc::timespec) };
+            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::fchown(
+                    file.as_raw_fd(),
+                    libc::uid_t::max_value(),
+                    libc::gid_t::max_value(),
+                )
+            };
+            if ret < 0 {
+                return Err(io::Error::last_os_error());
+            }
+        }
+
+        Ok(Rmessage::SetAttr)
+    }
+
+    fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rmessage> {
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<Rmessage> {
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rmessage> {
+        let fid = self.fids
+            .get_mut(&readdir.fid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+
+        if !fid.metadata.is_dir() {
+            return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
+        }
+
+        // The p9 client implementation in the kernel doesn't fully read all the contents
+        // of the directory.  This means that if some application performs a getdents()
+        // call, followed by removing some files, followed by another getdents() call,
+        // the offset that we get from the kernel is completely meaningless.  Instead
+        // we fully read the contents of the directory here and only re-read the directory
+        // if the offset we get from the client is 0.  Any other offset is served from the
+        // directory entries in memory.  This ensures consistency even if the directory
+        // changes in between Treaddir messages.
+        if readdir.offset == 0 {
+            let mut offset = 0;
+            let iter = fs::read_dir(&fid.path)?;
+            let dirents = iter.map(|item| -> io::Result<Dirent> {
+                let entry = item?;
+
+                let md = entry.metadata()?;
+                let qid = metadata_to_qid(&md);
+
+                let ty = if md.is_dir() {
+                    libc::DT_DIR
+                } else if md.is_file() {
+                    libc::DT_REG
+                } else {
+                    libc::DT_UNKNOWN
+                };
+
+                let name = entry
+                    .file_name()
+                    .into_string()
+                    .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
+
+                let mut out = Dirent {
+                    qid: qid,
+                    offset: 0, // set below
+                    ty: ty,
+                    name: name,
+                };
+
+                offset += out.byte_size() as u64;
+                out.offset = offset;
+
+                Ok(out)
+            });
+
+            // This is taking advantage of the fact that we can turn a Iterator of Result<T, E>
+            // into a Result<FromIterator<T>, E> since Result implements FromIterator<Result<T, E>>.
+            fid.dirents = Some(dirents.collect::<io::Result<Vec<Dirent>>>()?);
+        }
+
+        let mut entries = fid.dirents
+            .as_ref()
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?
+            .iter()
+            .skip_while(|entry| entry.offset <= readdir.offset)
+            .peekable();
+
+        // 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.msize - header_size, readdir.count);
+        let mut cursor = Cursor::new(Vec::with_capacity(count as usize));
+
+        loop {
+            let byte_size = if let Some(entry) = entries.peek() {
+                entry.byte_size() as usize
+            } else {
+                // No more entries.
+                break;
+            };
+
+            if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size {
+                // No more room in the buffer.
+                break;
+            }
+
+            // Safe because we just checked that the iterator contains at least one more item.
+            entries.next().unwrap().encode(&mut cursor)?;
+        }
+
+        Ok(Rmessage::Readdir(Rreaddir {
+            data: Data(cursor.into_inner()),
+        }))
+    }
+
+    fn fsync(&mut self, fsync: &Tfsync) -> io::Result<Rmessage> {
+        let file = self.fids
+            .get(&fsync.fid)
+            .and_then(|fid| fid.file.as_ref())
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+
+        if fsync.datasync == 0 {
+            file.sync_all()?;
+        } else {
+            file.sync_data()?;
+        }
+        Ok(Rmessage::Fsync)
+    }
+
+    fn lock(&mut self, _lock: &Tlock) -> io::Result<Rmessage> {
+        // File locking is not supported.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+    fn get_lock(&mut self, _get_lock: &Tgetlock) -> io::Result<Rmessage> {
+        // File locking is not supported.
+        Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+    }
+
+    fn link(&mut self, link: &Tlink) -> io::Result<Rmessage> {
+        let newname = Path::new(&link.name);
+        let buf = self.fids
+            .get(&link.dfid)
+            .map(|dfid| dfid.path.to_path_buf())
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+        let newpath = join_path(buf, newname, &*self.root)?;
+
+        let path = self.fids
+            .get(&link.fid)
+            .map(|fid| &fid.path)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+
+        fs::hard_link(path, &newpath)?;
+        Ok(Rmessage::Link)
+    }
+
+    fn mkdir(&mut self, mkdir: &Tmkdir) -> io::Result<Rmessage> {
+        let fid = self.fids
+            .get(&mkdir.dfid)
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+
+        let name = Path::new(&mkdir.name);
+        let newpath = join_path(fid.path.to_path_buf(), name, &*self.root)?;
+
+        fs::DirBuilder::new()
+            .recursive(false)
+            .mode(mkdir.mode & 0o777)
+            .create(&newpath)?;
+
+        Ok(Rmessage::Mkdir(Rmkdir {
+            qid: metadata_to_qid(&fs::metadata(&newpath)?),
+        }))
+    }
+
+    fn rename_at(&mut self, rename_at: &Trenameat) -> io::Result<Rmessage> {
+        let oldname = Path::new(&rename_at.oldname);
+        let oldbuf = self.fids
+            .get(&rename_at.olddirfid)
+            .map(|dfid| dfid.path.to_path_buf())
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+        let oldpath = join_path(oldbuf, oldname, &*self.root)?;
+
+        let newname = Path::new(&rename_at.newname);
+        let newbuf = self.fids
+            .get(&rename_at.newdirfid)
+            .map(|dfid| dfid.path.to_path_buf())
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+        let newpath = join_path(newbuf, newname, &*self.root)?;
+
+        fs::rename(&oldpath, &newpath)?;
+        Ok(Rmessage::RenameAt)
+    }
+
+    fn unlink_at(&mut self, unlink_at: &Tunlinkat) -> io::Result<Rmessage> {
+        let name = Path::new(&unlink_at.name);
+        let buf = self.fids
+            .get(&unlink_at.dirfd)
+            .map(|fid| fid.path.to_path_buf())
+            .ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
+        let path = join_path(buf, name, &*self.root)?;
+
+        let md = fs::metadata(&path)?;
+        if md.is_dir() && (unlink_at.flags & (libc::AT_REMOVEDIR as u32)) == 0 {
+            return Err(io::Error::from_raw_os_error(libc::EISDIR));
+        }
+
+        if md.is_dir() {
+            fs::remove_dir(&path)?;
+        } else {
+            fs::remove_file(&path)?;
+        }
+
+        Ok(Rmessage::UnlinkAt)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    // Most of the server implementation is tested via integration tests.
+    use super::*;
+
+    #[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.clone(), "..", &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.clone(), "/c/d/e", &root).expect_err("absolute path");
+    }
+}