blob: d3d75472ffd8f1306b2bfb50ffaa9c4520dc6247 [file] [log] [blame]
// 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,
// 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 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(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 & 0o755)
.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(), &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(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,
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));
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 & 0o755)
.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");
}
}