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