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::coding::*;
use crate::syscall;
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;
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), (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),
];
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;
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;
const _P9_GETATTR_ALL: u64 = 0x00003fff;
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;
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;
const MIN_MESSAGE_SIZE: u32 = 256;
const MAX_MESSAGE_SIZE: u32 = 64 * 1024 + 24;
#[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,
}
}
}
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,
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();
let res = unsafe {
libc::fstatat64(
d.as_raw_fd(),
name.as_ptr(),
st.as_mut_ptr(),
flags | libc::AT_SYMLINK_NOFOLLOW,
)
};
if res >= 0 {
Ok(unsafe { st.assume_init() })
} else {
Err(io::Error::last_os_error())
}
}
fn stat(f: &File) -> io::Result<libc::stat64> {
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 {
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,
})
}
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())
}
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> {
let fd = syscall!(unsafe {
libc::openat64(
parent.as_raw_fd(),
name.as_ptr(),
libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
)
})?;
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()))?;
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;
}
let fd = syscall!(unsafe {
libc::openat64(
proc.as_raw_fd(),
pathname.as_ptr(),
flags & !libc::O_NOFOLLOW,
)
})?;
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> {
let proc_cstr =
unsafe { CStr::from_bytes_with_nul_unchecked(b"/proc\0") };
let fd = syscall!(unsafe {
libc::openat64(
libc::AT_FDCWD,
proc_cstr.as_ptr(),
libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
)
})?;
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 async fn handle<'life0>(
&mut self,
tx: &'life0 Tframe,
) -> std::io::Result<Rframe> {
let Tframe { tag, msg } = tx;
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) => {
let error = format!("Tframe message decode failed: {}", e);
Err(io::Error::new(io::ErrorKind::InvalidData, error))
}
};
Ok(Rframe {
tag: *tag,
msg: rmsg.unwrap_or_else(error_to_rmessage),
})
}
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) => {
let error = format!("Tframe message decode failed: {}", e);
Err(io::Error::new(io::ErrorKind::InvalidData, error))
}
};
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> {
Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
}
fn attach(&mut self, attach: &Tattach) -> io::Result<Rattach> {
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)
})?;
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 {
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));
}
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<()> {
Ok(())
}
fn walk(&mut self, walk: &Twalk) -> io::Result<Rwalk> {
if walk.fid != walk.newfid && self.fids.contains_key(&walk.newfid) {
return Err(io::Error::from_raw_os_error(libc::EBADF));
}
let start = &self.fids.get(&walk.fid).ok_or_else(ebadf)?.path;
let expected_len = walk.wnames.len();
let mut mds = Vec::with_capacity(expected_len);
let names: Vec<String> = walk.wnames.to_vec();
match do_walk(
&self.proc,
names,
start,
self.cfg.ascii_casefold,
&mut mds,
) {
Ok(end) => {
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) => {
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> {
let file = self
.fids
.get_mut(&read.fid)
.and_then(|fid| fid.file.as_mut())
.ok_or_else(ebadf)?;
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<()> {
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();
syscall!(unsafe {
libc::fstatfs64(fid.path.as_raw_fd(), buf.as_mut_ptr())
})?;
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,
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, })
}
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 nm = lcreate.name.clone();
let name = string_to_cstring(nm)?;
let fd = syscall!(unsafe {
libc::openat64(
fid.path.as_raw_fd(),
name.as_ptr(),
flags,
lcreate.mode,
)
})?;
let file = unsafe { File::from_raw_fd(fd) };
let st = stat(&file)?;
fid.file = Some(file);
fid.filetype = FileType::Regular;
fid.path = lookup(&fid.path, &name)?;
Ok(Rlcreate {
qid: st.into(),
iounit: 0, })
}
fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink> {
Err(io::Error::from_raw_os_error(libc::EACCES))
}
fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod> {
Err(io::Error::from_raw_os_error(libc::EACCES))
}
fn rename(&mut self, _rename: &Trename) -> io::Result<()> {
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];
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)] 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 {
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
};
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 _
},
},
];
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());
}
}
if set_attr.valid & P9_SETATTR_CTIME != 0
&& set_attr.valid & (!P9_SETATTR_CTIME) == 0
{
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));
}
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
{
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(())
}
fn lock(&mut self, lock: &Tlock) -> io::Result<Rlock> {
let fd = self
.fids
.get(&lock.fid)
.and_then(|fid| fid.file.as_ref())
.ok_or_else(ebadf)?
.as_raw_fd();
syscall!(unsafe {
let mut stbuf: libc::stat64 = std::mem::zeroed();
libc::fstat64(fd, &mut stbuf)
})?;
Ok(Rlock {
status: P9_LOCK_SUCCESS,
})
}
fn get_lock(&mut self, get_lock: &Tgetlock) -> io::Result<Rgetlock> {
let fd = self
.fids
.get(&get_lock.fid)
.and_then(|fid| fid.file.as_ref())
.ok_or_else(ebadf)?
.as_raw_fd();
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.clone())?;
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.clone())?;
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.clone())?;
let newdir = self.fids.get(&rename_at.newdirfid).ok_or_else(ebadf)?;
let newname = string_to_cstring(rename_at.newname.clone())?;
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.clone())?;
syscall!(unsafe {
libc::unlinkat(
dir.path.as_raw_fd(),
name.as_ptr(),
unlink_at.flags as libc::c_int,
)
})?;
Ok(())
}
}
#[cfg(test)]
mod tests;