mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-12-25 04:14:06 +00:00
p9: Add server implementation
Implement all the server methods for the 9P protocol. BUG=chromium:703939 TEST=bonnie++ -r 256 Change-Id: I6b1b5fe4fea4d4941db42e5c1a364a54d0827054 Signed-off-by: Chirantan Ekbote <chirantan@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/1013203 Reviewed-by: Stephen Barber <smbarber@chromium.org> Reviewed-by: Dylan Reid <dgreid@chromium.org>
This commit is contained in:
parent
a7b0a71204
commit
b657603f10
4 changed files with 949 additions and 5 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -215,6 +215,7 @@ dependencies = [
|
|||
name = "p9"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wire_format_derive 0.1.0",
|
||||
]
|
||||
|
||||
|
|
|
@ -4,4 +4,8 @@ version = "0.1.0"
|
|||
authors = ["The Chromium OS Authors"]
|
||||
|
||||
[dependencies]
|
||||
libc = "*"
|
||||
wire_format_derive = { path = "wire_format_derive" }
|
||||
|
||||
[features]
|
||||
trace = []
|
|
@ -2,13 +2,12 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
extern crate libc;
|
||||
|
||||
#[macro_use]
|
||||
extern crate wire_format_derive;
|
||||
|
||||
mod protocol;
|
||||
mod server;
|
||||
|
||||
#[derive(P9WireFormat)]
|
||||
struct Test {
|
||||
a: u32,
|
||||
b: u16,
|
||||
}
|
||||
pub use server::Server;
|
||||
|
|
940
p9/src/server.rs
Normal file
940
p9/src/server.rs
Normal file
|
@ -0,0 +1,940 @@
|
|||
// Copyright 2018 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use libc;
|
||||
|
||||
use std::cmp::min;
|
||||
use std::collections::{btree_map, BTreeMap};
|
||||
use std::ffi::CString;
|
||||
use std::fs;
|
||||
use std::io::{self, Cursor, Read, Write};
|
||||
use std::mem;
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
use std::os::unix::fs::{DirBuilderExt, FileExt, OpenOptionsExt};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use protocol::*;
|
||||
|
||||
// Tlopen and Tlcreate flags. Taken from "include/net/9p/9p.h" in the linux tree.
|
||||
const _P9_RDONLY: u32 = 0o00000000;
|
||||
const P9_WRONLY: u32 = 0o00000001;
|
||||
const P9_RDWR: u32 = 0o00000002;
|
||||
const P9_NOACCESS: u32 = 0o00000003;
|
||||
const P9_CREATE: u32 = 0o00000100;
|
||||
const P9_EXCL: u32 = 0o00000200;
|
||||
const P9_NOCTTY: u32 = 0o00000400;
|
||||
const P9_TRUNC: u32 = 0o00001000;
|
||||
const P9_APPEND: u32 = 0o00002000;
|
||||
const P9_NONBLOCK: u32 = 0o00004000;
|
||||
const P9_DSYNC: u32 = 0o00010000;
|
||||
const P9_FASYNC: u32 = 0o00020000;
|
||||
const P9_DIRECT: u32 = 0o00040000;
|
||||
const P9_LARGEFILE: u32 = 0o00100000;
|
||||
const P9_DIRECTORY: u32 = 0o00200000;
|
||||
const P9_NOFOLLOW: u32 = 0o00400000;
|
||||
const P9_NOATIME: u32 = 0o01000000;
|
||||
const _P9_CLOEXEC: u32 = 0o02000000;
|
||||
const P9_SYNC: u32 = 0o04000000;
|
||||
|
||||
// Mapping from 9P flags to libc flags.
|
||||
const MAPPED_FLAGS: [(u32, i32); 10] = [
|
||||
(P9_NOCTTY, libc::O_NOCTTY),
|
||||
(P9_NONBLOCK, libc::O_NONBLOCK),
|
||||
(P9_DSYNC, libc::O_DSYNC),
|
||||
(P9_FASYNC, 0), // Unsupported
|
||||
(P9_DIRECT, libc::O_DIRECT),
|
||||
(P9_LARGEFILE, libc::O_LARGEFILE),
|
||||
(P9_DIRECTORY, libc::O_DIRECTORY),
|
||||
(P9_NOFOLLOW, libc::O_NOFOLLOW),
|
||||
(P9_NOATIME, libc::O_NOATIME),
|
||||
(P9_SYNC, libc::O_SYNC),
|
||||
];
|
||||
|
||||
// 9P Qid types. Taken from "include/net/9p/9p.h" in the linux tree.
|
||||
const P9_QTDIR: u8 = 0x80;
|
||||
const _P9_QTAPPEND: u8 = 0x40;
|
||||
const _P9_QTEXCL: u8 = 0x20;
|
||||
const _P9_QTMOUNT: u8 = 0x10;
|
||||
const _P9_QTAUTH: u8 = 0x08;
|
||||
const _P9_QTTMP: u8 = 0x04;
|
||||
const _P9_QTSYMLINK: u8 = 0x02;
|
||||
const _P9_QTLINK: u8 = 0x01;
|
||||
const P9_QTFILE: u8 = 0x00;
|
||||
|
||||
// Bitmask values for the getattr request.
|
||||
const _P9_GETATTR_MODE: u64 = 0x00000001;
|
||||
const _P9_GETATTR_NLINK: u64 = 0x00000002;
|
||||
const _P9_GETATTR_UID: u64 = 0x00000004;
|
||||
const _P9_GETATTR_GID: u64 = 0x00000008;
|
||||
const _P9_GETATTR_RDEV: u64 = 0x00000010;
|
||||
const _P9_GETATTR_ATIME: u64 = 0x00000020;
|
||||
const _P9_GETATTR_MTIME: u64 = 0x00000040;
|
||||
const _P9_GETATTR_CTIME: u64 = 0x00000080;
|
||||
const _P9_GETATTR_INO: u64 = 0x00000100;
|
||||
const _P9_GETATTR_SIZE: u64 = 0x00000200;
|
||||
const _P9_GETATTR_BLOCKS: u64 = 0x00000400;
|
||||
|
||||
const _P9_GETATTR_BTIME: u64 = 0x00000800;
|
||||
const _P9_GETATTR_GEN: u64 = 0x00001000;
|
||||
const _P9_GETATTR_DATA_VERSION: u64 = 0x00002000;
|
||||
|
||||
const P9_GETATTR_BASIC: u64 = 0x000007ff; /* Mask for fields up to BLOCKS */
|
||||
const _P9_GETATTR_ALL: u64 = 0x00003fff; /* Mask for All fields above */
|
||||
|
||||
// Bitmask values for the setattr request.
|
||||
const P9_SETATTR_MODE: u32 = 0x00000001;
|
||||
const P9_SETATTR_UID: u32 = 0x00000002;
|
||||
const P9_SETATTR_GID: u32 = 0x00000004;
|
||||
const P9_SETATTR_SIZE: u32 = 0x00000008;
|
||||
const P9_SETATTR_ATIME: u32 = 0x00000010;
|
||||
const P9_SETATTR_MTIME: u32 = 0x00000020;
|
||||
const P9_SETATTR_CTIME: u32 = 0x00000040;
|
||||
const P9_SETATTR_ATIME_SET: u32 = 0x00000080;
|
||||
const P9_SETATTR_MTIME_SET: u32 = 0x00000100;
|
||||
|
||||
// Minimum and maximum message size that we'll expect from the client.
|
||||
const MIN_MESSAGE_SIZE: u32 = 256;
|
||||
const MAX_MESSAGE_SIZE: u32 = ::std::u16::MAX as u32;
|
||||
|
||||
// Represents state that the server is holding on behalf of a client. Fids are somewhat like file
|
||||
// descriptors but are not restricted to open files and directories. Fids are identified by a unique
|
||||
// 32-bit number chosen by the client. Most messages sent by clients include a fid on which to
|
||||
// operate. The fid in a Tattach message represents the root of the file system tree that the client
|
||||
// is allowed to access. A client can create more fids by walking the directory tree from that fid.
|
||||
struct Fid {
|
||||
path: Box<Path>,
|
||||
metadata: fs::Metadata,
|
||||
file: Option<fs::File>,
|
||||
dirents: Option<Vec<Dirent>>,
|
||||
}
|
||||
|
||||
fn metadata_to_qid(metadata: &fs::Metadata) -> Qid {
|
||||
let ty = if metadata.is_dir() {
|
||||
P9_QTDIR
|
||||
} else if metadata.is_file() {
|
||||
P9_QTFILE
|
||||
} else {
|
||||
// Unknown file type...
|
||||
0
|
||||
};
|
||||
|
||||
Qid {
|
||||
ty: ty,
|
||||
// TODO: deal with the 2038 problem before 2038
|
||||
version: metadata.st_mtime() as u32,
|
||||
path: metadata.st_ino(),
|
||||
}
|
||||
}
|
||||
|
||||
fn error_to_rmessage(err: io::Error) -> Rmessage {
|
||||
let errno = if let Some(errno) = err.raw_os_error() {
|
||||
errno
|
||||
} else {
|
||||
// Make a best-effort guess based on the kind.
|
||||
match err.kind() {
|
||||
io::ErrorKind::NotFound => libc::ENOENT,
|
||||
io::ErrorKind::PermissionDenied => libc::EPERM,
|
||||
io::ErrorKind::ConnectionRefused => libc::ECONNREFUSED,
|
||||
io::ErrorKind::ConnectionReset => libc::ECONNRESET,
|
||||
io::ErrorKind::ConnectionAborted => libc::ECONNABORTED,
|
||||
io::ErrorKind::NotConnected => libc::ENOTCONN,
|
||||
io::ErrorKind::AddrInUse => libc::EADDRINUSE,
|
||||
io::ErrorKind::AddrNotAvailable => libc::EADDRNOTAVAIL,
|
||||
io::ErrorKind::BrokenPipe => libc::EPIPE,
|
||||
io::ErrorKind::AlreadyExists => libc::EEXIST,
|
||||
io::ErrorKind::WouldBlock => libc::EWOULDBLOCK,
|
||||
io::ErrorKind::InvalidInput => libc::EINVAL,
|
||||
io::ErrorKind::InvalidData => libc::EINVAL,
|
||||
io::ErrorKind::TimedOut => libc::ETIMEDOUT,
|
||||
io::ErrorKind::WriteZero => libc::EIO,
|
||||
io::ErrorKind::Interrupted => libc::EINTR,
|
||||
io::ErrorKind::Other => libc::EIO,
|
||||
io::ErrorKind::UnexpectedEof => libc::EIO,
|
||||
_ => libc::EIO,
|
||||
}
|
||||
};
|
||||
|
||||
Rmessage::Lerror(Rlerror {
|
||||
ecode: errno as u32,
|
||||
})
|
||||
}
|
||||
|
||||
// Joins `path` to `buf`. If `path` is '..', removes the last component from `buf`
|
||||
// only if `buf` != `root` but does nothing if `buf` == `root`. Pushes `path` onto
|
||||
// `buf` if it is a normal path component.
|
||||
//
|
||||
// Returns an error if `path` is absolute, has more than one component, or contains
|
||||
// a '.' component.
|
||||
fn join_path<P: AsRef<Path>, R: AsRef<Path>>(
|
||||
mut buf: PathBuf,
|
||||
path: P,
|
||||
root: R,
|
||||
) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
let root = root.as_ref();
|
||||
debug_assert!(buf.starts_with(root));
|
||||
|
||||
if path.components().count() > 1 {
|
||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
for component in path.components() {
|
||||
match component {
|
||||
// Prefix should only appear on windows systems.
|
||||
Component::Prefix(_) => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
|
||||
// Absolute paths are not allowed.
|
||||
Component::RootDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
|
||||
// '.' elements are not allowed.
|
||||
Component::CurDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
|
||||
Component::ParentDir => {
|
||||
// We only remove the parent path if we are not already at the root of the
|
||||
// file system.
|
||||
if buf != root {
|
||||
buf.pop();
|
||||
}
|
||||
}
|
||||
Component::Normal(element) => buf.push(element),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
root: Box<Path>,
|
||||
msize: u32,
|
||||
fids: BTreeMap<u32, Fid>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new<P: AsRef<Path>>(root: P) -> Server {
|
||||
Server {
|
||||
root: root.as_ref().into(),
|
||||
msize: MAX_MESSAGE_SIZE,
|
||||
fids: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_message<R: Read, W: Write>(
|
||||
&mut self,
|
||||
reader: &mut R,
|
||||
writer: &mut W,
|
||||
) -> io::Result<()> {
|
||||
let request: Tframe = WireFormat::decode(&mut reader.take(self.msize as u64))?;
|
||||
|
||||
if cfg!(feature = "trace") {
|
||||
println!("{:?}", &request);
|
||||
}
|
||||
|
||||
let rmsg = match request.msg {
|
||||
Tmessage::Version(ref version) => self.version(version),
|
||||
Tmessage::Flush(ref flush) => self.flush(flush),
|
||||
Tmessage::Walk(ref walk) => self.walk(walk),
|
||||
Tmessage::Read(ref read) => self.read(read),
|
||||
Tmessage::Write(ref write) => self.write(write),
|
||||
Tmessage::Clunk(ref clunk) => self.clunk(clunk),
|
||||
Tmessage::Remove(ref remove) => self.remove(remove),
|
||||
Tmessage::Attach(ref attach) => self.attach(attach),
|
||||
Tmessage::Auth(ref auth) => self.auth(auth),
|
||||
Tmessage::Statfs(ref statfs) => self.statfs(statfs),
|
||||
Tmessage::Lopen(ref lopen) => self.lopen(lopen),
|
||||
Tmessage::Lcreate(ref lcreate) => self.lcreate(lcreate),
|
||||
Tmessage::Symlink(ref symlink) => self.symlink(symlink),
|
||||
Tmessage::Mknod(ref mknod) => self.mknod(mknod),
|
||||
Tmessage::Rename(ref rename) => self.rename(rename),
|
||||
Tmessage::Readlink(ref readlink) => self.readlink(readlink),
|
||||
Tmessage::GetAttr(ref get_attr) => self.get_attr(get_attr),
|
||||
Tmessage::SetAttr(ref set_attr) => self.set_attr(set_attr),
|
||||
Tmessage::XattrWalk(ref xattr_walk) => self.xattr_walk(xattr_walk),
|
||||
Tmessage::XattrCreate(ref xattr_create) => self.xattr_create(xattr_create),
|
||||
Tmessage::Readdir(ref readdir) => self.readdir(readdir),
|
||||
Tmessage::Fsync(ref fsync) => self.fsync(fsync),
|
||||
Tmessage::Lock(ref lock) => self.lock(lock),
|
||||
Tmessage::GetLock(ref get_lock) => self.get_lock(get_lock),
|
||||
Tmessage::Link(ref link) => self.link(link),
|
||||
Tmessage::Mkdir(ref mkdir) => self.mkdir(mkdir),
|
||||
Tmessage::RenameAt(ref rename_at) => self.rename_at(rename_at),
|
||||
Tmessage::UnlinkAt(ref unlink_at) => self.unlink_at(unlink_at),
|
||||
};
|
||||
|
||||
// Errors while handling requests are never fatal.
|
||||
let response = Rframe {
|
||||
tag: request.tag,
|
||||
msg: rmsg.unwrap_or_else(error_to_rmessage),
|
||||
};
|
||||
|
||||
if cfg!(feature = "trace") {
|
||||
println!("{:?}", &response);
|
||||
}
|
||||
|
||||
response.encode(writer)?;
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
fn auth(&mut self, _auth: &Tauth) -> io::Result<Rmessage> {
|
||||
// Returning an error for the auth message means that the server does not require
|
||||
// authentication.
|
||||
Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
|
||||
}
|
||||
|
||||
fn attach(&mut self, attach: &Tattach) -> io::Result<Rmessage> {
|
||||
// TODO: Check attach parameters
|
||||
match self.fids.entry(attach.fid) {
|
||||
btree_map::Entry::Vacant(entry) => {
|
||||
let fid = Fid {
|
||||
path: self.root.to_path_buf().into_boxed_path(),
|
||||
metadata: fs::metadata(&self.root)?,
|
||||
file: None,
|
||||
dirents: None,
|
||||
};
|
||||
let response = Rattach {
|
||||
qid: metadata_to_qid(&fid.metadata),
|
||||
};
|
||||
entry.insert(fid);
|
||||
Ok(Rmessage::Attach(response))
|
||||
}
|
||||
btree_map::Entry::Occupied(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
|
||||
}
|
||||
}
|
||||
|
||||
fn version(&mut self, version: &Tversion) -> io::Result<Rmessage> {
|
||||
if version.msize < MIN_MESSAGE_SIZE {
|
||||
return Err(io::Error::from_raw_os_error(libc::EINVAL));
|
||||
}
|
||||
|
||||
// A Tversion request clunks all open fids and terminates any pending I/O.
|
||||
self.fids.clear();
|
||||
self.msize = min(MAX_MESSAGE_SIZE, version.msize);
|
||||
|
||||
Ok(Rmessage::Version(Rversion {
|
||||
msize: self.msize,
|
||||
version: if version.version == "9P2000.L" {
|
||||
String::from("9P2000.L")
|
||||
} else {
|
||||
String::from("unknown")
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
fn flush(&mut self, _flush: &Tflush) -> io::Result<Rmessage> {
|
||||
// TODO: Since everything is synchronous we can't actually flush requests.
|
||||
Ok(Rmessage::Flush)
|
||||
}
|
||||
|
||||
fn do_walk(
|
||||
&self,
|
||||
wnames: &[String],
|
||||
mut buf: PathBuf,
|
||||
mds: &mut Vec<fs::Metadata>,
|
||||
) -> io::Result<PathBuf> {
|
||||
for wname in wnames {
|
||||
let name = Path::new(wname);
|
||||
buf = join_path(buf, name, &*self.root)?;
|
||||
mds.push(fs::metadata(&buf)?);
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn walk(&mut self, walk: &Twalk) -> io::Result<Rmessage> {
|
||||
// `newfid` must not currently be in use unless it is the same as `fid`.
|
||||
if walk.fid != walk.newfid && self.fids.contains_key(&walk.newfid) {
|
||||
return Err(io::Error::from_raw_os_error(libc::EBADF));
|
||||
}
|
||||
|
||||
// We need to walk the tree. First get the starting path.
|
||||
let (buf, oldmd) = self.fids
|
||||
.get(&walk.fid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))
|
||||
.map(|fid| (fid.path.to_path_buf(), fid.metadata.clone()))?;
|
||||
|
||||
// Now walk the tree and break on the first error, if any.
|
||||
let mut mds = Vec::with_capacity(walk.wnames.len());
|
||||
match self.do_walk(&walk.wnames, buf, &mut mds) {
|
||||
Ok(buf) => {
|
||||
// Store the new fid if the full walk succeeded.
|
||||
if mds.len() == walk.wnames.len() {
|
||||
// This could just be a duplication operation.
|
||||
let md = if let Some(md) = mds.last() {
|
||||
md.clone()
|
||||
} else {
|
||||
oldmd
|
||||
};
|
||||
|
||||
self.fids.insert(
|
||||
walk.newfid,
|
||||
Fid {
|
||||
path: buf.into_boxed_path(),
|
||||
metadata: md,
|
||||
file: None,
|
||||
dirents: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Only return an error if it occurred on the first component.
|
||||
if mds.is_empty() {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Rmessage::Walk(Rwalk {
|
||||
wqids: mds.iter().map(metadata_to_qid).collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn read(&mut self, read: &Tread) -> io::Result<Rmessage> {
|
||||
// Thankfully, `read` cannot be used to read directories in 9P2000.L.
|
||||
let file = self.fids
|
||||
.get_mut(&read.fid)
|
||||
.and_then(|fid| fid.file.as_mut())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
|
||||
// Use an empty Rread struct to figure out the overhead of the header.
|
||||
let header_size = Rframe {
|
||||
tag: 0,
|
||||
msg: Rmessage::Read(Rread {
|
||||
data: Data(Vec::new()),
|
||||
}),
|
||||
}.byte_size();
|
||||
let count = min(self.msize - header_size, read.count);
|
||||
let mut buf = Data(Vec::with_capacity(count as usize));
|
||||
|
||||
// Safe because `buf` is guaranteed to have a capacity of `read.count`. We do
|
||||
// this because we don't want to spend time zero-initializing a potentially
|
||||
// large buffer.
|
||||
unsafe {
|
||||
buf.set_len(read.count as usize);
|
||||
}
|
||||
let count = file.read_at(&mut buf, read.offset)?;
|
||||
|
||||
// Safe because read_at guarantees that `count` bytes have been written
|
||||
// into `buf` and that `count` is less than or equal to `buf.len()`.
|
||||
unsafe {
|
||||
buf.set_len(count);
|
||||
}
|
||||
Ok(Rmessage::Read(Rread { data: buf }))
|
||||
}
|
||||
|
||||
fn write(&mut self, write: &Twrite) -> io::Result<Rmessage> {
|
||||
let file = self.fids
|
||||
.get_mut(&write.fid)
|
||||
.and_then(|fid| fid.file.as_mut())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
|
||||
let count = file.write_at(&write.data, write.offset)?;
|
||||
Ok(Rmessage::Write(Rwrite {
|
||||
count: count as u32,
|
||||
}))
|
||||
}
|
||||
|
||||
fn clunk(&mut self, clunk: &Tclunk) -> io::Result<Rmessage> {
|
||||
match self.fids.entry(clunk.fid) {
|
||||
btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
|
||||
btree_map::Entry::Occupied(entry) => {
|
||||
entry.remove();
|
||||
Ok(Rmessage::Clunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, remove: &Tremove) -> io::Result<Rmessage> {
|
||||
match self.fids.entry(remove.fid) {
|
||||
btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
|
||||
btree_map::Entry::Occupied(o) => {
|
||||
let (_, fid) = o.remove_entry();
|
||||
|
||||
if fid.metadata.is_dir() {
|
||||
fs::remove_dir(&fid.path)?;
|
||||
} else {
|
||||
fs::remove_file(&fid.path)?;
|
||||
}
|
||||
|
||||
Ok(Rmessage::Remove)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rmessage> {
|
||||
let fid = self.fids
|
||||
.get(&statfs.fid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
let path = fid.path
|
||||
.to_str()
|
||||
.and_then(|path| CString::new(path).ok())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
|
||||
|
||||
// Safe because we are zero-initializing a C struct with only primitive
|
||||
// data members.
|
||||
let mut out: libc::statfs64 = unsafe { mem::zeroed() };
|
||||
|
||||
// Safe because we know that `path` is valid and we have already initialized `out`.
|
||||
let ret = unsafe { libc::statfs64(path.as_ptr(), &mut out) };
|
||||
if ret != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(Rmessage::Statfs(Rstatfs {
|
||||
ty: out.f_type as u32,
|
||||
bsize: out.f_bsize as u32,
|
||||
blocks: out.f_blocks,
|
||||
bfree: out.f_bfree,
|
||||
bavail: out.f_bavail,
|
||||
files: out.f_files,
|
||||
ffree: out.f_ffree,
|
||||
fsid: 0, // No way to get the fields of a libc::fsid_t
|
||||
namelen: out.f_namelen as u32,
|
||||
}))
|
||||
}
|
||||
|
||||
fn lopen(&mut self, lopen: &Tlopen) -> io::Result<Rmessage> {
|
||||
let fid = self.fids
|
||||
.get_mut(&lopen.fid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
// We always open files with O_CLOEXEC.
|
||||
let mut custom_flags: i32 = libc::O_CLOEXEC;
|
||||
for &(p9f, of) in &MAPPED_FLAGS {
|
||||
if (lopen.flags & p9f) != 0 {
|
||||
custom_flags |= of;
|
||||
}
|
||||
}
|
||||
|
||||
let file = fs::OpenOptions::new()
|
||||
.read((lopen.flags & P9_NOACCESS) == 0 || (lopen.flags & P9_RDWR) != 0)
|
||||
.write((lopen.flags & P9_WRONLY) != 0 || (lopen.flags & P9_RDWR) != 0)
|
||||
.append((lopen.flags & P9_APPEND) != 0)
|
||||
.truncate((lopen.flags & P9_TRUNC) != 0)
|
||||
.create((lopen.flags & P9_CREATE) != 0)
|
||||
.create_new((lopen.flags & P9_CREATE) != 0 && (lopen.flags & P9_EXCL) != 0)
|
||||
.custom_flags(custom_flags)
|
||||
.open(&fid.path)?;
|
||||
|
||||
fid.metadata = file.metadata()?;
|
||||
fid.file = Some(file);
|
||||
|
||||
Ok(Rmessage::Lopen(Rlopen {
|
||||
qid: metadata_to_qid(&fid.metadata),
|
||||
iounit: 0,
|
||||
}))
|
||||
}
|
||||
|
||||
fn lcreate(&mut self, lcreate: &Tlcreate) -> io::Result<Rmessage> {
|
||||
let fid = self.fids
|
||||
.get_mut(&lcreate.fid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
|
||||
if !fid.metadata.is_dir() {
|
||||
return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
|
||||
}
|
||||
|
||||
let name = Path::new(&lcreate.name);
|
||||
let path = join_path(fid.path.to_path_buf(), name, &*self.root)?;
|
||||
|
||||
let mut custom_flags: i32 = libc::O_CLOEXEC;
|
||||
for &(p9f, of) in &MAPPED_FLAGS {
|
||||
if (lcreate.flags & p9f) != 0 {
|
||||
custom_flags |= of;
|
||||
}
|
||||
}
|
||||
|
||||
let file = fs::OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.create(true)
|
||||
.append((lcreate.flags & P9_APPEND) != 0)
|
||||
.create_new((lcreate.flags & P9_EXCL) != 0)
|
||||
.custom_flags(custom_flags)
|
||||
.mode(lcreate.mode & 0o777)
|
||||
.open(&path)?;
|
||||
|
||||
fid.metadata = file.metadata()?;
|
||||
fid.file = Some(file);
|
||||
fid.path = path.into_boxed_path();
|
||||
|
||||
Ok(Rmessage::Lcreate(Rlcreate {
|
||||
qid: metadata_to_qid(&fid.metadata),
|
||||
iounit: 0,
|
||||
}))
|
||||
}
|
||||
|
||||
fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rmessage> {
|
||||
// symlinks are not allowed.
|
||||
Err(io::Error::from_raw_os_error(libc::EACCES))
|
||||
}
|
||||
|
||||
fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmessage> {
|
||||
// No nodes either.
|
||||
Err(io::Error::from_raw_os_error(libc::EACCES))
|
||||
}
|
||||
|
||||
fn rename(&mut self, rename: &Trename) -> io::Result<Rmessage> {
|
||||
let newname = Path::new(&rename.name);
|
||||
let buf = self.fids
|
||||
.get(&rename.dfid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))
|
||||
.map(|dfid| dfid.path.to_path_buf())?;
|
||||
let newpath = join_path(buf, newname, &*self.root)?;
|
||||
|
||||
let fid = self.fids
|
||||
.get_mut(&rename.fid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
|
||||
|
||||
fs::rename(&fid.path, &newpath)?;
|
||||
|
||||
// TODO: figure out if the client expects |fid.path| to point to
|
||||
// the renamed path.
|
||||
fid.path = newpath.into_boxed_path();
|
||||
Ok(Rmessage::Rename)
|
||||
}
|
||||
|
||||
fn readlink(&mut self, _readlink: &Treadlink) -> io::Result<Rmessage> {
|
||||
// symlinks are not allowed
|
||||
Err(io::Error::from_raw_os_error(libc::EACCES))
|
||||
}
|
||||
|
||||
fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rmessage> {
|
||||
let fid = self.fids
|
||||
.get_mut(&get_attr.fid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
|
||||
// Refresh the metadata since we were explicitly asked for it.
|
||||
fid.metadata = fs::metadata(&fid.path)?;
|
||||
|
||||
Ok(Rmessage::GetAttr(Rgetattr {
|
||||
valid: P9_GETATTR_BASIC,
|
||||
qid: metadata_to_qid(&fid.metadata),
|
||||
mode: fid.metadata.st_mode(),
|
||||
uid: fid.metadata.st_uid(),
|
||||
gid: fid.metadata.st_gid(),
|
||||
nlink: fid.metadata.st_nlink(),
|
||||
rdev: fid.metadata.st_rdev(),
|
||||
size: fid.metadata.st_size(),
|
||||
blksize: fid.metadata.st_blksize(),
|
||||
blocks: fid.metadata.st_blocks(),
|
||||
atime_sec: fid.metadata.st_atime() as u64,
|
||||
atime_nsec: fid.metadata.st_atime_nsec() as u64,
|
||||
mtime_sec: fid.metadata.st_mtime() as u64,
|
||||
mtime_nsec: fid.metadata.st_mtime_nsec() as u64,
|
||||
ctime_sec: fid.metadata.st_ctime() as u64,
|
||||
ctime_nsec: fid.metadata.st_ctime_nsec() as u64,
|
||||
btime_sec: 0,
|
||||
btime_nsec: 0,
|
||||
gen: 0,
|
||||
data_version: 0,
|
||||
}))
|
||||
}
|
||||
|
||||
fn set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<Rmessage> {
|
||||
let blocked_ops = P9_SETATTR_MODE | P9_SETATTR_UID | P9_SETATTR_GID;
|
||||
if set_attr.valid & blocked_ops != 0 {
|
||||
return Err(io::Error::from_raw_os_error(libc::EPERM));
|
||||
}
|
||||
|
||||
let fid = self.fids
|
||||
.get_mut(&set_attr.fid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
let file = fs::OpenOptions::new().write(true).open(&fid.path)?;
|
||||
|
||||
if set_attr.valid & P9_SETATTR_SIZE != 0 {
|
||||
file.set_len(set_attr.size)?;
|
||||
}
|
||||
|
||||
if set_attr.valid & (P9_SETATTR_ATIME | P9_SETATTR_MTIME) != 0 {
|
||||
let times = [
|
||||
libc::timespec {
|
||||
tv_sec: set_attr.atime_sec as _,
|
||||
tv_nsec: if set_attr.valid & P9_SETATTR_ATIME == 0 {
|
||||
libc::UTIME_OMIT
|
||||
} else if set_attr.valid & P9_SETATTR_ATIME_SET == 0 {
|
||||
libc::UTIME_NOW
|
||||
} else {
|
||||
set_attr.atime_nsec as _
|
||||
},
|
||||
},
|
||||
libc::timespec {
|
||||
tv_sec: set_attr.mtime_sec as _,
|
||||
tv_nsec: if set_attr.valid & P9_SETATTR_MTIME == 0 {
|
||||
libc::UTIME_OMIT
|
||||
} else if set_attr.valid & P9_SETATTR_MTIME_SET == 0 {
|
||||
libc::UTIME_NOW
|
||||
} else {
|
||||
set_attr.mtime_nsec as _
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Safe because file is valid and we have initialized times fully.
|
||||
let ret = unsafe { libc::futimens(file.as_raw_fd(), × as *const libc::timespec) };
|
||||
if ret < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
// The ctime would have been updated by any of the above operations so we only
|
||||
// need to change it if it was the only option given.
|
||||
if set_attr.valid & P9_SETATTR_CTIME != 0 && set_attr.valid & (!P9_SETATTR_CTIME) == 0 {
|
||||
// Setting -1 as the uid and gid will not actually change anything but will
|
||||
// still update the ctime.
|
||||
let ret = unsafe {
|
||||
libc::fchown(
|
||||
file.as_raw_fd(),
|
||||
libc::uid_t::max_value(),
|
||||
libc::gid_t::max_value(),
|
||||
)
|
||||
};
|
||||
if ret < 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Rmessage::SetAttr)
|
||||
}
|
||||
|
||||
fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rmessage> {
|
||||
Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
|
||||
}
|
||||
|
||||
fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<Rmessage> {
|
||||
Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
|
||||
}
|
||||
|
||||
fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rmessage> {
|
||||
let fid = self.fids
|
||||
.get_mut(&readdir.fid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
|
||||
if !fid.metadata.is_dir() {
|
||||
return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
|
||||
}
|
||||
|
||||
// The p9 client implementation in the kernel doesn't fully read all the contents
|
||||
// of the directory. This means that if some application performs a getdents()
|
||||
// call, followed by removing some files, followed by another getdents() call,
|
||||
// the offset that we get from the kernel is completely meaningless. Instead
|
||||
// we fully read the contents of the directory here and only re-read the directory
|
||||
// if the offset we get from the client is 0. Any other offset is served from the
|
||||
// directory entries in memory. This ensures consistency even if the directory
|
||||
// changes in between Treaddir messages.
|
||||
if readdir.offset == 0 {
|
||||
let mut offset = 0;
|
||||
let iter = fs::read_dir(&fid.path)?;
|
||||
let dirents = iter.map(|item| -> io::Result<Dirent> {
|
||||
let entry = item?;
|
||||
|
||||
let md = entry.metadata()?;
|
||||
let qid = metadata_to_qid(&md);
|
||||
|
||||
let ty = if md.is_dir() {
|
||||
libc::DT_DIR
|
||||
} else if md.is_file() {
|
||||
libc::DT_REG
|
||||
} else {
|
||||
libc::DT_UNKNOWN
|
||||
};
|
||||
|
||||
let name = entry
|
||||
.file_name()
|
||||
.into_string()
|
||||
.map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
|
||||
|
||||
let mut out = Dirent {
|
||||
qid: qid,
|
||||
offset: 0, // set below
|
||||
ty: ty,
|
||||
name: name,
|
||||
};
|
||||
|
||||
offset += out.byte_size() as u64;
|
||||
out.offset = offset;
|
||||
|
||||
Ok(out)
|
||||
});
|
||||
|
||||
// This is taking advantage of the fact that we can turn a Iterator of Result<T, E>
|
||||
// into a Result<FromIterator<T>, E> since Result implements FromIterator<Result<T, E>>.
|
||||
fid.dirents = Some(dirents.collect::<io::Result<Vec<Dirent>>>()?);
|
||||
}
|
||||
|
||||
let mut entries = fid.dirents
|
||||
.as_ref()
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?
|
||||
.iter()
|
||||
.skip_while(|entry| entry.offset <= readdir.offset)
|
||||
.peekable();
|
||||
|
||||
// Use an empty Rreaddir struct to figure out the maximum number of bytes that
|
||||
// can be returned.
|
||||
let header_size = Rframe {
|
||||
tag: 0,
|
||||
msg: Rmessage::Readdir(Rreaddir {
|
||||
data: Data(Vec::new()),
|
||||
}),
|
||||
}.byte_size();
|
||||
let count = min(self.msize - header_size, readdir.count);
|
||||
let mut cursor = Cursor::new(Vec::with_capacity(count as usize));
|
||||
|
||||
loop {
|
||||
let byte_size = if let Some(entry) = entries.peek() {
|
||||
entry.byte_size() as usize
|
||||
} else {
|
||||
// No more entries.
|
||||
break;
|
||||
};
|
||||
|
||||
if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size {
|
||||
// No more room in the buffer.
|
||||
break;
|
||||
}
|
||||
|
||||
// Safe because we just checked that the iterator contains at least one more item.
|
||||
entries.next().unwrap().encode(&mut cursor)?;
|
||||
}
|
||||
|
||||
Ok(Rmessage::Readdir(Rreaddir {
|
||||
data: Data(cursor.into_inner()),
|
||||
}))
|
||||
}
|
||||
|
||||
fn fsync(&mut self, fsync: &Tfsync) -> io::Result<Rmessage> {
|
||||
let file = self.fids
|
||||
.get(&fsync.fid)
|
||||
.and_then(|fid| fid.file.as_ref())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
|
||||
if fsync.datasync == 0 {
|
||||
file.sync_all()?;
|
||||
} else {
|
||||
file.sync_data()?;
|
||||
}
|
||||
Ok(Rmessage::Fsync)
|
||||
}
|
||||
|
||||
fn lock(&mut self, _lock: &Tlock) -> io::Result<Rmessage> {
|
||||
// File locking is not supported.
|
||||
Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
|
||||
}
|
||||
fn get_lock(&mut self, _get_lock: &Tgetlock) -> io::Result<Rmessage> {
|
||||
// File locking is not supported.
|
||||
Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
|
||||
}
|
||||
|
||||
fn link(&mut self, link: &Tlink) -> io::Result<Rmessage> {
|
||||
let newname = Path::new(&link.name);
|
||||
let buf = self.fids
|
||||
.get(&link.dfid)
|
||||
.map(|dfid| dfid.path.to_path_buf())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
let newpath = join_path(buf, newname, &*self.root)?;
|
||||
|
||||
let path = self.fids
|
||||
.get(&link.fid)
|
||||
.map(|fid| &fid.path)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
|
||||
fs::hard_link(path, &newpath)?;
|
||||
Ok(Rmessage::Link)
|
||||
}
|
||||
|
||||
fn mkdir(&mut self, mkdir: &Tmkdir) -> io::Result<Rmessage> {
|
||||
let fid = self.fids
|
||||
.get(&mkdir.dfid)
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
|
||||
let name = Path::new(&mkdir.name);
|
||||
let newpath = join_path(fid.path.to_path_buf(), name, &*self.root)?;
|
||||
|
||||
fs::DirBuilder::new()
|
||||
.recursive(false)
|
||||
.mode(mkdir.mode & 0o777)
|
||||
.create(&newpath)?;
|
||||
|
||||
Ok(Rmessage::Mkdir(Rmkdir {
|
||||
qid: metadata_to_qid(&fs::metadata(&newpath)?),
|
||||
}))
|
||||
}
|
||||
|
||||
fn rename_at(&mut self, rename_at: &Trenameat) -> io::Result<Rmessage> {
|
||||
let oldname = Path::new(&rename_at.oldname);
|
||||
let oldbuf = self.fids
|
||||
.get(&rename_at.olddirfid)
|
||||
.map(|dfid| dfid.path.to_path_buf())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
let oldpath = join_path(oldbuf, oldname, &*self.root)?;
|
||||
|
||||
let newname = Path::new(&rename_at.newname);
|
||||
let newbuf = self.fids
|
||||
.get(&rename_at.newdirfid)
|
||||
.map(|dfid| dfid.path.to_path_buf())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
let newpath = join_path(newbuf, newname, &*self.root)?;
|
||||
|
||||
fs::rename(&oldpath, &newpath)?;
|
||||
Ok(Rmessage::RenameAt)
|
||||
}
|
||||
|
||||
fn unlink_at(&mut self, unlink_at: &Tunlinkat) -> io::Result<Rmessage> {
|
||||
let name = Path::new(&unlink_at.name);
|
||||
let buf = self.fids
|
||||
.get(&unlink_at.dirfd)
|
||||
.map(|fid| fid.path.to_path_buf())
|
||||
.ok_or_else(|| io::Error::from_raw_os_error(libc::EBADF))?;
|
||||
let path = join_path(buf, name, &*self.root)?;
|
||||
|
||||
let md = fs::metadata(&path)?;
|
||||
if md.is_dir() && (unlink_at.flags & (libc::AT_REMOVEDIR as u32)) == 0 {
|
||||
return Err(io::Error::from_raw_os_error(libc::EISDIR));
|
||||
}
|
||||
|
||||
if md.is_dir() {
|
||||
fs::remove_dir(&path)?;
|
||||
} else {
|
||||
fs::remove_file(&path)?;
|
||||
}
|
||||
|
||||
Ok(Rmessage::UnlinkAt)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Most of the server implementation is tested via integration tests.
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn path_joins() {
|
||||
let root = PathBuf::from("/a/b/c");
|
||||
let path = PathBuf::from("/a/b/c/d/e/f");
|
||||
|
||||
assert_eq!(
|
||||
&join_path(path.clone(), "nested", &root).expect("normal"),
|
||||
Path::new("/a/b/c/d/e/f/nested")
|
||||
);
|
||||
|
||||
let p1 = join_path(path.clone(), "..", &root).expect("parent 1");
|
||||
assert_eq!(&p1, Path::new("/a/b/c/d/e/"));
|
||||
|
||||
let p2 = join_path(p1, "..", &root).expect("parent 2");
|
||||
assert_eq!(&p2, Path::new("/a/b/c/d/"));
|
||||
|
||||
let p3 = join_path(p2, "..", &root).expect("parent 3");
|
||||
assert_eq!(&p3, Path::new("/a/b/c/"));
|
||||
|
||||
let p4 = join_path(p3, "..", &root).expect("parent of root");
|
||||
assert_eq!(&p4, Path::new("/a/b/c/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_joins() {
|
||||
let root = PathBuf::from("/a");
|
||||
let path = PathBuf::from("/a/b");
|
||||
|
||||
join_path(path.clone(), ".", &root).expect_err("current directory");
|
||||
join_path(path.clone(), "c/d/e", &root).expect_err("too many components");
|
||||
join_path(path.clone(), "/c/d/e", &root).expect_err("absolute path");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue