diff options
author | Alistair Delva <adelva@google.com> | 2020-10-22 10:35:12 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-10-22 10:35:12 +0000 |
commit | 44a66c13604a1f1414641d1ea927b34a82d86ef7 (patch) | |
tree | 9918ff34d4bf62f4a02ea8f4ea57cf97cfe8250b | |
parent | b2f28fa79752251d3214d1ffa566af57803ba769 (diff) | |
parent | 082a1c3c35b054d7ee9b92186a64a7c9a21c1707 (diff) | |
download | p9-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-format | 19 | ||||
-rw-r--r-- | .gitignore | 71 | ||||
-rw-r--r-- | Cargo.toml | 12 | ||||
-rw-r--r-- | LICENSE | 27 | ||||
-rw-r--r-- | METADATA | 14 | ||||
-rw-r--r-- | MODULE_LICENSE_BSD | 0 | ||||
-rw-r--r-- | OWNERS | 3 | ||||
-rw-r--r-- | README.md | 21 | ||||
-rw-r--r-- | fuzz/Cargo.toml | 19 | ||||
-rw-r--r-- | fuzz/tframe_decode.rs | 12 | ||||
-rw-r--r-- | src/fuzzing.rs | 13 | ||||
-rw-r--r-- | src/lib.rs | 16 | ||||
-rw-r--r-- | src/protocol/messages.rs | 840 | ||||
-rw-r--r-- | src/protocol/mod.rs | 9 | ||||
-rw-r--r-- | src/protocol/wire_format.rs | 707 | ||||
-rw-r--r-- | src/server/mod.rs | 937 | ||||
-rw-r--r-- | src/server/tests.rs | 1081 | ||||
-rw-r--r-- | wire_format_derive/Cargo.toml | 13 | ||||
-rw-r--r-- | wire_format_derive/wire_format_derive.rs | 303 |
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 = [] @@ -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 @@ -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(), × 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(), + ); + } +} |