summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlistair Delva <adelva@google.com>2020-10-22 10:35:12 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-10-22 10:35:12 +0000
commit44a66c13604a1f1414641d1ea927b34a82d86ef7 (patch)
tree9918ff34d4bf62f4a02ea8f4ea57cf97cfe8250b
parentb2f28fa79752251d3214d1ffa566af57803ba769 (diff)
parent082a1c3c35b054d7ee9b92186a64a7c9a21c1707 (diff)
downloadp9-44a66c13604a1f1414641d1ea927b34a82d86ef7.tar.gz
Initial import of p9 rust crate am: 376f07e4cc am: 3416efc4d8 am: 082a1c3c35
Original change: https://android-review.googlesource.com/c/platform/external/vm_tools/p9/+/1468198 Change-Id: Ib4699fa37bae1ce40de9bf60690829f3501ce04a
-rw-r--r--.clang-format19
-rw-r--r--.gitignore71
-rw-r--r--Cargo.toml12
-rw-r--r--LICENSE27
-rw-r--r--METADATA14
-rw-r--r--MODULE_LICENSE_BSD0
-rw-r--r--OWNERS3
-rw-r--r--README.md21
-rw-r--r--fuzz/Cargo.toml19
-rw-r--r--fuzz/tframe_decode.rs12
-rw-r--r--src/fuzzing.rs13
-rw-r--r--src/lib.rs16
-rw-r--r--src/protocol/messages.rs840
-rw-r--r--src/protocol/mod.rs9
-rw-r--r--src/protocol/wire_format.rs707
-rw-r--r--src/server/mod.rs937
-rw-r--r--src/server/tests.rs1081
-rw-r--r--wire_format_derive/Cargo.toml13
-rw-r--r--wire_format_derive/wire_format_derive.rs303
19 files changed, 4117 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..4fdfdbe
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,19 @@
+# Copyright 2017 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.
+
+# Defines the Chromium OS style for automatic reformatting.
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+# Please keep all directives after this one sorted alphabetically.
+BasedOnStyle: Chromium
+
+# This is permitted by the Google and Chromium style guides, and existing code
+# uses it heavily.
+AllowAllParametersOfDeclarationOnNextLine: true
+
+# NOLINT(reason) is used heavily by existing code.
+CommentPragmas: 'NOLINT:.*'
+
+# cpplint.py does smarter #include sorting than clang-format (the former ignores
+# case and changes '-' to '_').
+SortIncludes: false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0c60586
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,71 @@
+#
+# NOTE! Please use 'git ls-files -i --exclude-standard'
+# command after changing this file, to see if there are
+# any tracked files which get ignored after the change.
+#
+
+# Temp files (e.g. editors).
+*~
+*.sw[op]
+
+# Compiled objects.
+*.a
+*.o
+*.l[ao]
+*.so
+*.exe
+
+*.d
+*.depends
+.deps
+.libs
+
+*.gch
+*.gcda
+*.gcno
+
+# Common output files.
+*.dump
+*.out
+*.test
+
+# Protobuf files.
+*.pb.cc
+*.pb.h
+
+# Python files.
+*.pyc
+
+# Debug (e.g. gdb).
+.gdb_history
+.gdbinit
+
+core
+cscope.*
+tags
+tags_sorted_by_file
+
+# Patch files.
+*.diff
+*.patch
+*.orig
+*.rej
+
+# Nested git repos.
+/glbench/images/
+
+# Cargo lock file.
+Cargo.lock
+
+# Cargo build directories.
+target/
+
+# VSCode source dirs
+.vscode/
+
+# Intellij project dirs
+.idea
+
+# clangd cache dir
+.clangd
+.cache
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..abb7a45
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "p9"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[dependencies]
+libc = "*"
+wire_format_derive = { path = "wire_format_derive", version = "*" }
+
+[features]
+trace = []
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b9e779f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+//
+// 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..0feb0d2
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,14 @@
+name: "p9"
+description:
+ "Server implementation of the [9p] file system protocol. Taken from "
+ "chromium/platform2, vm_tools/p9."
+
+third_party {
+ url {
+ type: GIT
+ value: "https://chromium.googlesource.com/chromiumos/platform2"
+ }
+ version: "8739fdb7d25a53496536f328d2675824c7199e17"
+ last_upgrade_date { year: 2020 month: 10 day: 6 }
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..566eabb
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+adelva@google.com
+chirantan@google.com
+dgreid@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b85cc15
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# 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/fuzz/Cargo.toml b/fuzz/Cargo.toml
new file mode 100644
index 0000000..1220681
--- /dev/null
+++ b/fuzz/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "p9-fuzz"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[dependencies]
+p9 = { path = "../" }
+cros_fuzz = "*"
+
+[workspace]
+members = ["."]
+
+[[bin]]
+name = "p9_tframe_decode_fuzzer"
+path = "tframe_decode.rs"
+
+[patch.crates-io]
+wire_format_derive = { path = "../wire_format_derive" } \ No newline at end of file
diff --git a/fuzz/tframe_decode.rs b/fuzz/tframe_decode.rs
new file mode 100644
index 0000000..6cdeae3
--- /dev/null
+++ b/fuzz/tframe_decode.rs
@@ -0,0 +1,12 @@
+// 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.
+
+#![no_main]
+
+use cros_fuzz::fuzz_target;
+use p9::fuzzing::tframe_decode;
+
+fuzz_target!(|bytes: &[u8]| {
+ tframe_decode(bytes);
+});
diff --git a/src/fuzzing.rs b/src/fuzzing.rs
new file mode 100644
index 0000000..0ba8e23
--- /dev/null
+++ b/src/fuzzing.rs
@@ -0,0 +1,13 @@
+// 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 std::io::Cursor;
+
+use crate::protocol::{Tframe, WireFormat};
+
+pub fn tframe_decode(bytes: &[u8]) {
+ let mut cursor = Cursor::new(bytes);
+
+ while let Ok(_) = Tframe::decode(&mut cursor) {}
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b00c706
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,16 @@
+// 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.
+
+extern crate libc;
+
+#[macro_use]
+extern crate wire_format_derive;
+
+mod protocol;
+mod server;
+
+#[cfg(fuzzing)]
+pub mod fuzzing;
+
+pub use server::*;
diff --git a/src/protocol/messages.rs b/src/protocol/messages.rs
new file mode 100644
index 0000000..b5a03c0
--- /dev/null
+++ b/src/protocol/messages.rs
@@ -0,0 +1,840 @@
+// 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 std::io::{self, ErrorKind, Read, Write};
+use std::mem;
+use std::string::String;
+use std::vec::Vec;
+
+use crate::protocol::wire_format::{Data, 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: Tmessage,
+}
+
+impl WireFormat for Tframe {
+ fn byte_size(&self) -> u32 {
+ let msg_size = match self.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<()> {
+ self.byte_size().encode(writer)?;
+
+ let ty = match self.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 self.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 = match ty[0] {
+ 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),
+ )),
+ }?;
+
+ Ok(Tframe { tag, msg })
+ }
+}
+
+#[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 ty: 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..9c278ee
--- /dev/null
+++ b/src/protocol/mod.rs
@@ -0,0 +1,9 @@
+// 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.
+
+mod messages;
+mod wire_format;
+
+pub use self::messages::*;
+pub use self::wire_format::{Data, WireFormat};
diff --git a/src/protocol/wire_format.rs b/src/protocol/wire_format.rs
new file mode 100644
index 0000000..b787990
--- /dev/null
+++ b/src/protocol/wire_format.rs
@@ -0,0 +1,707 @@
+// 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 std::fmt;
+use std::io;
+use std::io::{ErrorKind, Read, Write};
+use std::mem;
+use std::ops::{Deref, 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)]
+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 super::*;
+ use std::io::Cursor;
+ use std::mem;
+ use std::string::String;
+
+ #[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 as u8,
+ WireFormat::decode(&mut Cursor::new(&buf)).unwrap()
+ );
+ assert_eq!(
+ 0xbeef as u16,
+ WireFormat::decode(&mut Cursor::new(&buf)).unwrap()
+ );
+ assert_eq!(
+ 0xdeadbeef as u32,
+ WireFormat::decode(&mut Cursor::new(&buf)).unwrap()
+ );
+ assert_eq!(
+ 0x8badf00d_deadbeef as u64,
+ WireFormat::decode(&mut Cursor::new(&buf)).unwrap()
+ );
+ }
+
+ #[test]
+ fn integer_encode() {
+ let value: u64 = 0x8badf00d_deadbeef;
+ 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_str("!");
+
+ 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: 0xdead10cc_00bab10c,
+ 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>,
+ }
+
+ 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..c25cb3b
--- /dev/null
+++ b/src/server/mod.rs
@@ -0,0 +1,937 @@
+// 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 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 crate::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); 14] = [
+ (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;
+
+// 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.
+#[derive(Debug)]
+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,
+ // 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 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())
+}
+
+pub struct Server {
+ root: Box<Path>,
+ msize: u32,
+ fids: BTreeMap<u32, Fid>,
+ uid_map: ServerUidMap,
+ gid_map: ServerGidMap,
+}
+
+impl Server {
+ pub fn new<P: Into<Box<Path>>>(
+ root: P,
+ uid_map: ServerUidMap,
+ gid_map: ServerGidMap,
+ ) -> Server {
+ Server {
+ root: root.into(),
+ msize: MAX_MESSAGE_SIZE,
+ fids: BTreeMap::new(),
+ uid_map,
+ gid_map,
+ }
+ }
+
+ 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).map(Rmessage::Version),
+ Tmessage::Flush(ref flush) => self.flush(flush).and(Ok(Rmessage::Flush)),
+ Tmessage::Walk(ref walk) => self.walk(walk).map(Rmessage::Walk),
+ Tmessage::Read(ref read) => self.read(read).map(Rmessage::Read),
+ Tmessage::Write(ref write) => self.write(write).map(Rmessage::Write),
+ Tmessage::Clunk(ref clunk) => self.clunk(clunk).and(Ok(Rmessage::Clunk)),
+ Tmessage::Remove(ref remove) => self.remove(remove).and(Ok(Rmessage::Remove)),
+ Tmessage::Attach(ref attach) => self.attach(attach).map(Rmessage::Attach),
+ Tmessage::Auth(ref auth) => self.auth(auth).map(Rmessage::Auth),
+ Tmessage::Statfs(ref statfs) => self.statfs(statfs).map(Rmessage::Statfs),
+ Tmessage::Lopen(ref lopen) => self.lopen(lopen).map(Rmessage::Lopen),
+ Tmessage::Lcreate(ref lcreate) => self.lcreate(lcreate).map(Rmessage::Lcreate),
+ Tmessage::Symlink(ref symlink) => self.symlink(symlink).map(Rmessage::Symlink),
+ Tmessage::Mknod(ref mknod) => self.mknod(mknod).map(Rmessage::Mknod),
+ Tmessage::Rename(ref rename) => self.rename(rename).and(Ok(Rmessage::Rename)),
+ Tmessage::Readlink(ref readlink) => self.readlink(readlink).map(Rmessage::Readlink),
+ Tmessage::GetAttr(ref get_attr) => self.get_attr(get_attr).map(Rmessage::GetAttr),
+ Tmessage::SetAttr(ref set_attr) => self.set_attr(set_attr).and(Ok(Rmessage::SetAttr)),
+ Tmessage::XattrWalk(ref xattr_walk) => {
+ self.xattr_walk(xattr_walk).map(Rmessage::XattrWalk)
+ }
+ Tmessage::XattrCreate(ref xattr_create) => self
+ .xattr_create(xattr_create)
+ .and(Ok(Rmessage::XattrCreate)),
+ Tmessage::Readdir(ref readdir) => self.readdir(readdir).map(Rmessage::Readdir),
+ Tmessage::Fsync(ref fsync) => self.fsync(fsync).and(Ok(Rmessage::Fsync)),
+ Tmessage::Lock(ref lock) => self.lock(lock).map(Rmessage::Lock),
+ Tmessage::GetLock(ref get_lock) => self.get_lock(get_lock).map(Rmessage::GetLock),
+ Tmessage::Link(ref link) => self.link(link).and(Ok(Rmessage::Link)),
+ Tmessage::Mkdir(ref mkdir) => self.mkdir(mkdir).map(Rmessage::Mkdir),
+ Tmessage::RenameAt(ref rename_at) => {
+ self.rename_at(rename_at).and(Ok(Rmessage::RenameAt))
+ }
+ Tmessage::UnlinkAt(ref unlink_at) => {
+ self.unlink_at(unlink_at).and(Ok(Rmessage::UnlinkAt))
+ }
+ };
+
+ // 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<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 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(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.msize = min(MAX_MESSAGE_SIZE, version.msize);
+
+ Ok(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<()> {
+ // TODO: Since everything is synchronous we can't actually flush requests.
+ Ok(())
+ }
+
+ 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<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 (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(Rwalk {
+ wqids: mds.iter().map(metadata_to_qid).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(|| 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 capacity = min(self.msize - header_size, read.count);
+ let mut buf = Data(Vec::with_capacity(capacity as usize));
+ buf.resize(capacity as usize, 0);
+
+ let count = file.read_at(&mut buf, read.offset)?;
+ buf.resize(count, 0);
+
+ 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(|| io::Error::from_raw_os_error(libc::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<()> {
+ 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(())
+ }
+ }
+ }
+
+ fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rstatfs> {
+ 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(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<Rlopen> {
+ 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;
+ }
+ }
+
+ // MAPPED_FLAGS will handle append, create[_new], and truncate.
+ 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)
+ .custom_flags(custom_flags)
+ .open(&fid.path)?;
+
+ fid.metadata = file.metadata()?;
+ fid.file = Some(file);
+
+ Ok(Rlopen {
+ qid: metadata_to_qid(&fid.metadata),
+ iounit: 0,
+ })
+ }
+
+ fn lcreate(&mut self, lcreate: &Tlcreate) -> io::Result<Rlcreate> {
+ 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;
+ }
+ }
+
+ // Set O_CREAT|O_EXCL, MAPPED_FLAGS will handle append and truncate.
+ custom_flags |= libc::O_CREAT | libc::O_EXCL;
+ let file = fs::OpenOptions::new()
+ .read((lcreate.flags & P9_NOACCESS) == 0 || (lcreate.flags & P9_RDWR) != 0)
+ .write((lcreate.flags & P9_WRONLY) != 0 || (lcreate.flags & P9_RDWR) != 0)
+ .custom_flags(custom_flags)
+ .mode(lcreate.mode & 0o755)
+ .open(&path)?;
+
+ fid.metadata = file.metadata()?;
+ fid.file = Some(file);
+ fid.path = path.into_boxed_path();
+
+ Ok(Rlcreate {
+ qid: metadata_to_qid(&fid.metadata),
+ iounit: 0,
+ })
+ }
+
+ 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<()> {
+ 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(())
+ }
+
+ fn readlink(&mut self, _readlink: &Treadlink) -> io::Result<Rreadlink> {
+ // symlinks are not allowed
+ Err(io::Error::from_raw_os_error(libc::EACCES))
+ }
+
+ fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr> {
+ 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(Rgetattr {
+ valid: P9_GETATTR_BASIC,
+ qid: metadata_to_qid(&fid.metadata),
+ mode: fid.metadata.st_mode(),
+ uid: map_id_from_host(&self.uid_map, fid.metadata.st_uid()),
+ gid: map_id_from_host(&self.gid_map, 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<()> {
+ 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(())
+ }
+
+ 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(|| 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,
+ offset: 0, // set below
+ ty,
+ 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));
+
+ while let Some(entry) = entries.peek() {
+ 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;
+ }
+
+ // Safe because we just checked that the iterator contains at least one more item.
+ entries.next().unwrap().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(|| io::Error::from_raw_os_error(libc::EBADF))?;
+
+ if fsync.datasync == 0 {
+ file.sync_all()?;
+ } else {
+ file.sync_data()?;
+ }
+ Ok(())
+ }
+
+ fn lock(&mut self, _lock: &Tlock) -> io::Result<Rlock> {
+ // File locking is not supported.
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+ fn get_lock(&mut self, _get_lock: &Tgetlock) -> io::Result<Rgetlock> {
+ // File locking is not supported.
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+
+ fn link(&mut self, link: &Tlink) -> io::Result<()> {
+ 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(())
+ }
+
+ fn mkdir(&mut self, mkdir: &Tmkdir) -> io::Result<Rmkdir> {
+ 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 & 0o755)
+ .create(&newpath)?;
+
+ Ok(Rmkdir {
+ qid: metadata_to_qid(&fs::metadata(&newpath)?),
+ })
+ }
+
+ fn rename_at(&mut self, rename_at: &Trenameat) -> io::Result<()> {
+ 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(())
+ }
+
+ fn unlink_at(&mut self, unlink_at: &Tunlinkat) -> io::Result<()> {
+ 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(())
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/src/server/tests.rs b/src/server/tests.rs
new file mode 100644
index 0000000..fa161e9
--- /dev/null
+++ b/src/server/tests.rs
@@ -0,0 +1,1081 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::*;
+
+use std::borrow::Cow;
+use std::collections::{HashSet, VecDeque};
+use std::env;
+use std::ffi::{CString, OsString};
+use std::fs::File;
+use std::io::{self, Cursor};
+use std::mem;
+use std::ops::Deref;
+use std::os::linux::fs::MetadataExt;
+use std::os::unix::ffi::OsStringExt;
+use std::path::{Path, PathBuf};
+use std::u32;
+
+// 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;
+
+// How big we want the default buffer to be when running tests.
+const DEFAULT_BUFFER_SIZE: u32 = 4096;
+
+// 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>],
+ },
+}
+
+impl<'a> DirEntry<'a> {
+ // Creates `self` in the path given by `dir`.
+ 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());
+ }
+ }
+ }
+}
+
+// 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(200).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
+}
+
+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.st_mtime() as u32);
+ assert_eq!(qid.path, md.st_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.st_mtime() as u32);
+ assert_eq!(rgetattr.qid.path, md.st_ino());
+ assert_eq!(rgetattr.mode, md.st_mode());
+ assert_eq!(rgetattr.uid, md.st_uid());
+ assert_eq!(rgetattr.gid, md.st_gid());
+ assert_eq!(rgetattr.nlink, md.st_nlink());
+ assert_eq!(rgetattr.rdev, md.st_rdev());
+ assert_eq!(rgetattr.size, md.st_size());
+ assert_eq!(rgetattr.atime_sec, md.st_atime() as u64);
+ assert_eq!(rgetattr.atime_nsec, md.st_atime_nsec() as u64);
+ assert_eq!(rgetattr.mtime_sec, md.st_mtime() as u64);
+ assert_eq!(rgetattr.mtime_nsec, md.st_mtime_nsec() as u64);
+ assert_eq!(rgetattr.ctime_sec, md.st_ctime() as u64);
+ assert_eq!(rgetattr.ctime_nsec, md.st_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> {
+ walk(server, dir, dir_fid, fid, vec![String::from(name)]);
+
+ 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());
+
+ 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 fid = 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, fid, wnames);
+
+ let md = dir.symlink_metadata().expect("failed to get metadata");
+
+ check_attr(&mut server, fid, &md);
+
+ for dirent in readdir(&mut server, fid) {
+ 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");
+}
+
+fn set_attr_test<F>(set_fields: F) -> io::Result<fs::Metadata>
+where
+ F: FnOnce(&mut Tsetattr),
+{
+ let (test_dir, mut server) = setup("set_attr");
+
+ let name = "existing";
+ create_local_file(&test_dir, name);
+
+ 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(|tsetattr| {
+ tsetattr.valid = P9_SETATTR_SIZE;
+ tsetattr.size = len;
+ })
+ .expect("failed to run set length of file");
+
+ assert_eq!(md.st_size(), len);
+}
+
+#[test]
+fn set_mode() {
+ let mode = 0o640;
+ let err = set_attr_test(|tsetattr| {
+ tsetattr.valid = P9_SETATTR_MODE;
+ tsetattr.mode = mode;
+ })
+ .expect_err("successfully set mode");
+
+ assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
+}
+
+#[test]
+fn set_uid() {
+ let uid = 294;
+ let err = set_attr_test(|tsetattr| {
+ tsetattr.valid = P9_SETATTR_UID;
+ tsetattr.uid = uid;
+ })
+ .expect_err("successfully set uid");
+
+ assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
+}
+
+#[test]
+fn set_gid() {
+ let gid = 9024;
+ let err = set_attr_test(|tsetattr| {
+ tsetattr.valid = P9_SETATTR_GID;
+ tsetattr.gid = gid;
+ })
+ .expect_err("successfully set gid");
+
+ assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
+}
+
+#[test]
+fn set_mtime() {
+ let (secs, nanos) = (1245247825, 524617);
+ let md = set_attr_test(|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.st_mtime() as u64, secs);
+ assert_eq!(md.st_mtime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_atime() {
+ let (secs, nanos) = (9247605, 4016);
+ let md = set_attr_test(|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.st_atime() as u64, secs);
+ assert_eq!(md.st_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 fid = ROOT_FID + 1;
+ walk(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ fid,
+ 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));
+ }
+
+ 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);
+
+ 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 remove_all() {
+ 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());
+
+ // First iterate over the whole directory.
+ let mut fids = VecDeque::new();
+ while let Some(dir) = dirs.pop_front() {
+ for entry in fs::read_dir(dir).expect("failed to read directory") {
+ let entry = entry.expect("unable to iterate over directory");
+ 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);
+
+ let ft = entry
+ .file_type()
+ .expect("failed to get file type for entry");
+ if ft.is_dir() {
+ dirs.push_back(entry.path());
+ }
+
+ fids.push_back(fid);
+ }
+ }
+
+ // Now remove everything in reverse order.
+ while let Some(fid) = fids.pop_back() {
+ let tremove = Tremove { fid };
+
+ server.remove(&tremove).expect("failed to remove entry");
+ }
+}
+
+#[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() {
+ let (test_dir, mut server) = setup("rename");
+
+ let name = "oldfile";
+ let content = create_local_file(&test_dir, name);
+
+ // First walk to the file to be renamed.
+ let fid = ROOT_FID + 1;
+ walk(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ fid,
+ vec![String::from(name)],
+ );
+
+ let newname = "newfile";
+ let trename = Trename {
+ fid,
+ dfid: ROOT_FID,
+ name: String::from(newname),
+ };
+
+ server.rename(&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);
+}
+
+#[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);
+}
+
+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);
diff --git a/wire_format_derive/Cargo.toml b/wire_format_derive/Cargo.toml
new file mode 100644
index 0000000..52ab6a4
--- /dev/null
+++ b/wire_format_derive/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "wire_format_derive"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+
+[dependencies]
+syn = "^1"
+quote = "^1"
+proc-macro2 = "^1"
+
+[lib]
+proc-macro = true
+path = "wire_format_derive.rs"
diff --git a/wire_format_derive/wire_format_derive.rs b/wire_format_derive/wire_format_derive.rs
new file mode 100644
index 0000000..290ffc5
--- /dev/null
+++ b/wire_format_derive/wire_format_derive.rs
@@ -0,0 +1,303 @@
+// 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.
+
+//! Derives a 9P wire format encoding for a struct by recursively calling
+//! `WireFormat::encode` or `WireFormat::decode` on the fields of the struct.
+//! This is only intended to be used from within the `p9` crate.
+
+#![recursion_limit = "256"]
+
+extern crate proc_macro;
+extern crate proc_macro2;
+
+#[macro_use]
+extern crate quote;
+
+#[macro_use]
+extern crate syn;
+
+use proc_macro2::{Span, TokenStream};
+use syn::spanned::Spanned;
+use syn::{Data, DeriveInput, Fields, Ident};
+
+/// The function that derives the actual implementation.
+#[proc_macro_derive(P9WireFormat)]
+pub fn p9_wire_format(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ p9_wire_format_inner(input).into()
+}
+
+fn p9_wire_format_inner(input: DeriveInput) -> TokenStream {
+ if !input.generics.params.is_empty() {
+ return quote! {
+ compile_error!("derive(P9WireFormat) does not support generic parameters");
+ };
+ }
+
+ let container = input.ident;
+
+ let byte_size_impl = byte_size_sum(&input.data);
+ let encode_impl = encode_wire_format(&input.data);
+ let decode_impl = decode_wire_format(&input.data, &container);
+
+ let scope = format!("wire_format_{}", container).to_lowercase();
+ let scope = Ident::new(&scope, Span::call_site());
+ quote! {
+ mod #scope {
+ extern crate std;
+ use self::std::io;
+ use self::std::result::Result::Ok;
+
+ use super::#container;
+
+ use protocol::WireFormat;
+
+ impl WireFormat for #container {
+ fn byte_size(&self) -> u32 {
+ #byte_size_impl
+ }
+
+ fn encode<W: io::Write>(&self, _writer: &mut W) -> io::Result<()> {
+ #encode_impl
+ }
+
+ fn decode<R: io::Read>(_reader: &mut R) -> io::Result<Self> {
+ #decode_impl
+ }
+ }
+ }
+ }
+}
+
+// Generate code that recursively calls byte_size on every field in the struct.
+fn byte_size_sum(data: &Data) -> TokenStream {
+ if let Data::Struct(ref data) = *data {
+ if let Fields::Named(ref fields) = data.fields {
+ let fields = fields.named.iter().map(|f| {
+ let field = &f.ident;
+ let span = field.span();
+ quote_spanned! {span=>
+ WireFormat::byte_size(&self.#field)
+ }
+ });
+
+ quote! {
+ 0 #(+ #fields)*
+ }
+ } else {
+ unimplemented!();
+ }
+ } else {
+ unimplemented!();
+ }
+}
+
+// Generate code that recursively calls encode on every field in the struct.
+fn encode_wire_format(data: &Data) -> TokenStream {
+ if let Data::Struct(ref data) = *data {
+ if let Fields::Named(ref fields) = data.fields {
+ let fields = fields.named.iter().map(|f| {
+ let field = &f.ident;
+ let span = field.span();
+ quote_spanned! {span=>
+ WireFormat::encode(&self.#field, _writer)?;
+ }
+ });
+
+ quote! {
+ #(#fields)*
+
+ Ok(())
+ }
+ } else {
+ unimplemented!();
+ }
+ } else {
+ unimplemented!();
+ }
+}
+
+// Generate code that recursively calls decode on every field in the struct.
+fn decode_wire_format(data: &Data, container: &Ident) -> TokenStream {
+ if let Data::Struct(ref data) = *data {
+ if let Fields::Named(ref fields) = data.fields {
+ let values = fields.named.iter().map(|f| {
+ let field = &f.ident;
+ let span = field.span();
+ quote_spanned! {span=>
+ let #field = WireFormat::decode(_reader)?;
+ }
+ });
+
+ let members = fields.named.iter().map(|f| {
+ let field = &f.ident;
+ quote! {
+ #field: #field,
+ }
+ });
+
+ quote! {
+ #(#values)*
+
+ Ok(#container {
+ #(#members)*
+ })
+ }
+ } else {
+ unimplemented!();
+ }
+ } else {
+ unimplemented!();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn byte_size() {
+ let input: DeriveInput = parse_quote! {
+ struct Item {
+ ident: u32,
+ with_underscores: String,
+ other: u8,
+ }
+ };
+
+ let expected = quote! {
+ 0
+ + WireFormat::byte_size(&self.ident)
+ + WireFormat::byte_size(&self.with_underscores)
+ + WireFormat::byte_size(&self.other)
+ };
+
+ assert_eq!(byte_size_sum(&input.data).to_string(), expected.to_string());
+ }
+
+ #[test]
+ fn encode() {
+ let input: DeriveInput = parse_quote! {
+ struct Item {
+ ident: u32,
+ with_underscores: String,
+ other: u8,
+ }
+ };
+
+ let expected = quote! {
+ WireFormat::encode(&self.ident, _writer)?;
+ WireFormat::encode(&self.with_underscores, _writer)?;
+ WireFormat::encode(&self.other, _writer)?;
+ Ok(())
+ };
+
+ assert_eq!(
+ encode_wire_format(&input.data).to_string(),
+ expected.to_string(),
+ );
+ }
+
+ #[test]
+ fn decode() {
+ let input: DeriveInput = parse_quote! {
+ struct Item {
+ ident: u32,
+ with_underscores: String,
+ other: u8,
+ }
+ };
+
+ let container = Ident::new("Item", Span::call_site());
+ let expected = quote! {
+ let ident = WireFormat::decode(_reader)?;
+ let with_underscores = WireFormat::decode(_reader)?;
+ let other = WireFormat::decode(_reader)?;
+ Ok(Item {
+ ident: ident,
+ with_underscores: with_underscores,
+ other: other,
+ })
+ };
+
+ assert_eq!(
+ decode_wire_format(&input.data, &container).to_string(),
+ expected.to_string(),
+ );
+ }
+
+ #[test]
+ fn end_to_end() {
+ let input: DeriveInput = parse_quote! {
+ struct Niijima_先輩 {
+ a: u8,
+ b: u16,
+ c: u32,
+ d: u64,
+ e: String,
+ f: Vec<String>,
+ g: Nested,
+ }
+ };
+
+ let expected = quote! {
+ mod wire_format_niijima_先輩 {
+ extern crate std;
+ use self::std::io;
+ use self::std::result::Result::Ok;
+
+ use super::Niijima_先輩;
+
+ use protocol::WireFormat;
+
+ impl WireFormat for Niijima_先輩 {
+ fn byte_size(&self) -> u32 {
+ 0
+ + WireFormat::byte_size(&self.a)
+ + WireFormat::byte_size(&self.b)
+ + WireFormat::byte_size(&self.c)
+ + WireFormat::byte_size(&self.d)
+ + WireFormat::byte_size(&self.e)
+ + WireFormat::byte_size(&self.f)
+ + WireFormat::byte_size(&self.g)
+ }
+
+ fn encode<W: io::Write>(&self, _writer: &mut W) -> io::Result<()> {
+ WireFormat::encode(&self.a, _writer)?;
+ WireFormat::encode(&self.b, _writer)?;
+ WireFormat::encode(&self.c, _writer)?;
+ WireFormat::encode(&self.d, _writer)?;
+ WireFormat::encode(&self.e, _writer)?;
+ WireFormat::encode(&self.f, _writer)?;
+ WireFormat::encode(&self.g, _writer)?;
+ Ok(())
+ }
+ fn decode<R: io::Read>(_reader: &mut R) -> io::Result<Self> {
+ let a = WireFormat::decode(_reader)?;
+ let b = WireFormat::decode(_reader)?;
+ let c = WireFormat::decode(_reader)?;
+ let d = WireFormat::decode(_reader)?;
+ let e = WireFormat::decode(_reader)?;
+ let f = WireFormat::decode(_reader)?;
+ let g = WireFormat::decode(_reader)?;
+ Ok(Niijima_先輩 {
+ a: a,
+ b: b,
+ c: c,
+ d: d,
+ e: e,
+ f: f,
+ g: g,
+ })
+ }
+ }
+ }
+ };
+
+ assert_eq!(
+ p9_wire_format_inner(input).to_string(),
+ expected.to_string(),
+ );
+ }
+}