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