Rewrite of 9p filesystem

Old version barely worked, but the new version works very well.

Supports two backend types. Exporting a tree from the host filesystem
is supported of course, and this is one backend. The second backend
is a synthetic filesystem on which directories can be created and then
files can be added to the directories. The files added to the filesystem
are either backed by a file on the host filesystem or a buffer of bytes
in memory.
This commit is contained in:
Bruce Leidl 2019-09-20 16:01:15 -04:00
parent 1a587bb6cc
commit 98a1e9361e
11 changed files with 1973 additions and 1201 deletions

View File

@ -8,6 +8,7 @@ mod virtio_block;
pub use self::virtio_serial::VirtioSerial; pub use self::virtio_serial::VirtioSerial;
pub use self::virtio_9p::VirtioP9; pub use self::virtio_9p::VirtioP9;
pub use self::virtio_9p::SyntheticFS;
pub use self::virtio_rng::VirtioRandom; pub use self::virtio_rng::VirtioRandom;
pub use self::virtio_wl::VirtioWayland; pub use self::virtio_wl::VirtioWayland;
pub use self::virtio_block::VirtioBlock; pub use self::virtio_block::VirtioBlock;

View File

@ -1,404 +0,0 @@
use std::path::PathBuf;
use std::io;
use std::path::Path;
use std::fs;
use libc;
use crate::memory::GuestRam;
use super::pdu::{PduParser,P9Attr};
use super::fid::FidCache;
use super::filesystem::{FileSystem,FsTouch,FileSystemOps};
const P9_TSTATFS: u8 = 8;
const P9_TLOPEN: u8 = 12;
const P9_TLCREATE: u8 = 14;
const P9_TSYMLINK: u8 = 16;
//const P9_TMKNOD: u8 = 18;
//const P9_TRENAME: u8 = 20;
const P9_TREADLINK: u8 = 22;
const P9_TGETATTR: u8 = 24;
const P9_TSETATTR: u8 = 26;
const P9_TXATTRWALK: u8 = 30;
const P9_TXATTRCREATE: u8 = 32;
const P9_TREADDIR: u8 = 40;
const P9_TFSYNC: u8 = 50;
const P9_TLOCK: u8 = 52;
const P9_TGETLOCK: u8 = 54;
//const P9_TLINK: u8 = 70;
//const P9_TMKDIR: u8 = 72;
//const P9_TRENAMEAT: u8 = 74;
//const P9_TUNLINKAT: u8 = 76;
const P9_TVERSION:u8 = 100;
const P9_TATTACH :u8 = 104;
//const P9_TFLUSH: u8 = 108;
const P9_TWALK :u8 = 110;
const P9_TREAD: u8 = 116;
//const P9_TWRITE: u8 = 118;
const P9_TCLUNK: u8 = 120;
//const P9_REMOVE: u8 = 122;
const P9_LOCK_SUCCESS:u32 = 0;
const F_UNLCK: u8 = 2;
const P9_VERSION_DOTL:&str = "9P2000.L";
pub struct Commands {
filesystem: FileSystem,
fids: FidCache,
root_dir: PathBuf,
_memory: GuestRam,
}
impl Commands {
pub fn new(root_dir: PathBuf, init_path: PathBuf, memory: GuestRam) -> Commands {
let fsys = FileSystem::new(root_dir.clone(), init_path,true);
Commands {
filesystem: fsys.clone(),
fids: FidCache::new(fsys.clone()),
root_dir, _memory: memory,
}
}
fn handle_io_result(&self, cmd: u8, result: io::Result<()>) {
match result {
Ok(()) => (),
Err(e) => println!("io error in 9p command {} processing: {:?}",cmd, e),
}
}
pub fn handle(&mut self, pp: &mut PduParser) {
match pp.command() {
Ok(cmd) => {
let res = self.dispatch(cmd, pp);
self.handle_io_result(cmd,res);
},
Err(e) => self.handle_io_result(0,Err(e)),
}
}
fn dispatch(&mut self, cmd: u8, pp: &mut PduParser) -> io::Result<()> {
match cmd {
P9_TSTATFS => self.p9_statfs(pp)?,
P9_TLOPEN => self.p9_open(pp)?,
P9_TLCREATE => self.p9_create(pp)?,
P9_TSYMLINK => self.p9_symlink(pp)?,
//P9_TMKNOD => self.p9_mknod(pp)?,
//P9_TRENAME => self.p9_rename(pp)?,
P9_TREADLINK => self.p9_readlink(pp)?,
P9_TGETATTR => self.p9_getattr(pp)?,
P9_TSETATTR => self.p9_setattr(pp)?,
P9_TXATTRWALK => self.p9_unsupported(pp)?,
P9_TXATTRCREATE => self.p9_unsupported(pp)?,
P9_TREADDIR => self.p9_readdir(pp)?,
P9_TFSYNC => self.p9_fsync(pp)?,
P9_TLOCK => self.p9_lock(pp)?,
P9_TGETLOCK => self.p9_getlock(pp)?,
//P9_TLINK => self.p9_link(pp)?,
//P9_TMKDIR=> self.p9_mkdir(pp)?,
//P9_TRENAMEAT => self.p9_renameat(pp)?,
//P9_UNLINKAT => self.p9_unlinkat(pp)?,
P9_TVERSION => self.p9_version(pp)?,
P9_TATTACH => self.p9_attach(pp)?,
//P9_FLUSH => self.p9_flush(pp)?,
P9_TWALK => self.p9_walk(pp)?,
P9_TREAD => self.p9_read(pp)?,
//P9_WRITE => self.p9_write(pp)?,
P9_TCLUNK => self.p9_clunk(pp)?,
//P9_REMOVE => self.p9_remove(pp)?,
n => println!("unhandled 9p command: {}", n),
}
Ok(())
}
fn p9_unsupported(&self, pp: &mut PduParser) -> io::Result<()> {
pp.read_done()?;
pp.bail_err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
}
fn p9_statfs(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid = pp.r32()?;
pp.read_done()?;
match self.fids.statfs(fid) {
Ok(statfs) => {
pp.write_statfs(statfs)?;
pp.write_done()
},
Err(err) => pp.bail_err(err),
}
}
fn p9_version(&self, pp: &mut PduParser) -> io::Result<()> {
let msize = pp.r32()?;
let version = pp.read_string()?;
pp.read_done()?;
pp.w32(msize)?;
if version == P9_VERSION_DOTL {
pp.write_string(&version)?;
} else {
pp.write_string("unknown")?;
}
pp.write_done()
}
fn p9_attach(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid_val = pp.r32()?;
let _afid = pp.r32()?;
let _uname = pp.read_string()?;
let _aname = pp.read_string()?;
let uid = pp.r32()?;
pp.read_done()?;
self.fids.with_fid_mut(fid_val, |fid| {
fid.uid = uid;
fid.path.push("/");
});
match fs::metadata(&self.root_dir) {
Ok(ref meta) => {
pp.write_qid(meta)?;
pp.write_done()
}
Err(e) => pp.bail_err(e),
}
}
fn p9_open(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid = pp.r32()?;
let flags = pp.r32()?;
pp.read_done()?;
if let Err(err) = self.fids.open(fid, flags) {
return pp.bail_err(err);
}
let meta = match self.fids.metadata(fid) {
Ok(meta) => meta,
Err(err) => {
return pp.bail_err(err);
}
};
pp.write_qid(&meta)?;
// XXX iounit goes here
pp.w32(0)?;
pp.write_done()
}
fn p9_create(&mut self, pp: &mut PduParser) -> io::Result<()> {
let dfid = pp.r32()?;
let name = pp.read_string()?;
let flags = pp.r32()?;
let mode = pp.r32()?;
let gid = pp.r32()?;
pp.read_done()?;
match self.fids.create(dfid, name, flags, mode, gid) {
Ok(meta) => {
pp.write_statl(&meta)?;
pp.write_done()?;
},
Err(err) => return pp.bail_err(err),
}
Ok(())
}
fn p9_symlink(&mut self, pp: &mut PduParser) -> io::Result<()> {
let _fid = pp.r32()?;
let _name = pp.read_string()?;
let _old_path = pp.read_string()?;
let _gid = pp.r32()?;
pp.read_done()?;
// XXX
pp.write_done()
}
fn p9_read(&mut self, pp: &mut PduParser) -> io::Result<()> {
let id = pp.r32()?;
let off = pp.r64()?;
let cnt = pp.r32()?;
pp.read_done()?;
// space for size field
pp.w32(0)?;
match self.fids.fid_mut(id).read(off, cnt as usize, pp) {
Ok(nread) => {
// write nread in space reserved earlier
pp.w32_at(0, nread as u32);
pp.write_done()?;
}
Err(err) => {
println!("oops error on read: {:?}", err);
return pp.bail_err(err)
},
};
Ok(())
}
fn p9_readdir(&mut self, pp: &mut PduParser) -> io::Result<()> {
let id = pp.r32()?;
let off = pp.r64()?;
let cnt = pp.r32()?;
pp.read_done()?;
self.fids.readdir(id,off, cnt as usize, pp)
}
fn p9_clunk(&mut self, pp: &mut PduParser) -> io::Result<()> {
let id = pp.r32()?;
pp.read_done()?;
self.fids.clunk(id);
pp.write_done()
}
fn p9_readlink(&mut self, pp: &mut PduParser) -> io::Result<()> {
let id = pp.r32()?;
pp.read_done()?;
let link = self.fids.readlink(id)?;
pp.write_os_string(&link)?;
pp.write_done()
}
fn p9_getattr(&mut self, pp: &mut PduParser) -> io::Result<()> {
let id = pp.r32()?;
let _mask = pp.r64()?;
pp.read_done()?;
let meta = match self.fids.metadata(id) {
Ok(meta) => meta,
Err(e) => return pp.bail_err(e),
};
pp.write_statl(&meta)?;
pp.write_done()
}
fn do_setattr(&mut self, fid: u32, attr: P9Attr) -> io::Result<()> {
if attr.has_mode() {
self.fids.chmod(fid, attr.mode())?
}
if attr.has_atime() {
if attr.has_atime_set() {
self.fids.touch(fid, FsTouch::Atime,attr.atime())?
} else {
self.fids.touch(fid, FsTouch::AtimeNow,(0,0))?
}
}
if attr.has_mtime() {
if attr.has_mtime_set() {
self.fids.touch(fid, FsTouch::Mtime,attr.mtime())?
} else {
self.fids.touch(fid, FsTouch::MtimeNow,(0,0))?
}
}
if attr.has_chown() {
let (uid, gid) = attr.chown_ids();
self.fids.chown(fid, uid, gid)?;
}
if attr.has_size() {
self.fids.truncate(fid, attr.size())?;
}
Ok(())
}
fn p9_setattr(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid = pp.r32()?;
let attr = pp.read_attr()?;
pp.read_done()?;
if let Err(err) = self.do_setattr(fid, attr) {
return pp.bail_err(err)
}
pp.write_done()
}
// XXX look at walk in qemu
fn p9_walk(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid_id = pp.r32()?;
let new_fid_id = pp.r32()?;
let nwname = pp.r16()?;
self.fids.dup_fid(fid_id, new_fid_id);
let mut cur = self.fids.fid(new_fid_id).path.clone();
let mut metalist = Vec::new();
for _ in 0..nwname {
let s = pp.read_string()?;
let p = Path::new(&s);
if p.components().count() != 1 {
println!("uh...");
}
cur.push(p);
match self.filesystem.stat(&cur) {
Ok(m) => metalist.push(m),
Err(e) => {
pp.read_done()?;
return pp.bail_err(e)
},
}
}
self.fids.with_fid_mut(new_fid_id, |fid| {
fid.path = cur;
});
pp.read_done()?;
pp.w16(metalist.len() as u16)?;
for meta in metalist {
pp.write_qid(&meta)?;
}
pp.write_done()
}
fn p9_fsync(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid = pp.r32()?;
let dsync = pp.r32()?;
pp.read_done()?;
if let Err(err) = self.fids.fsync(fid, dsync != 0) {
return pp.bail_err(err);
}
pp.write_done()
}
fn p9_lock(&mut self, pp: &mut PduParser) -> io::Result<()> {
let _ = pp.r32()?;
let _ = pp.r8()?;
let _ = pp.r32()?;
let _ = pp.r64()?;
let _ = pp.r64()?;
let _ = pp.r32()?;
let _ = pp.read_string()?;
pp.read_done()?;
pp.w32(P9_LOCK_SUCCESS)?;
pp.write_done()
}
fn p9_getlock(&mut self, pp: &mut PduParser) -> io::Result<()> {
let _fid = pp.r32()?;
let _type = pp.r8()?;
let glock_start = pp.r64()?;
let glock_len = pp.r64()?;
let glock_proc_id = pp.r32()?;
let glock_client_id = pp.read_string()?;
pp.read_done()?;
pp.w8(F_UNLCK)?;
pp.w64(glock_start)?;
pp.w64(glock_len)?;
pp.w32(glock_proc_id)?;
pp.write_string(&glock_client_id)?;
pp.write_done()
}
}

View File

@ -0,0 +1,95 @@
use std::{fs, io};
use crate::devices::virtio_9p::{
pdu::PduParser, file::Qid,
};
pub struct Directory {
entries: Vec<P9DirEntry>,
}
impl Directory {
pub fn new() -> Directory {
Directory { entries: Vec::new() }
}
pub fn write_entries(&self, pp: &mut PduParser, offset: u64, size: usize) -> io::Result<()> {
let mut remaining = size;
pp.w32(0)?;
for entry in self.entries.iter()
.skip_while(|e| e.offset <= offset)
{
if entry.size() > remaining {
break;
}
entry.write(pp)?;
remaining -= entry.size();
}
pp.w32_at(0, (size - remaining) as u32);
Ok(())
}
pub fn push_entry(&mut self, entry: P9DirEntry) {
self.entries.push(entry)
}
}
pub struct P9DirEntry{
qid: Qid,
offset: u64,
dtype: u8,
name: String,
}
impl P9DirEntry {
pub fn new(qid: Qid, offset: u64, dtype: u8, name: &str) -> Self {
let name = name.to_string();
let offset = offset + Self::size_with_name(&name) as u64;
P9DirEntry { qid, offset, dtype, name }
}
pub fn from_direntry(entry: fs::DirEntry, offset: u64) -> io::Result<Self> {
let meta = entry.metadata()?;
let qid = Qid::from_metadata(&meta);
let dtype = if meta.is_dir() {
libc::DT_DIR
} else if meta.is_file() {
libc::DT_REG
} else {
libc::DT_UNKNOWN
};
let name = match entry.file_name().into_string() {
Ok(s) => s,
_ => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
};
// qid + offset + dtype + strlen + name
let offset = offset + Self::size_with_name(&name) as u64;
Ok(P9DirEntry{
qid, offset,
dtype, name,
})
}
pub fn offset(&self) -> u64 {
self.offset
}
fn size(&self) -> usize {
Self::size_with_name(&self.name)
}
fn size_with_name(name: &str) -> usize {
// qid + offset + dtype + strlen + name
13 + 8 + 1 + 2 + name.as_bytes().len()
}
fn write(&self, pp: &mut PduParser) -> io::Result<()> {
self.qid.write(pp)?;
pp.w64(self.offset)?;
pp.w8(self.dtype)?;
pp.write_string(&self.name)?;
Ok(())
}
}

View File

@ -1,230 +0,0 @@
use std::fs::Metadata;
use std::collections::HashMap;
use std::path::PathBuf;
use std::io::{self, Seek,Write};
use std::os::unix::io::AsRawFd;
use std::ffi::OsString;
use libc;
use super::pdu::PduParser;
use super::readdir::DirEntry;
use super::filesystem::{FileSystem,FileDescriptor,StatFs,FileSystemOps,FsTouch};
pub struct FidCache {
filesystem: FileSystem,
fidmap: HashMap<u32, Fid>,
}
impl FidCache {
pub fn new(filesystem: FileSystem) -> FidCache {
FidCache {
filesystem,
fidmap: HashMap::new(),
}
}
fn add_if_absent(&mut self, id: u32) {
if !self.fidmap.contains_key(&id) {
self.fidmap.insert(id, Fid::new());
}
}
pub fn fid(&mut self, id: u32) -> &Fid {
self.add_if_absent(id);
self.fidmap.get(&id).expect("fidmap does not have element")
}
pub fn _fid(&self, id: u32) -> &Fid {
self.fidmap.get(&id).expect("fidmap does not have element")
}
pub fn fid_mut(&mut self, id: u32) -> &mut Fid {
self.add_if_absent(id);
self.fidmap.get_mut(&id).expect("fidmap does not have element")
}
pub fn with_fid_mut<F,U>(&mut self, id: u32, f: F) -> U
where F: FnOnce(&mut Fid) -> U {
self.add_if_absent(id);
f(self.fid_mut(id))
}
#[allow(dead_code)]
pub fn with_fid<F,U>(&mut self, id: u32, f: F) -> U
where F: FnOnce(&Fid) -> U {
self.add_if_absent(id);
f(self.fid(id))
}
pub fn dup_fid(&mut self, old_id: u32, new_id: u32) {
self.fid_mut(new_id).path = self.fid(old_id).path.clone();
self.fid_mut(new_id).uid = self.fid(old_id).uid;
}
pub fn clunk(&mut self, id: u32) {
match self.fidmap.remove(&id) {
Some(ref mut fid) => fid.close(),
None => (),
}
}
pub fn open(&mut self, id: u32, flags: u32) -> io::Result<()> {
let path = self.fid(id).path.clone();
let fd = self.filesystem.open(&path, flags)?;
self.fid_mut(id).desc = fd;
Ok(())
}
fn fid_dir_join(&mut self, id: u32, name: &str) -> io::Result<PathBuf> {
let meta = self.metadata(id)?;
if !meta.is_dir() {
return Err(io::Error::from_raw_os_error(libc::EBADF));
}
let fname = PathBuf::from(name);
if fname.is_absolute() || fname.components().count() != 1 {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
let mut path = self.fid(id).path.clone();
path.push(fname);
Ok(path)
}
pub fn create(&mut self, id: u32, name: String, flags: u32, mode: u32, gid: u32) -> io::Result<Metadata> {
let path = self.fid_dir_join(id,&name)?;
self.filesystem.create(&path, flags, mode)?;
let uid = self.fid(id).uid;
self.filesystem.chown(&path, uid, gid)?;
self.filesystem.stat(&path)
}
pub fn readlink(&mut self, id: u32) -> io::Result<OsString> {
let path = self.fid(id).path.clone();
self.filesystem.readlink(&path)
}
pub fn metadata(&mut self, id: u32) -> io::Result<Metadata> {
let path = self.fid(id).path.clone();
self.filesystem.stat(&path)
}
pub fn readdir(&mut self, id: u32, off: u64, len: usize, pp: &mut PduParser) -> io::Result<()> {
//let is_dir = self.fid(id).desc.is_dir();
if off != 0 {
//self.fid_mut(id).desc.borrow_dir().unwrap().seek(off as i64);
}
self.fid_mut(id).readdir(len, pp)
}
pub fn chmod(&mut self, id: u32, mode: u32) -> io::Result<()> {
let path = self.fid(id).path.clone();
self.filesystem.chmod(&path, mode)
}
pub fn chown(&mut self, id: u32, uid: u32, gid: u32) -> io::Result<()> {
let path = self.fid(id).path.clone();
self.filesystem.chown(&path, uid, gid)
}
pub fn touch(&mut self, id: u32, which: FsTouch, tv: (u64,u64)) -> io::Result<()> {
let path = self.fid(id).path.clone();
self.filesystem.touch(&path, which, tv)
}
pub fn truncate(&mut self, _id: u32, _size: u64) -> io::Result<()> {
Ok(())
}
pub fn statfs(&mut self, fid: u32) -> io::Result<StatFs> {
let path = self.fid(fid).path.clone();
self.filesystem.statfs(&path)
}
pub fn fsync(&mut self, fid: u32, datasync: bool) -> io::Result<()> {
match self.fid(fid).desc {
FileDescriptor::File(ref file) => {
let fd = file.as_raw_fd();
unsafe {
let res = if datasync {
libc::fdatasync(fd)
} else {
libc::fsync(fd)
};
if res < 0 {
return Err(io::Error::last_os_error());
}
}
},
FileDescriptor::Dir(ref dir) => { return dir.fsync(); },
FileDescriptor::None => { return Err(io::Error::from_raw_os_error(libc::EBADF))},
};
Ok(())
}
}
pub struct Fid {
pub uid: u32,
pub path: PathBuf,
desc: FileDescriptor,
}
impl Fid {
fn new() -> Fid {
Fid {
uid: 0, path: PathBuf::new(), desc: FileDescriptor::None,
}
}
pub fn read(&mut self, offset: u64, len: usize, pp: &mut PduParser) -> io::Result<(usize)> {
self.desc.borrow_file()?.seek(io::SeekFrom::Start(offset))?;
pp.chain.copy_from_reader(self.desc.borrow_file()?, len)
}
fn dirent_len(dent: &DirEntry) -> usize {
// qid + offset + type + strlen + str
return 13 + 8 + 1 + 2 + dent.name_bytes().len()
}
fn write_dirent(dent: &DirEntry, pp: &mut PduParser) -> io::Result<()> {
pp.write_qid_path_only(dent.ino())?;
pp.w64(dent.offset())?;
pp.w8(dent.file_type())?;
pp.w16(dent.name_bytes().len() as u16)?;
pp.chain.write(&dent.name_bytes())?;
Ok(())
}
pub fn readdir(&mut self, len: usize, pp: &mut PduParser) -> io::Result<()> {
let mut write_len = 0_usize;
pp.w32(0)?;
while let Some(entry) = self.desc.borrow_dir()?.next() {
match entry {
Ok(ref dent) => {
let dlen = Fid::dirent_len(dent);
if write_len + dlen > len {
self.desc.borrow_dir()?.restore_last_pos();
break;
}
write_len += dlen;
Fid::write_dirent(dent, pp)?;
}
Err(err) => return pp.bail_err(err),
}
}
pp.w32_at(0, write_len as u32);
pp.write_done()
}
pub fn close(&mut self) {
self.desc = FileDescriptor::None;
}
}

View File

@ -0,0 +1,366 @@
use std::cell::{RefCell, RefMut};
use std::collections::BTreeMap;
use std::{io, fmt};
use std::path::{Path, PathBuf, Component};
use std::fs::{Metadata, File};
use std::os::linux::fs::MetadataExt;
use std::os::unix::fs::FileExt;
use crate::devices::virtio_9p::{
pdu::PduParser, directory::Directory, filesystem::FileSystemOps,
};
use std::io::{Cursor, SeekFrom, Seek, Read};
use std::sync::{RwLock, Arc};
pub const P9_DOTL_RDONLY: u32 = 0o00000000;
pub const P9_DOTL_WRONLY: u32 = 0o00000001;
pub const P9_DOTL_RDWR: u32 = 0o00000002;
const _P9_DOTL_NOACCESS: u32 = 0o00000003;
const P9_DOTL_CREATE: u32 = 0o00000100;
const P9_DOTL_EXCL: u32 = 0o00000200;
const P9_DOTL_NOCTTY: u32 = 0o00000400;
const P9_DOTL_TRUNC: u32 = 0o00001000;
const P9_DOTL_APPEND: u32 = 0o00002000;
const P9_DOTL_NONBLOCK: u32 = 0o00004000;
const P9_DOTL_DSYNC: u32 = 0o00010000;
const P9_DOTL_FASYNC: u32 = 0o00020000;
const P9_DOTL_DIRECT: u32 = 0o00040000;
const P9_DOTL_LARGEFILE: u32 = 0o00100000;
const P9_DOTL_DIRECTORY: u32 = 0o00200000;
const P9_DOTL_NOFOLLOW: u32 = 0o00400000;
const P9_DOTL_NOATIME: u32 = 0o01000000;
const _P9_DOTL_CLOEXEC: u32 = 0o02000000;
const P9_DOTL_SYNC: u32 = 0o04000000;
pub const P9_QTFILE: u8 = 0x00;
pub const P9_QTSYMLINK: u8 = 0x02;
pub const P9_QTDIR: u8 = 0x80;
#[derive(Clone)]
pub struct Buffer<T: AsRef<[u8]>>(Arc<RwLock<Cursor<T>>>);
impl <T: AsRef<[u8]>> Buffer <T> {
pub fn new(bytes: T) -> Self {
Buffer(Arc::new(RwLock::new(Cursor::new(bytes))))
}
pub fn read_at(&self, buffer: &mut [u8], offset: u64) -> io::Result<usize> {
let mut lock = self.0.write().unwrap();
lock.seek(SeekFrom::Start(offset))?;
lock.read(buffer)
}
pub fn write_at(&self, _buffer: &[u8], _offset: u64) -> io::Result<usize> {
return Err(io::Error::from_raw_os_error(libc::EPERM))
}
}
enum FileObject {
File(File),
BufferFile(Buffer<&'static [u8]>),
NotAFile,
}
pub struct P9File {
file: FileObject,
}
impl P9File {
pub fn new_not_a_file() -> Self {
P9File { file: FileObject::NotAFile }
}
pub fn from_file(file: File) -> Self {
P9File { file: FileObject::File(file) }
}
pub fn from_buffer(buffer: Buffer<&'static [u8]>) -> Self {
P9File { file: FileObject::BufferFile(buffer) }
}
pub fn sync_all(&self) -> io::Result<()> {
match self.file {
FileObject::File(ref f) => f.sync_all(),
_ => Ok(()),
}
}
pub fn sync_data(&self) -> io::Result<()> {
match self.file {
FileObject::File(ref f) => f.sync_data(),
_ => Ok(()),
}
}
pub fn read_at(&self, buffer: &mut [u8], offset: u64) -> io::Result<usize> {
match self.file {
FileObject::File(ref f) => f.read_at(buffer,offset),
FileObject::BufferFile(ref f) => f.read_at(buffer, offset),
FileObject::NotAFile => Ok(0),
}
}
pub fn write_at(&self, buffer: &[u8], offset: u64) -> io::Result<usize> {
match self.file {
FileObject::File(ref f) => f.write_at(buffer,offset),
FileObject::BufferFile(ref f) => f.write_at(buffer, offset),
FileObject::NotAFile => Ok(0),
}
}
}
#[derive(Copy,Clone)]
pub struct Qid {
qtype: u8,
version: u32,
path: u64,
}
impl Qid {
pub fn new(qtype: u8, version: u32, path: u64) -> Qid {
Qid { qtype, version, path }
}
pub fn from_metadata(meta: &Metadata) -> Qid {
let qtype = if meta.is_dir() {
P9_QTDIR
} else if meta.is_file() {
P9_QTFILE
} else if meta.file_type().is_symlink() {
P9_QTSYMLINK
} else {
0
};
let version = meta.st_mtime() as u32 ^ (meta.st_size() << 8) as u32;
let path = meta.st_ino();
Qid::new(qtype, version, path)
}
pub fn is_dir(&self) -> bool {
self.qtype == P9_QTDIR
}
pub fn write(&self, pp: &mut PduParser) -> io::Result<()> {
pp.w8(self.qtype)?;
pp.w32(self.version)?;
pp.w64(self.path)?;
Ok(())
}
}
pub fn translate_p9_flags(flags: u32, is_root: bool) -> libc::c_int {
let flagmap = &[
(P9_DOTL_CREATE, libc::O_CREAT),
(P9_DOTL_EXCL, libc::O_EXCL),
(P9_DOTL_NOCTTY, libc::O_NOCTTY),
(P9_DOTL_TRUNC, libc::O_TRUNC),
(P9_DOTL_APPEND, libc::O_APPEND),
(P9_DOTL_NONBLOCK, libc::O_NONBLOCK),
(P9_DOTL_DSYNC, libc::O_DSYNC),
(P9_DOTL_FASYNC, libc::O_ASYNC),
(P9_DOTL_DIRECT, libc::O_DIRECT),
(P9_DOTL_LARGEFILE, libc::O_LARGEFILE),
(P9_DOTL_DIRECTORY, libc::O_DIRECTORY),
(P9_DOTL_NOFOLLOW, libc::O_NOFOLLOW),
(P9_DOTL_SYNC, libc::O_SYNC),
];
let mut custom = flagmap.iter()
.fold(0, |acc, (a,b)|
if flags & *a != 0 { acc | *b } else { acc });
if is_root && flags & P9_DOTL_NOATIME != 0 {
custom |= libc::O_NOATIME;
}
/* copied from qemu */
custom &= !(libc::O_NOCTTY|libc::O_ASYNC|libc::O_CREAT);
custom &= !libc::O_DIRECT;
custom
}
pub struct Fids<T: FileSystemOps> {
ops: T,
root: PathBuf,
fidmap: BTreeMap<u32, Fid<T>>,
}
impl <T: FileSystemOps> Fids<T> {
pub fn new(root: PathBuf, ops: T) -> Self {
Fids {
ops,
root,
fidmap: BTreeMap::new(),
}
}
pub fn fid(&self, id: u32) -> io::Result<&Fid<T>> {
self.fidmap.get(&id).ok_or(Self::bad_fd_error())
}
pub fn fid_mut(&mut self, id: u32) -> io::Result<&mut Fid<T>> {
self.fidmap.get_mut(&id).ok_or(Self::bad_fd_error())
}
pub fn read_fid(&self, pp: &mut PduParser) -> io::Result<&Fid<T>> {
let id = pp.r32()?;
self.fid(id)
}
pub fn read_new_path(&self, pp: &mut PduParser) -> io::Result<PathBuf> {
let fid = self.read_fid(pp)?;
let name = pp.read_string()?;
fid.join_name(&self.root, &name)
}
pub fn path_join_name(&self, qid: Qid, path: &Path, name: &str) -> io::Result<PathBuf> {
Fid::<T>::path_join_name(qid, path, &self.root, name)
}
pub fn clear(&mut self) {
self.fidmap.clear()
}
pub fn add(&mut self, fid: Fid<T>) {
self.fidmap.insert(fid.id, fid);
}
pub fn exists(&self, id: u32) -> bool {
self.fidmap.contains_key(&id)
}
pub fn remove(&mut self, id: u32) -> io::Result<Fid<T>> {
match self.fidmap.remove(&id) {
Some(fid) => Ok(fid),
None => Err(Self::bad_fd_error())
}
}
pub fn create<P: Into<PathBuf>>(&self, id: u32, path: P) -> io::Result<Fid<T>> {
Fid::create(self.ops.clone(), id, path)
}
pub fn read_qid(&self, path: &Path) -> io::Result<Qid> {
self.ops.read_qid(path)
}
fn bad_fd_error() -> io::Error {
io::Error::from_raw_os_error(libc::EBADF)
}
}
pub struct Fid<T: FileSystemOps> {
ops: T,
id: u32,
path: PathBuf,
qid: Qid,
file: Option<P9File>,
directory: RefCell<Option<Directory>>,
}
impl <T: FileSystemOps> Fid<T> {
fn create<P: Into<PathBuf>>(ops: T, id: u32, path: P) -> io::Result<Self> {
let path = path.into();
let qid = ops.read_qid(&path)?;
Ok(Fid {
ops, id, path, qid,
file: None,
directory: RefCell::new(None),
})
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn qid(&self) -> Qid {
self.qid
}
pub fn id(&self) -> u32 {
self.id
}
pub fn write_stat(&self, pp: &mut PduParser) -> io::Result<()> {
self.ops.write_stat(self.path(), pp)
}
pub fn reload_qid(&mut self) -> io::Result<()> {
self.qid = self.ops.read_qid(self.path())?;
Ok(())
}
pub fn set_file(&mut self, file: P9File) {
self.file = Some(file)
}
pub fn set_path<P: Into<PathBuf>>(&mut self, path: P) -> io::Result<()> {
self.path = path.into();
self.reload_qid()
}
pub fn write_qid(&self, pp: &mut PduParser) -> io::Result<()> {
self.qid.write(pp)
}
pub fn is_dir(&self) -> bool {
self.qid.is_dir()
}
pub fn file(&self) -> io::Result<&P9File> {
match self.file.as_ref() {
Some(file) => Ok(file),
None => system_error(libc::EBADF),
}
}
pub fn join_name(&self, root: &Path, name: &str) -> io::Result<PathBuf> {
Self::path_join_name(self.qid, self.path(), root, name)
}
fn path_join_name(qid: Qid, path: &Path, root: &Path, name: &str) -> io::Result<PathBuf> {
if !qid.is_dir() {
return system_error(libc::ENOTDIR);
}
let p= Path::new(name);
if p.components().count() > 1 {
return system_error(libc::EINVAL);
}
let mut path = path.to_path_buf();
match p.components().next() {
Some(Component::ParentDir) => {
path.pop();
if !path.starts_with(root) {
return system_error(libc::EINVAL);
}
}
Some(Component::Normal(name)) => path.push(name),
None => {},
_ => return system_error(libc::EINVAL),
};
Ok(path)
}
pub fn load_directory(&self) -> io::Result<()> {
if !self.is_dir() {
return system_error(libc::ENOTDIR);
}
let dir = self.ops.readdir_populate(self.path())?;
self.directory.replace(Some(dir));
Ok(())
}
pub fn directory(&self) -> RefMut<Option<Directory>>{
self.directory.borrow_mut()
}
}
impl <T: FileSystemOps> fmt::Display for Fid<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Fid:({}, id={})", self.path().display(), self.id)
}
}
fn system_error<T>(errno: libc::c_int) -> io::Result<T> {
Err(io::Error::from_raw_os_error(errno))
}

View File

@ -1,46 +1,21 @@
use std::mem; use std::ffi::{CString,OsString};
use std::ffi::CString; use std::fs::{self, File, Metadata, OpenOptions};
use std::ffi::OsString;
use std::os::unix::ffi::OsStrExt;
use std::fs::{self,File,Metadata,OpenOptions};
use std::io; use std::io;
use std::path::{PathBuf,Path,Component}; use std::mem;
use std::os::unix;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::{DirBuilderExt,OpenOptionsExt,PermissionsExt};
use std::os::linux::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::os::unix::fs::OpenOptionsExt;
use libc; use libc;
use crate::devices::virtio_9p::file::{
P9File, P9_DOTL_RDONLY, P9_DOTL_RDWR, P9_DOTL_WRONLY, translate_p9_flags, Qid
};
use crate::devices::virtio_9p::pdu::PduParser;
use crate::devices::virtio_9p::directory::{Directory, P9DirEntry};
use super::readdir::ReadDir;
const MAX_SYMLINKS: usize = 16;
const PATH_MAX: usize = 1024; // it's actually 4096 on linux
const O_RDONLY: u32 = 0;
const O_WRONLY: u32 = 1;
const O_RDWR: u32 = 2;
const O_ACCMODE: u32 = 0x3;
const ALLOWED_FLAGS: u32 = (libc::O_APPEND | libc::O_TRUNC | libc::O_LARGEFILE
| libc::O_DIRECTORY | libc::O_DSYNC | libc::O_NOFOLLOW
| libc::O_SYNC) as u32;
#[derive(Default)]
pub struct StatFs {
pub f_type: u32,
pub f_bsize: u32,
pub f_blocks: u64,
pub f_bfree: u64,
pub f_bavail: u64,
pub f_files: u64,
pub f_ffree: u64,
pub fsid: u64,
pub f_namelen: u32,
}
impl StatFs {
fn new() -> StatFs {
StatFs { ..Default::default() }
}
}
pub enum FsTouch { pub enum FsTouch {
Atime, Atime,
@ -49,195 +24,90 @@ pub enum FsTouch {
MtimeNow, MtimeNow,
} }
pub trait FileSystemOps { pub trait FileSystemOps: Clone+Sync+Send {
fn open(&self, path: &Path, flags: u32) -> io::Result<FileDescriptor>; fn read_qid(&self, path: &Path) -> io::Result<Qid>;
fn open_dir(&self, path: &Path) -> io::Result<FileDescriptor>; fn write_stat(&self, path: &Path, pp: &mut PduParser) -> io::Result<()>;
fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result<FileDescriptor>; fn open(&self, path: &Path, flags: u32) -> io::Result<P9File>;
fn stat(&self, path: &Path) -> io::Result<Metadata>; fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result<P9File>;
fn statfs(&self, path: &Path) -> io::Result<StatFs>; fn write_statfs(&self, path: &Path, pp: &mut PduParser) -> io::Result<()>;
fn chown(&self, path: &Path, uid: u32, gid: u32) -> io::Result<()>; fn chown(&self, path: &Path, uid: u32, gid: u32) -> io::Result<()>;
fn chmod(&self, path: &Path, mode: u32) -> io::Result<()>; fn set_mode(&self, path: &Path, mode: u32) -> io::Result<()>;
fn touch(&self, path: &Path, which: FsTouch, tv: (u64, u64)) -> io::Result<()>; fn touch(&self, path: &Path, which: FsTouch, tv: (u64, u64)) -> io::Result<()>;
fn truncate(&self, path: &Path, size: u64) -> io::Result<()>; fn truncate(&self, path: &Path, size: u64) -> io::Result<()>;
fn readlink(&self, path: &Path) -> io::Result<OsString>; fn readlink(&self, path: &Path) -> io::Result<OsString>;
// fn symlink(&self, target: &Path, linkpath: &Path) -> io::Result<()>; fn symlink(&self, target: &Path, linkpath: &Path) -> io::Result<()>;
fn link(&self, target: &Path, newpath: &Path) -> io::Result<()>;
fn rename(&self, from: &Path, to: &Path) -> io::Result<()>;
fn remove_file(&self, path: &Path) -> io::Result<()>;
fn remove_dir(&self, path: &Path) -> io::Result<()>;
fn create_dir(&self, path: &Path, mode: u32) -> io::Result<()>;
fn readdir_populate(&self, path: &Path) -> io::Result<Directory>;
} }
#[derive(Clone)] #[derive(Clone)]
pub struct FileSystem { pub struct FileSystem {
init_path: PathBuf, root: PathBuf,
resolver: PathResolver,
readonly: bool, readonly: bool,
} euid_root: bool,
pub enum FileDescriptor {
None,
Dir(ReadDir),
File(File),
}
impl FileDescriptor {
#[allow(dead_code)]
pub fn is_file(&self) -> bool {
match *self {
FileDescriptor::File(..) => true,
_ => false,
}
}
#[allow(dead_code)]
pub fn is_dir(&self) -> bool {
match *self {
FileDescriptor::Dir(..) => true,
_ => false,
}
}
pub fn borrow_file(&mut self) -> io::Result<&mut File> {
match *self {
FileDescriptor::File(ref mut file_ref) => Ok(file_ref),
_ => Err(os_err(libc::EBADF)),
}
}
pub fn borrow_dir(&mut self) -> io::Result<&mut ReadDir> {
match *self {
FileDescriptor::Dir(ref mut dir_ref) => Ok(dir_ref),
_ => Err(os_err(libc::EBADF)),
}
}
} }
impl FileSystem { impl FileSystem {
pub fn new(root: PathBuf, init_path: PathBuf, readonly: bool) -> FileSystem { pub fn new(root: PathBuf, readonly: bool) -> FileSystem {
FileSystem { resolver: PathResolver::new(root), init_path, readonly } let euid_root = Self::is_euid_root();
FileSystem { root, readonly, euid_root }
} }
fn fullpath(&self, path: &Path) -> io::Result<PathBuf> { pub fn is_euid_root() -> bool {
if path.to_str().unwrap() == "/phinit" { unsafe { libc::geteuid() == 0 }
return Ok(self.init_path.clone()) }
pub fn create_with_flags(path: &Path, flags: u32, mode: u32, is_root: bool) -> io::Result<File> {
let rdwr = flags & libc::O_ACCMODE as u32;
let flags = translate_p9_flags(flags, is_root) &!libc::O_TRUNC;
OpenOptions::new()
.read(rdwr == P9_DOTL_RDONLY || rdwr == P9_DOTL_RDWR)
.write(rdwr == P9_DOTL_WRONLY || rdwr == P9_DOTL_RDWR)
.custom_flags(flags)
.create_new(true)
.mode(mode)
.open(path)
}
pub fn open_with_flags(path: &Path, flags: u32, is_root: bool) -> io::Result<File> {
let rdwr = flags & libc::O_ACCMODE as u32;
let flags = translate_p9_flags(flags, is_root);
OpenOptions::new()
.read(rdwr == P9_DOTL_RDONLY || rdwr == P9_DOTL_RDWR)
.write(rdwr == P9_DOTL_WRONLY || rdwr == P9_DOTL_RDWR)
.custom_flags(flags)
.open(path)
}
fn new_file(&self, file: File) -> P9File {
P9File::from_file(file)
}
fn metadata(&self, path: &Path) -> io::Result<Metadata> {
let path = self.canonicalize(path)?;
path.symlink_metadata()
}
fn canonicalize_parent(&self, path: &Path) -> io::Result<PathBuf> {
let parent = path.parent()
.ok_or(io::Error::from_raw_os_error(libc::ENOENT))?;
let parent = self.canonicalize(parent)?;
let filename = path.file_name()
.ok_or(io::Error::from_raw_os_error(libc::ENOENT))?;
Ok(parent.join(filename))
}
fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
let canon = path.canonicalize()?;
if !canon.starts_with(&self.root) {
return Err(io::Error::from_raw_os_error(libc::EIO))
} }
self.resolver.fullpath(path) Ok(canon)
}
fn flags_to_open_options(&self, flags: u32) -> io::Result<OpenOptions> {
let acc = flags & O_ACCMODE;
let mut oo = OpenOptions::new();
if self.readonly && acc != O_RDONLY {
return Err(io::Error::from_raw_os_error(libc::EACCES));
}
match acc {
O_RDONLY => { oo.read(true).write(false); }
O_WRONLY => { oo.read(false).write(true); }
O_RDWR => { oo.read(true).write(true); }
_ => return Err(os_err(libc::EINVAL))
}
// There should never be a symlink in path but add O_NOFOLLOW anyways
let custom = libc::O_NOFOLLOW | (flags & ALLOWED_FLAGS) as i32;
oo.custom_flags(custom);
Ok(oo)
}
}
///
/// Resolves paths into a canonical path which is always no higher
/// than the `root` path.
#[derive(Clone)]
struct PathResolver {
root: PathBuf,
}
impl PathResolver {
fn new(root: PathBuf) -> PathResolver {
// root must be absolute path
PathResolver{ root }
}
///
/// Canonicalize `path` so that .. segments in both in
/// the path itself and any symlinks in the path do
/// not escape. The returned path will not contain any
/// symlinks and refers to a path which is a subdirectory
/// of `self.root`
fn resolve_path(&self, path: &Path) -> io::Result<PathBuf> {
let mut buf = PathBuf::from(path);
let mut nlinks = 0_usize;
while self._resolve(&mut buf)? {
nlinks += 1;
if nlinks > MAX_SYMLINKS {
return Err(io::Error::from_raw_os_error(libc::ELOOP))
}
if buf.as_os_str().len() > PATH_MAX {
return Err(io::Error::from_raw_os_error(libc::ENAMETOOLONG))
}
}
Ok(buf)
}
fn is_path_symlink(path: &Path) -> bool {
match path.symlink_metadata() {
Ok(meta) => meta.file_type().is_symlink(),
Err(..) => false
}
}
fn fullpath(&self, path: &Path) -> io::Result<PathBuf> {
let resolved = self.resolve_path(path)?;
Ok(self.realpath(&resolved))
}
fn realpath(&self, path: &Path) -> PathBuf {
let mut cs = path.components();
if path.is_absolute() {
cs.next();
}
self.root.join(cs.as_path())
}
fn resolve_symlink(&self, path: &mut PathBuf) -> io::Result<bool> {
let realpath = self.realpath(path);
if PathResolver::is_path_symlink(&realpath) {
path.pop();
path.push(realpath.read_link()?);
return Ok(true)
}
Ok(false)
}
fn resolve_component(&self, c: Component, pathbuf: &mut PathBuf) -> io::Result<bool> {
match c {
Component::RootDir => pathbuf.push("/"),
Component::CurDir | Component::Prefix(..) => (),
Component::ParentDir => { pathbuf.pop(); },
Component::Normal(name) => {
pathbuf.push(name);
let link = self.resolve_symlink(pathbuf)?;
return Ok(link)
}
};
Ok(false)
}
fn _resolve(&self, path: &mut PathBuf) -> io::Result<bool> {
let copy = (*path).clone();
let mut components = copy.components();
path.push("/");
while let Some(c) = components.next() {
if self.resolve_component(c, path)? {
let tmp = path.join(components.as_path());
path.push(tmp);
return Ok(true)
}
}
Ok(false)
} }
} }
@ -246,68 +116,80 @@ fn cstr(path: &Path) -> io::Result<CString> {
} }
impl FileSystemOps for FileSystem { impl FileSystemOps for FileSystem {
fn open(&self, path: &Path, flags: u32) -> io::Result<FileDescriptor> { fn read_qid(&self, path: &Path) -> io::Result<Qid> {
let fullpath = self.fullpath(path)?; let meta = self.metadata(&path)?;
let meta = fullpath.metadata()?; let qid = Qid::from_metadata(&meta);
if meta.is_dir() { Ok(qid)
let read_dir = ReadDir::open(&fullpath)?;
return Ok(FileDescriptor::Dir(read_dir))
}
let options = self.flags_to_open_options(flags)?;
let file = options.open(&fullpath)?;
return Ok(FileDescriptor::File(file))
} }
fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result<FileDescriptor> { fn write_stat(&self, path: &Path, pp: &mut PduParser) -> io::Result<()> {
let fullpath = self.fullpath(path)?; let meta = self.metadata(path)?;
let mut options = self.flags_to_open_options(flags)?;
options.create(true); const P9_STATS_BASIC: u64 = 0x000007ff;
options.mode(mode & 0o777); pp.w64(P9_STATS_BASIC)?;
let file = options.open(&fullpath)?;
return Ok(FileDescriptor::File(file)) let qid = Qid::from_metadata(&meta);
qid.write(pp)?;
pp.w32(meta.st_mode())?;
pp.w32(meta.st_uid())?;
pp.w32(meta.st_gid())?;
pp.w64(meta.st_nlink())?;
pp.w64(meta.st_rdev())?;
pp.w64(meta.st_size())?;
pp.w64(meta.st_blksize())?;
pp.w64(meta.st_blocks())?;
pp.w64(meta.st_atime() as u64)?;
pp.w64(meta.st_atime_nsec() as u64)?;
pp.w64(meta.st_mtime() as u64)?;
pp.w64(meta.st_mtime_nsec() as u64)?;
pp.w64(meta.st_ctime() as u64)?;
pp.w64(meta.st_ctime_nsec() as u64)?;
pp.w64(0)?;
pp.w64(0)?;
pp.w64(0)?;
pp.w64(0)?;
Ok(())
} }
fn open_dir(&self, path: &Path) -> io::Result<FileDescriptor> { fn open(&self, path: &Path, flags: u32) -> io::Result<P9File> {
let fullpath = self.fullpath(path)?; let path = self.canonicalize(path)?;
let read_dir = ReadDir::open(&fullpath)?; let file =FileSystem::open_with_flags(&path, flags, self.euid_root)?;
return Ok(FileDescriptor::Dir(read_dir)) Ok(self.new_file(file))
} }
fn stat(&self, path: &Path) -> io::Result<Metadata> { fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result<P9File> {
let fullpath = self.fullpath(path)?; let path = self.canonicalize_parent(path)?;
let meta = fullpath.metadata()?; let file = FileSystem::create_with_flags(&path, flags, mode, self.euid_root)?;
Ok(meta) Ok(self.new_file(file))
} }
fn statfs(&self, path: &Path) -> io::Result<StatFs> { fn write_statfs(&self, path: &Path, pp: &mut PduParser) -> io::Result<()> {
let fullpath = self.fullpath(path)?; let path = self.canonicalize(path)?;
let path_cstr = cstr(&fullpath)?; let path_cstr = cstr(&path)?;
let mut stat: LibcStatFs;
let mut statfs: libc::statfs64 = unsafe { mem::zeroed() };
unsafe { unsafe {
stat = mem::zeroed(); let ret = libc::statfs64(path_cstr.as_ptr(), &mut statfs);
let ret = statfs(path_cstr.as_ptr(), &mut stat);
if ret < 0 { if ret < 0 {
return Err(io::Error::last_os_error()); return Err(io::Error::last_os_error());
} }
} }
let mut statfs = StatFs::new(); pp.w32(statfs.f_type as u32)?;
statfs.f_type = stat.f_type as u32; pp.w32(statfs.f_bsize as u32)?;
statfs.f_bsize = stat.f_bsize as u32; pp.w64(statfs.f_blocks)?;
statfs.f_blocks = stat.f_blocks; pp.w64(statfs.f_bfree)?;
statfs.f_bfree = stat.f_bfree; pp.w64(statfs.f_bavail)?;
statfs.f_bavail = stat.f_bavail; pp.w64(statfs.f_files)?;
statfs.f_files = stat.f_files; pp.w64(statfs.f_ffree)?;
statfs.f_ffree = stat.f_ffree; pp.w64(0)?;
statfs.f_namelen = stat.f_namelen as u32; pp.w32(statfs.f_namelen as u32)?;
statfs.fsid = stat.f_fsid.val[0] as u64 | ((stat.f_fsid.val[1] as u64) << 32); Ok(())
Ok(statfs)
} }
fn chown(&self, path: &Path, uid: u32, gid: u32) -> io::Result<()> { fn chown(&self, path: &Path, uid: u32, gid: u32) -> io::Result<()> {
let fullpath = self.fullpath(path)?; let path = self.canonicalize(path)?;
let path_cstr = cstr(&fullpath)?; let path_cstr = cstr(&path)?;
unsafe { unsafe {
if libc::chown(path_cstr.as_ptr(), uid, gid) < 0 { if libc::chown(path_cstr.as_ptr(), uid, gid) < 0 {
return Err(io::Error::last_os_error()); return Err(io::Error::last_os_error());
@ -316,21 +198,14 @@ impl FileSystemOps for FileSystem {
} }
} }
fn chmod(&self, path: &Path, mode: u32) -> io::Result<()> { fn set_mode(&self, path: &Path, mode: u32) -> io::Result<()> {
// XXX see std::os::unix::fs::PermissionsExt for a better way let meta = self.metadata(path)?;
let fullpath = self.fullpath(path)?; Ok(meta.permissions().set_mode(mode))
let path_cstr = cstr(&fullpath)?;
unsafe {
if libc::chmod(path_cstr.as_ptr(), mode) < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
} }
fn touch(&self, path: &Path, which: FsTouch, tv: (u64, u64)) -> io::Result<()> { fn touch(&self, path: &Path, which: FsTouch, tv: (u64, u64)) -> io::Result<()> {
let fullpath = self.fullpath(path)?; let path = self.canonicalize(path)?;
let path_cstr = cstr(&fullpath)?; let path_cstr = cstr(&path)?;
let tval = libc::timespec { let tval = libc::timespec {
tv_sec: tv.0 as i64, tv_sec: tv.0 as i64,
@ -352,8 +227,7 @@ impl FileSystemOps for FileSystem {
FsTouch::MtimeNow => [omit, now], FsTouch::MtimeNow => [omit, now],
}; };
unsafe { unsafe {
// XXX this could be wildly wrong but libc has wrong type if libc::utimensat(-1, path_cstr.as_ptr(), times.as_ptr(), 0) < 0 {
if libc::utimensat(-1, path_cstr.as_ptr(), &times.as_ptr() as *const _ as *const libc::timespec, 0) < 0 {
return Err(io::Error::last_os_error()); return Err(io::Error::last_os_error());
} }
} }
@ -361,8 +235,8 @@ impl FileSystemOps for FileSystem {
} }
fn truncate(&self, path: &Path, size: u64) -> io::Result<()> { fn truncate(&self, path: &Path, size: u64) -> io::Result<()> {
let fullpath = self.fullpath(path)?; let path = self.canonicalize(path)?;
let path_cstr = cstr(&fullpath)?; let path_cstr = cstr(&path)?;
unsafe { unsafe {
if libc::truncate64(path_cstr.as_ptr(), size as i64) < 0 { if libc::truncate64(path_cstr.as_ptr(), size as i64) < 0 {
return Err(io::Error::last_os_error()); return Err(io::Error::last_os_error());
@ -371,42 +245,57 @@ impl FileSystemOps for FileSystem {
Ok(()) Ok(())
} }
// XXX
fn readlink(&self, path: &Path) -> io::Result<OsString> { fn readlink(&self, path: &Path) -> io::Result<OsString> {
let fullpath = self.fullpath(path)?; let path = self.canonicalize(path)?;
fs::read_link(&fullpath).map(|pbuf| pbuf.into_os_string()) fs::read_link(&path).map(|pbuf| pbuf.into_os_string())
}
fn symlink(&self, target: &Path, linkpath: &Path) -> io::Result<()> {
let linkpath = self.canonicalize(linkpath)?;
unix::fs::symlink(target, linkpath)
}
fn link(&self, target: &Path, newpath: &Path) -> io::Result<()> {
let target = self.canonicalize(target)?;
let newpath= self.canonicalize(newpath)?;
fs::hard_link(target, newpath)
}
fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
let from = self.canonicalize(from)?;
let to = self.canonicalize(to)?;
fs::rename(from, to)
}
fn remove_file(&self, path: &Path) -> io::Result<()> {
let path = self.canonicalize(path)?;
fs::remove_file(path)
}
fn remove_dir(&self, path: &Path) -> io::Result<()> {
let path = self.canonicalize(path)?;
fs::remove_dir(path)
}
fn create_dir(&self, path: &Path, mode: u32) -> io::Result<()> {
let path = self.canonicalize(path)?;
fs::DirBuilder::new()
.recursive(false)
.mode(mode & 0o755)
.create(path)
}
fn readdir_populate(&self, path: &Path) -> io::Result<Directory> {
let mut directory = Directory::new();
let mut offset = 0;
for dent in fs::read_dir(path)? {
let dent = dent?;
let p9entry = P9DirEntry::from_direntry(dent, offset)?;
offset = p9entry.offset();
directory.push_entry(p9entry);
}
Ok(directory)
} }
} }
#[repr(C)]
pub struct LibcStatFs {
f_type: u64,
f_bsize: u64,
f_blocks: u64,
f_bfree: u64,
f_bavail: u64,
f_files: u64,
f_ffree: u64,
f_fsid: FsidT,
f_namelen: u64,
f_frsize: u64,
f_spare: [u64; 5],
}
#[repr(C)]
struct FsidT{
val: [libc::c_int; 2],
}
extern {
pub fn statfs(path: *const libc::c_char, buf: *mut LibcStatFs) -> libc::c_int;
}
fn os_err(errno: i32) -> io::Error {
io::Error::from_raw_os_error(errno)
}

View File

@ -1,33 +1,37 @@
use std::sync::{Arc,RwLock}; use std::sync::{Arc,RwLock};
use std::thread; use std::thread;
use std::path::{Path,PathBuf}; use std::path::{PathBuf, Path};
use crate::memory::{GuestRam, MemoryManager}; use crate::memory::{GuestRam, MemoryManager};
use crate::virtio::{self,VirtioBus,VirtioDeviceOps, VirtQueue}; use crate::virtio::{self,VirtioBus,VirtioDeviceOps, VirtQueue};
use crate::vm::Result; use crate::vm::Result;
use crate::devices::virtio_9p::server::Server;
mod fid; use crate::devices::virtio_9p::filesystem::{FileSystem, FileSystemOps};
mod pdu;
mod commands;
mod readdir;
mod filesystem;
use self::pdu::PduParser; use self::pdu::PduParser;
use self::commands::Commands;
mod pdu;
mod file;
mod directory;
mod filesystem;
mod server;
mod synthetic;
const VIRTIO_ID_9P: u16 = 9; const VIRTIO_ID_9P: u16 = 9;
const VIRTIO_9P_MOUNT_TAG: u64 = 0x1; const VIRTIO_9P_MOUNT_TAG: u64 = 0x1;
pub use synthetic::SyntheticFS;
pub struct VirtioP9 { pub struct VirtioP9<T: FileSystemOps> {
filesystem: T,
root_dir: PathBuf, root_dir: PathBuf,
init_path: PathBuf,
feature_bits: u64, feature_bits: u64,
debug: bool,
config: Vec<u8>, config: Vec<u8>,
} }
impl VirtioP9 { impl <T: FileSystemOps+'static> VirtioP9<T> {
fn create_config(tag_name: &str) -> Vec<u8> { fn create_config(tag_name: &str) -> Vec<u8> {
let tag_len = tag_name.len() as u16; let tag_len = tag_name.len() as u16;
let mut config = Vec::with_capacity(tag_name.len() + 3); let mut config = Vec::with_capacity(tag_name.len() + 3);
@ -38,17 +42,18 @@ impl VirtioP9 {
config config
} }
fn new(tag_name: &str, root_dir: &str, init_path: &Path) -> Arc<RwLock<VirtioP9>> { fn new(filesystem: T, tag_name: &str, root_dir: &str, debug: bool) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(VirtioP9 { Arc::new(RwLock::new(VirtioP9 {
filesystem,
root_dir: PathBuf::from(root_dir), root_dir: PathBuf::from(root_dir),
init_path: init_path.to_path_buf(),
feature_bits: 0, feature_bits: 0,
config: VirtioP9::create_config(tag_name), debug,
config: VirtioP9::<T>::create_config(tag_name),
})) }))
} }
pub fn create(vbus: &mut VirtioBus, tag_name: &str, root_dir: &str, init_path: &Path) -> Result<()> { pub fn create_with_filesystem(filesystem: T, vbus: &mut VirtioBus, tag_name: &str, root_dir: &str, debug: bool) -> Result<()> {
vbus.new_virtio_device(VIRTIO_ID_9P, VirtioP9::new(tag_name, root_dir, init_path)) vbus.new_virtio_device(VIRTIO_ID_9P, VirtioP9::new(filesystem, tag_name, root_dir, debug))
.set_num_queues(1) .set_num_queues(1)
.set_features(VIRTIO_9P_MOUNT_TAG) .set_features(VIRTIO_9P_MOUNT_TAG)
.set_config_size(tag_name.len() + 3) .set_config_size(tag_name.len() + 3)
@ -56,7 +61,15 @@ impl VirtioP9 {
} }
} }
impl VirtioDeviceOps for VirtioP9 { impl VirtioP9<FileSystem> {
pub fn create(vbus: &mut VirtioBus, tag_name: &str, root_dir: &str, read_only: bool, debug: bool) -> Result<()> {
let filesystem = FileSystem::new(PathBuf::from(root_dir), read_only);
Self::create_with_filesystem(filesystem, vbus, tag_name, root_dir, debug)
}
}
impl <T: FileSystemOps+'static> VirtioDeviceOps for VirtioP9<T> {
fn reset(&mut self) { fn reset(&mut self) {
println!("Reset called"); println!("Reset called");
} }
@ -70,22 +83,26 @@ impl VirtioDeviceOps for VirtioP9 {
virtio::read_config_buffer(&self.config, offset, size) virtio::read_config_buffer(&self.config, offset, size)
} }
fn start(&mut self, memory: &MemoryManager, mut queues: Vec<VirtQueue>) { fn start(&mut self, memory: &MemoryManager, mut queues: Vec<VirtQueue>) {
let vq = queues.pop().unwrap(); let vq = queues.pop().unwrap();
let root_dir = self.root_dir.clone(); let root_dir = self.root_dir.clone();
let init_path = self.init_path.clone(); let filesystem = self.filesystem.clone();
let ram = memory.guest_ram().clone(); let ram = memory.guest_ram().clone();
thread::spawn(|| run_device(ram, vq, root_dir, init_path)); let debug = self.debug;
thread::spawn(move || run_device(ram, vq, &root_dir, filesystem, debug));
} }
} }
fn run_device(memory: GuestRam, vq: VirtQueue, root_dir: PathBuf, init_path: PathBuf) { fn run_device<T: FileSystemOps>(memory: GuestRam, vq: VirtQueue, root_dir: &Path, filesystem: T, debug: bool) {
let mut commands = Commands::new(root_dir,init_path,memory.clone()); let mut server = Server::new(&root_dir, filesystem);
if debug {
server.enable_debug();
}
vq.on_each_chain(|mut chain| { vq.on_each_chain(|mut chain| {
let mut pp = PduParser::new(&mut chain, memory.clone()); let mut pp = PduParser::new(&mut chain, memory.clone());
commands.handle(&mut pp); server.handle(&mut pp);
}); });
} }

View File

@ -1,26 +1,16 @@
use std::fs::Metadata;
const P9_RLERROR: u8 = 7;
use byteorder::{LittleEndian,ReadBytesExt,WriteBytesExt};
use std::io::{self,Read,Write}; use std::io::{self,Read,Write};
use std::os::linux::fs::MetadataExt;
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::ffi::OsStr; use std::ffi::OsStr;
use libc;
use byteorder::{LittleEndian,ReadBytesExt,WriteBytesExt};
use crate::devices::virtio_9p::file::Qid;
use crate::memory::GuestRam; use crate::memory::GuestRam;
use crate::virtio::Chain; use crate::virtio::Chain;
use super::filesystem::StatFs;
use libc;
const P9_STATS_BASIC: u64 = 0x000007ff;
const P9_HEADER_LEN: usize = 7; const P9_HEADER_LEN: usize = 7;
const P9_RLERROR: u8 = 7;
const P9_QTFILE: u8 = 0x00;
const P9_QTLINK: u8 = 0x01;
const _P9_QTSYMLINK: u8 = 0x02;
const P9_QTDIR: u8 = 0x80;
pub struct PduParser<'a> { pub struct PduParser<'a> {
memory: GuestRam, memory: GuestRam,
@ -32,7 +22,7 @@ pub struct PduParser<'a> {
reply_start_addr: u64, reply_start_addr: u64,
} }
#[derive(Default)] #[derive(Default,Debug)]
pub struct P9Attr { pub struct P9Attr {
valid: u32, valid: u32,
mode: u32, mode: u32,
@ -46,15 +36,15 @@ pub struct P9Attr {
} }
impl P9Attr { impl P9Attr {
const MODE: u32 = (1 << 0); const MODE: u32 = (1 << 0); // 0x01
const UID: u32 = (1 << 1); const UID: u32 = (1 << 1); // 0x02
const GID: u32 = (1 << 2); const GID: u32 = (1 << 2); // 0x04
const SIZE: u32 = (1 << 3); const SIZE: u32 = (1 << 3); // 0x08
const ATIME: u32 = (1 << 4); const ATIME: u32 = (1 << 4); // 0x10
const MTIME: u32 = (1 << 5); const MTIME: u32 = (1 << 5); // 0x20
const CTIME: u32 = (1 << 6); const CTIME: u32 = (1 << 6); // 0x40
const ATIME_SET: u32 = (1 << 7); const ATIME_SET: u32 = (1 << 7); // 0x80
const MTIME_SET: u32 = (1 << 8); const MTIME_SET: u32 = (1 << 8); // 0x100
const MASK: u32 = 127; const MASK: u32 = 127;
const NO_UID: u32 = 0xFFFFFFFF; const NO_UID: u32 = 0xFFFFFFFF;
@ -75,6 +65,7 @@ impl P9Attr {
self.valid & P9Attr::MASK == P9Attr::CTIME|| self.valid & P9Attr::MASK == P9Attr::CTIME||
self.is_valid(P9Attr::UID|P9Attr::GID) self.is_valid(P9Attr::UID|P9Attr::GID)
} }
pub fn has_size(&self) -> bool { self.is_valid(P9Attr::SIZE) } pub fn has_size(&self) -> bool { self.is_valid(P9Attr::SIZE) }
pub fn mode(&self) -> u32 { pub fn mode(&self) -> u32 {
@ -115,7 +106,6 @@ impl P9Attr {
} }
} }
impl <'a> PduParser<'a> { impl <'a> PduParser<'a> {
pub fn new(chain: &'a mut Chain, memory: GuestRam) -> PduParser<'a> { pub fn new(chain: &'a mut Chain, memory: GuestRam) -> PduParser<'a> {
PduParser{ memory, chain, size: 0, cmd: 0, tag: 0, reply_start_addr: 0 } PduParser{ memory, chain, size: 0, cmd: 0, tag: 0, reply_start_addr: 0 }
@ -129,8 +119,9 @@ impl <'a> PduParser<'a> {
} }
pub fn read_done(&mut self) -> io::Result<()> { pub fn read_done(&mut self) -> io::Result<()> {
// XXX unwrap self.reply_start_addr = self.chain.current_write_address(8)
self.reply_start_addr = self.chain.current_write_address(8).unwrap(); .ok_or(io::Error::from_raw_os_error(libc::EIO))?;
// reserve header // reserve header
self.w32(0)?; self.w32(0)?;
self.w8(0)?; self.w8(0)?;
@ -138,20 +129,47 @@ impl <'a> PduParser<'a> {
Ok(()) Ok(())
} }
fn error_code(err: io::Error) -> u32 {
if let Some(errno) = err.raw_os_error() {
return errno as u32;
}
let errno = 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,
};
return errno as u32;
}
pub fn bail_err(&mut self, error: io::Error) -> io::Result<()> { pub fn bail_err(&mut self, error: io::Error) -> io::Result<()> {
let errno = Self::error_code(error);
self.write_err(errno)
}
pub fn write_err(&mut self, errno: u32) -> io::Result<()> {
if self.reply_start_addr == 0 { if self.reply_start_addr == 0 {
self.read_done()?; self.read_done()?;
} }
self.w32(errno)?;
let err = match error.raw_os_error() {
Some(errno) => errno as u32,
None => 0,
};
self._w32_at(0,P9_HEADER_LEN as u32 + 4); self._w32_at(0,P9_HEADER_LEN as u32 + 4);
self._w8_at(4, P9_RLERROR); self._w8_at(4, P9_RLERROR);
self._w16_at(5, self.tag); self._w16_at(5, self.tag);
self._w32_at(7, err);
self.chain.flush_chain(); self.chain.flush_chain();
Ok(()) Ok(())
} }
@ -182,7 +200,6 @@ impl <'a> PduParser<'a> {
self.memory.write_int::<u32>(self.reply_start_addr + offset as u64, val).unwrap(); self.memory.write_int::<u32>(self.reply_start_addr + offset as u64, val).unwrap();
} }
pub fn write_done(&mut self) -> io::Result<()> { pub fn write_done(&mut self) -> io::Result<()> {
self._w32_at(0, self.chain.get_wlen() as u32); self._w32_at(0, self.chain.get_wlen() as u32);
let cmd = self.cmd + 1; let cmd = self.cmd + 1;
@ -205,6 +222,16 @@ impl <'a> PduParser<'a> {
Ok(s) Ok(s)
} }
pub fn read_string_list(&mut self) -> io::Result<Vec<String>> {
let count = self.r16()?;
let mut strings = Vec::with_capacity(count as usize);
for _ in 0..count {
let s = self.read_string()?;
strings.push(s);
}
Ok(strings)
}
pub fn read_attr(&mut self) -> io::Result<P9Attr> { pub fn read_attr(&mut self) -> io::Result<P9Attr> {
let mut attr = P9Attr::new(); let mut attr = P9Attr::new();
attr.parse(self)?; attr.parse(self)?;
@ -212,8 +239,9 @@ impl <'a> PduParser<'a> {
} }
pub fn write_string(&mut self, str: &str) -> io::Result<()> { pub fn write_string(&mut self, str: &str) -> io::Result<()> {
self.w16(str.len() as u16)?; let bytes = str.as_bytes();
self.chain.write_all(str.as_bytes()) self.w16(bytes.len() as u16)?;
self.chain.write_all(bytes)
} }
pub fn write_os_string(&mut self, str: &OsStr) -> io::Result<()> { pub fn write_os_string(&mut self, str: &OsStr) -> io::Result<()> {
@ -221,70 +249,11 @@ impl <'a> PduParser<'a> {
self.chain.write_all(str.as_bytes()) self.chain.write_all(str.as_bytes())
} }
pub fn write_qid_list(&mut self, list: &[Qid]) -> io::Result<()> {
fn is_lnk(meta: &Metadata) -> bool { self.w16(list.len() as u16)?;
meta.st_mode() & libc::S_IFMT == libc::S_IFLNK for qid in list {
} qid.write(self)?;
fn meta_to_qtype(meta: &Metadata) -> u8 {
if meta.is_dir() {
P9_QTDIR
} else if PduParser::is_lnk(meta) {
P9_QTLINK
} else {
P9_QTFILE
} }
}
pub fn write_qid(&mut self, meta: &Metadata) -> io::Result<()> {
// type
self.w8(PduParser::meta_to_qtype(meta))?;
// version
self.w32(meta.st_mtime() as u32 ^ (meta.st_size() << 8) as u32)?;
// path
self.w64(meta.st_ino())
}
pub fn write_qid_path_only(&mut self, ino: u64) -> io::Result<()> {
self.w8(0)?;
self.w32(0)?;
self.w64(ino)
}
pub fn write_statl(&mut self, st: &Metadata) -> io::Result<()> {
self.w64(P9_STATS_BASIC)?;
self.write_qid(&st)?;
self.w32(st.st_mode())?;
self.w32(st.st_uid())?;
self.w32(st.st_gid())?;
self.w64(st.st_nlink())?;
self.w64(st.st_rdev())?;
self.w64(st.st_size())?;
self.w64(st.st_blksize())?;
self.w64(st.st_blocks())?;
self.w64(st.st_atime() as u64)?;
self.w64(st.st_atime_nsec() as u64)?;
self.w64(st.st_mtime() as u64)?;
self.w64(st.st_mtime_nsec() as u64)?;
self.w64(st.st_ctime() as u64)?;
self.w64(st.st_ctime_nsec() as u64)?;
self.w64(0)?;
self.w64(0)?;
self.w64(0)?;
self.w64(0)?;
Ok(())
}
pub fn write_statfs(&mut self, statfs: StatFs) -> io::Result<()> {
self.w32(statfs.f_type)?;
self.w32(statfs.f_bsize)?;
self.w64(statfs.f_blocks)?;
self.w64(statfs.f_bfree)?;
self.w64(statfs.f_bavail)?;
self.w64(statfs.f_files)?;
self.w64(statfs.f_ffree)?;
self.w64(statfs.fsid)?;
self.w32(statfs.f_namelen)?;
Ok(()) Ok(())
} }
@ -319,4 +288,3 @@ impl <'a> PduParser<'a> {
self.chain.write_u64::<LittleEndian>(val) self.chain.write_u64::<LittleEndian>(val)
} }
} }

View File

@ -1,131 +0,0 @@
use std::path::Path;
use std::mem;
use std::ptr;
use std::io;
use std::ffi::{OsStr,CStr,CString};
use std::os::unix::ffi::OsStrExt;
use libc;
struct Dir(*mut libc::DIR);
pub struct ReadDir {
dirp: Dir,
last_pos: i64,
}
pub struct DirEntry {
entry: libc::dirent64,
}
fn cstr(path: &Path) -> io::Result<CString> {
Ok(CString::new(path.as_os_str().as_bytes())?)
}
impl ReadDir {
pub fn open(path: &Path) -> io::Result<ReadDir> {
let p = cstr(path)?;
unsafe {
let ptr = libc::opendir(p.as_ptr());
if ptr.is_null() {
Err(io::Error::last_os_error())
} else {
Ok(ReadDir{ dirp: Dir(ptr), last_pos: 0 })
}
}
}
pub fn tell(&self) -> io::Result<i64> {
unsafe {
let loc = libc::telldir(self.dirp.0);
if loc == -1 {
return Err(io::Error::last_os_error());
}
Ok(loc)
}
}
pub fn seek(&self, loc: i64) {
unsafe { libc::seekdir(self.dirp.0, loc)}
}
pub fn fsync(&self) -> io::Result<()> {
unsafe {
if libc::fsync(libc::dirfd(self.dirp.0)) < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
fn save_current_pos(&mut self) {
match self.tell() {
Ok(loc) => self.last_pos = loc,
Err(_) => (),
};
}
pub fn restore_last_pos(&mut self) {
self.seek(self.last_pos)
}
}
impl Iterator for ReadDir {
type Item = io::Result<DirEntry>;
fn next(&mut self) -> Option<io::Result<DirEntry>> {
self.save_current_pos();
unsafe {
let mut ret = DirEntry {
entry: mem::zeroed(),
};
let mut entry_ptr = ptr::null_mut();
loop {
if libc::readdir64_r(self.dirp.0, &mut ret.entry, &mut entry_ptr) != 0 {
return Some(Err(io::Error::last_os_error()))
}
if entry_ptr.is_null() {
return None
}
if ret.name_bytes() != b"." && ret.name_bytes() != b".." {
return Some(Ok(ret))
}
}
}
}
}
impl Drop for Dir {
fn drop(&mut self) {
let _ = unsafe { libc::closedir(self.0) };
}
}
impl DirEntry {
#[allow(dead_code)]
pub fn file_name(&self) -> &OsStr {
OsStr::from_bytes(self.name_bytes())
}
pub fn offset(&self) -> u64 {
self.entry.d_off as u64
}
pub fn file_type(&self) -> u8 {
self.entry.d_type
}
pub fn ino(&self) -> u64 {
self.entry.d_ino as u64
}
pub fn name_bytes(&self) -> &[u8] {
unsafe {
CStr::from_ptr(self.entry.d_name.as_ptr()).to_bytes()
}
}
}

View File

@ -0,0 +1,684 @@
use std::path::{PathBuf, Path};
use std::{io, cmp};
use crate::devices::virtio_9p::{
pdu::{PduParser, P9Attr},
filesystem::{FileSystemOps, FsTouch},
file::{Fids, Fid, Qid},
};
const P9_TSTATFS: u8 = 8;
const P9_TLOPEN: u8 = 12;
const P9_TLCREATE: u8 = 14;
const P9_TSYMLINK: u8 = 16;
const P9_TMKNOD: u8 = 18;
const P9_TRENAME: u8 = 20;
const P9_TREADLINK: u8 = 22;
const P9_TGETATTR: u8 = 24;
const P9_TSETATTR: u8 = 26;
const P9_TXATTRWALK: u8 = 30;
const P9_TXATTRCREATE: u8 = 32;
const P9_TREADDIR: u8 = 40;
const P9_TFSYNC: u8 = 50;
const P9_TLOCK: u8 = 52;
const P9_TGETLOCK: u8 = 54;
const P9_TLINK: u8 = 70;
const P9_TMKDIR: u8 = 72;
const P9_TRENAMEAT: u8 = 74;
const P9_TUNLINKAT: u8 = 76;
const P9_TVERSION:u8 = 100;
const P9_TATTACH :u8 = 104;
const P9_TFLUSH: u8 = 108;
const P9_TWALK :u8 = 110;
const P9_TREAD: u8 = 116;
const P9_TWRITE: u8 = 118;
const P9_TCLUNK: u8 = 120;
const P9_REMOVE: u8 = 122;
pub struct Server<T: FileSystemOps> {
root: PathBuf,
debug: bool,
msize: u32,
fids: Fids<T>,
filesystem: T,
}
fn system_error<T>(errno: libc::c_int) -> io::Result<T> {
Err(io::Error::from_raw_os_error(errno))
}
impl <T: FileSystemOps> Server<T> {
pub fn new(root: &Path, filesystem: T) -> Self {
let root = root.to_owned();
let fids = Fids::new(root.clone(), filesystem.clone());
Server {
root,
debug: false,
msize: 0,
fids,
filesystem
}
}
pub fn enable_debug(&mut self) {
self.debug = true;
}
fn fid_mut(&mut self, id: u32) -> io::Result<&mut Fid<T>> {
self.fids.fid_mut(id)
}
fn read_fid(&self, pp: &mut PduParser) -> io::Result<&Fid<T>> {
self.fids.read_fid(pp)
}
/// Reads a directory fid and a string together and constructs a new path by
/// joining fid with name
fn read_new_path(&self, pp: &mut PduParser) -> io::Result<PathBuf> {
self.fids.read_new_path(pp)
}
pub fn handle(&mut self, pp: &mut PduParser) {
match pp.command() {
Ok(cmd) => {
if let Err(err) = self.dispatch(cmd, pp) {
if self.debug {
notify!("error handling command: {}", err);
}
let _ = pp.bail_err(err);
}
}
Err(e) => {
warn!("Error reading p9 command: {}", e);
}
}
}
fn dispatch(&mut self, cmd: u8, pp: &mut PduParser) -> io::Result<()> {
match cmd {
P9_TSTATFS => self.p9_statfs(pp)?,
P9_TLOPEN => self.p9_open(pp)?,
P9_TLCREATE => self.p9_create(pp)?,
P9_TSYMLINK => self.p9_symlink(pp)?,
P9_TMKNOD => self.p9_mknod(pp)?,
P9_TRENAME => self.p9_rename(pp)?,
P9_TREADLINK => self.p9_readlink(pp)?,
P9_TGETATTR => self.p9_getattr(pp)?,
P9_TSETATTR => self.p9_setattr(pp)?,
P9_TXATTRWALK => self.p9_unsupported(pp)?,
P9_TXATTRCREATE => self.p9_unsupported(pp)?,
P9_TREADDIR => self.p9_readdir(pp)?,
P9_TFSYNC => self.p9_fsync(pp)?,
P9_TLOCK => self.p9_unsupported(pp)?,
P9_TGETLOCK => self.p9_unsupported(pp)?,
P9_TUNLINKAT => self.p9_unlinkat(pp)?,
P9_TLINK => self.p9_link(pp)?,
P9_TMKDIR=> self.p9_mkdir(pp)?,
P9_TRENAMEAT => self.p9_renameat(pp)?,
P9_TVERSION => self.p9_version(pp)?,
P9_TATTACH => self.p9_attach(pp)?,
P9_TFLUSH => self.p9_flush(pp)?,
P9_TWALK => self.p9_walk(pp)?,
P9_TREAD => self.p9_read(pp)?,
P9_TWRITE => self.p9_write(pp)?,
P9_TCLUNK => self.p9_clunk(pp)?,
P9_REMOVE => self.p9_remove(pp)?,
n => warn!("unhandled 9p command: {}", n),
}
Ok(())
}
fn p9_statfs_args(&self, pp: &mut PduParser) -> io::Result<&Fid<T>> {
let fid = self.read_fid(pp)?;
pp.read_done()?;
Ok(fid)
}
fn p9_statfs(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid = self.p9_statfs_args(pp)?;
if self.debug {
notify!("p9_statfs({})", fid)
}
self.filesystem.write_statfs(fid.path(), pp)?;
pp.write_done()
}
fn p9_open_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, u32)> {
let fid = self.read_fid(pp)?;
let flags = pp.r32()?;
pp.read_done()?;
Ok((fid, flags))
}
fn p9_open(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (fid, flags) = self.p9_open_args(pp)?;
if self.debug {
notify!("p9_open({}, {:08x})", fid, flags)
}
let file = self.filesystem.open(fid.path(), flags)?;
let id = fid.id();
let fid = self.fid_mut(id)?;
fid.set_file(file);
fid.write_qid(pp)?;
// iounit
pp.w32(0)?;
pp.write_done()
}
fn p9_create_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, PathBuf, u32, u32)> {
let dfid = self.read_fid(pp)?;
let name = pp.read_string()?;
let path = dfid.join_name(&self.root, &name)?;
let flags = pp.r32()?;
let mode = pp.r32()?;
let _gid = pp.r32()?;
pp.read_done()?;
Ok((dfid, path, flags, mode))
}
fn p9_create(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (dfid, path, flags, mode) = self.p9_create_args(pp)?;
if self.debug {
notify!("p9_create({:?}, flags={:08x}, mode={:04o})",
path, flags, mode)
}
let file = self.filesystem.create(&path, flags, mode)?;
let id = dfid.id();
let dfid = self.fid_mut(id)?;
dfid.set_path(path)?;
dfid.set_file(file);
dfid.write_qid(pp)?;
// iounit
pp.w32(0)?;
pp.write_done()
}
fn p9_symlink_args(&self, pp: &mut PduParser) -> io::Result<(PathBuf, String)> {
let newpath = self.read_new_path(pp)?;
let target = pp.read_string()?;
let _gid = pp.r32()?;
pp.read_done()?;
Ok((newpath, target))
}
fn p9_symlink(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (newpath, target) = self.p9_symlink_args(pp)?;
if self.debug {
notify!("p9_symlink({:?}, {})", newpath, target)
}
self.filesystem.symlink(&Path::new(&target), &newpath)?;
self.filesystem.write_stat(&newpath, pp)?;
pp.write_done()
}
fn p9_mknod_args(&self, pp: &mut PduParser) -> io::Result<(PathBuf, u32, u32, u32)> {
let path = self.read_new_path(pp)?;
let mode = pp.r32()?;
let major = pp.r32()?;
let minor = pp.r32()?;
let _gid = pp.r32()?;
pp.read_done()?;
Ok((path, mode, major, minor))
}
fn p9_mknod(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (path, mode, major, minor) = self.p9_mknod_args(pp)?;
if self.debug {
notify!("p9_mknod({:?}, {:04o}, {}:{})", path, mode, major, minor)
}
system_error(libc::EACCES)
}
fn p9_rename_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, PathBuf)> {
let oldfid = self.read_fid(pp)?;
let newpath = self.read_new_path(pp)?;
pp.read_done()?;
Ok((oldfid, newpath))
}
fn p9_rename(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (oldfid, newpath) = self.p9_rename_args(pp)?;
if self.debug {
format!("p9_rename({}, {:?})", oldfid, newpath);
}
self.filesystem.rename(oldfid.path(), &newpath)?;
let id = oldfid.id();
let oldfid = self.fid_mut(id)?;
oldfid.set_path(newpath)?;
pp.write_done()
}
fn p9_readlink_args(&self, pp: &mut PduParser) -> io::Result<&Fid<T>> {
let fid = self.read_fid(pp)?;
pp.read_done()?;
Ok(fid)
}
fn p9_readlink(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid = self.p9_readlink_args(pp)?;
if self.debug {
notify!("p9_readlink({})", fid);
}
let s = self.filesystem.readlink(fid.path())?;
pp.write_os_string(&s)?;
pp.write_done()
}
fn p9_getattr_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, u64)> {
let fid = self.read_fid(pp)?;
let mask = pp.r64()?;
pp.read_done()?;
Ok((fid, mask))
}
fn p9_getattr(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (fid,mask) = self.p9_getattr_args(pp)?;
if self.debug {
notify!("p9_getattr({}, {})", fid, mask);
}
// XXX mask?
fid.write_stat(pp)?;
if let Err(err) = fid.write_stat(pp) {
notify!("error from write_stat: {}", err);
return Err(err);
}
pp.write_done()
}
fn p9_setattr_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, P9Attr)> {
let fid = self.read_fid(pp)?;
let attr = pp.read_attr()?;
pp.read_done()?;
Ok((fid, attr))
}
fn p9_setattr(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (fid, attr) = self.p9_setattr_args(pp)?;
if self.debug {
notify!("p9_setattr({}, {:?})", fid, attr);
}
if attr.has_mode() {
self.filesystem.set_mode(fid.path(), attr.mode())?;
}
if attr.has_atime() {
if attr.has_atime_set() {
self.filesystem.touch(fid.path(), FsTouch::Atime, attr.atime())?;
} else {
self.filesystem.touch(fid.path(), FsTouch::AtimeNow, (0,0))?;
}
}
if attr.has_mtime() {
if attr.has_mtime_set() {
self.filesystem.touch(fid.path(), FsTouch::Mtime, attr.mtime())?;
} else {
self.filesystem.touch(fid.path(), FsTouch::MtimeNow, (0,0))?;
}
}
if attr.has_chown() {
let (uid, gid) = attr.chown_ids();
self.filesystem.chown(fid.path(), uid, gid)?;
}
if attr.has_size() {
self.filesystem.truncate(fid.path(), attr.size())?;
}
pp.write_done()
}
fn p9_readdir_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, u64, u32)> {
let fid = self.read_fid(pp)?;
let offset = pp.r64()?;
let count = pp.r32()?;
pp.read_done()?;
Ok((fid, offset, count))
}
fn p9_readdir(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (fid, offset, count) = self.p9_readdir_args(pp)?;
if self.debug {
notify!("p9_readdir({}, offset={}, count={})", fid, offset, count);
}
if offset == 0 {
fid.load_directory()?;
}
let mut dref = fid.directory();
let directory = match dref.as_mut() {
Some(directory) => directory,
None => return system_error(libc::EBADF),
};
let size= cmp::min(self.msize - 4, count) as usize;
directory.write_entries(pp, offset, size)?;
pp.write_done()
}
fn p9_fsync_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, u32)> {
let fid = self.read_fid(pp)?;
let datasync = pp.r32()?;
pp.read_done()?;
Ok((fid, datasync))
}
fn p9_fsync(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (fid, datasync) = self.p9_fsync_args(pp)?;
if self.debug {
notify!("p9_fsync({}, {})", fid, datasync);
}
let file = fid.file()?;
if datasync == 0 {
file.sync_all()?;
} else {
file.sync_data()?;
}
pp.write_done()
}
fn p9_unlinkat_args(&self, pp: &mut PduParser) -> io::Result<(PathBuf, u32)> {
let path = self.read_new_path(pp)?;
let flags = pp.r32()?;
pp.read_done()?;
Ok((path, flags))
}
fn p9_unlinkat(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (path, flags) = self.p9_unlinkat_args(pp)?;
if self.debug {
notify!("p9_unlinkat({:?}, {:08x})", path, flags);
}
if path.is_dir() && (flags & libc::AT_REMOVEDIR as u32) == 0 {
return system_error(libc::EISDIR);
} else if path.is_dir() {
self.filesystem.remove_dir(&path)?;
} else {
self.filesystem.remove_file(&path)?;
}
pp.write_done()
}
fn p9_link_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, PathBuf)> {
let dfid = self.read_fid(pp)?;
let fid = self.read_fid(pp)?;
let name = pp.read_string()?;
pp.read_done()?;
let newpath = dfid.join_name(&self.root, &name)?;
Ok((fid, newpath))
}
fn p9_link(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (fid, newpath) = self.p9_link_args(pp)?;
self.filesystem.link(fid.path(), &newpath)?;
pp.write_done()
}
fn p9_mkdir_args(&self, pp: &mut PduParser) -> io::Result<(PathBuf, u32)> {
let newpath = self.read_new_path(pp)?;
let mode = pp.r32()?;
let _gid = pp.r32()?;
pp.read_done()?;
Ok((newpath, mode))
}
fn p9_mkdir(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (newpath, mode) = self.p9_mkdir_args(pp)?;
self.filesystem.create_dir(&newpath, mode)?;
let qid = self.filesystem.read_qid(&newpath)?;
qid.write(pp)?;
pp.write_done()
}
fn p9_renameat_args(&self, pp: &mut PduParser) -> io::Result<(PathBuf, PathBuf)> {
let oldpath = self.read_new_path(pp)?;
let newpath = self.read_new_path(pp)?;
pp.read_done()?;
Ok((oldpath, newpath))
}
fn p9_renameat(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (oldpath, newpath) = self.p9_renameat_args(pp)?;
self.filesystem.rename(&oldpath, &newpath)?;
pp.write_done()?;
Ok(())
}
fn p9_version_args(&self, pp: &mut PduParser) -> io::Result<(u32, String)> {
let msize = pp.r32()?;
let version = pp.read_string()?;
pp.read_done()?;
Ok((msize, version))
}
fn p9_version(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (msize, version) = self.p9_version_args(pp)?;
if self.debug {
notify!("p9_version({}, {})", version, msize);
}
self.msize = msize;
self.fids.clear();
pp.w32(msize)?;
if version.as_str() == "9P2000.L" {
pp.write_string(&version)?;
} else {
pp.write_string("unknown")?;
}
pp.write_done()
}
fn p9_attach_args(&self, pp: &mut PduParser) -> io::Result<u32> {
let id = pp.r32()?;
let _afid = pp.r32()?;
let _uname = pp.read_string()?;
let _aname = pp.read_string()?;
let _uid = pp.r32()?;
pp.read_done()?;
Ok(id)
}
fn p9_attach(&mut self, pp: &mut PduParser) -> io::Result<()> {
let id = self.p9_attach_args(pp)?;
if self.fids.exists(id) {
return system_error(libc::EBADF);
}
let fid = self.fids.create(id, &self.root)?;
fid.write_qid(pp)?;
self.fids.add(fid);
pp.write_done()
}
fn p9_flush(&mut self, pp: &mut PduParser) -> io::Result<()> {
pp.read_done()?;
pp.write_done()
}
fn p9_walk_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, u32, Vec<String>)> {
let fid = self.read_fid(pp)?;
let newfid_id = pp.r32()?;
let names = pp.read_string_list()?;
pp.read_done()?;
Ok((fid, newfid_id, names))
}
fn p9_walk(&mut self, pp: &mut PduParser) -> io::Result<()> {
fn walk_extend<T: FileSystemOps>(fids: &Fids<T>, qid: Qid, path: &Path, name: &str) -> io::Result<(PathBuf, Qid)> {
let path = fids.path_join_name(qid, path, name)?;
let qid = fids.read_qid(&path)?;
Ok((path, qid))
}
let (fid, newfid_id, names) = self.p9_walk_args(pp)?;
if fid.id() != newfid_id && self.fids.exists(newfid_id) {
return system_error(libc::EBADF);
}
if self.debug {
notify!("p9_walk({}, newfid={}, names={:?})", fid, newfid_id, names);
}
let mut path = fid.path().to_path_buf();
let mut current_qid = fid.qid();
let mut qid_list = Vec::new();
for name in names {
path = match walk_extend(&self.fids, current_qid, &path, &name) {
Ok((path, qid)) => {
qid_list.push(qid);
current_qid = qid;
path
},
Err(e) => {
if qid_list.is_empty() {
return Err(e);
}
pp.write_qid_list(&qid_list)?;
pp.write_done()?;
return Ok(())
}
};
}
let new_fid = self.fids.create(newfid_id, path)?;
self.fids.add(new_fid);
pp.write_qid_list(&qid_list)?;
pp.write_done()
}
fn p9_read_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, u64, u32)> {
let fid = self.read_fid(pp)?;
let offset = pp.r64()?;
let count = pp.r32()?;
pp.read_done()?;
Ok((fid, offset, count))
}
fn p9_read(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (fid, offset, count) = self.p9_read_args(pp)?;
if self.debug {
notify!("p9_read({}, offset={}, count={})", fid, offset, count);
}
let file = fid.file()?;
// space for size field
pp.w32(0)?;
let mut nread = 0;
while nread < count {
let current = pp.chain.current_write_slice();
if current.len() == 0 {
break;
}
let rlen = cmp::min(current.len(), count as usize);
let n = file.read_at(&mut current[..rlen], offset + nread as u64)?;
if n == 0 {
break;
}
pp.chain.inc_offset(n, true);
nread += n as u32;
}
pp.w32_at(0, nread as u32);
pp.write_done()
}
fn p9_write_args(&self, pp: &mut PduParser) -> io::Result<(&Fid<T>, u64, u32)> {
let fid = self.read_fid(pp)?;
let offset = pp.r64()?;
let count = pp.r32()?;
Ok((fid, offset, count))
}
fn p9_write(&mut self, pp: &mut PduParser) -> io::Result<()> {
let (fid, offset, count) = self.p9_write_args(pp)?;
if self.debug {
notify!("p9_write({}, offset={}, count={})", fid, offset, count);
}
let file = fid.file()?;
let mut nread = 0;
while nread < count {
let n = file.write_at(pp.chain.current_read_slice(), offset + nread as u64)?;
if n == 0 {
break;
}
pp.chain.inc_offset(n, false);
nread += n as u32;
}
pp.read_done()?;
pp.w32(nread)?;
pp.write_done()
}
fn remove_fid(&mut self, pp: &mut PduParser) -> io::Result<Fid<T>> {
let id = pp.r32()?;
pp.read_done()?;
self.fids.remove(id)
}
fn p9_clunk(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid = self.remove_fid(pp)?;
if self.debug {
notify!("p9_clunk({})", fid);
}
pp.write_done()
}
fn p9_remove(&mut self, pp: &mut PduParser) -> io::Result<()> {
let fid = self.remove_fid(pp)?;
if self.debug {
notify!("p9_remove({})", fid);
}
if fid.is_dir() {
self.filesystem.remove_dir(fid.path())?;
} else {
self.filesystem.remove_file(fid.path())?;
}
pp.write_done()
}
fn p9_unsupported(&self, pp: &mut PduParser) -> io::Result<()> {
pp.read_done()?;
system_error(libc::EOPNOTSUPP)
}
}

View File

@ -0,0 +1,517 @@
use std::collections::{HashSet, BTreeMap};
use std::collections::btree_map::Entry;
use std::ffi::{OsString, OsStr};
use std::io;
use std::os::linux::fs::MetadataExt;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf, Component};
use std::process::{Command, Stdio};
use std::time::{UNIX_EPOCH, SystemTime};
use crate::devices::virtio_9p::{
directory::{Directory, P9DirEntry},
file::{P9File, Qid, P9_QTDIR, P9_QTFILE},
filesystem::{FileSystemOps, FsTouch, FileSystem},
pdu::PduParser,
};
use crate::devices::virtio_9p::file::Buffer;
#[derive(Clone)]
struct NodeData {
name: OsString,
qid: Qid,
size: u64,
mode: u32,
inode: u32,
}
impl NodeData {
fn name_str(&self) -> &str {
self.name.to_str()
.expect("SyntheticFS: unable to convert name to &str")
}
fn dtype(&self) -> u8 {
if self.qid.is_dir() {
libc::DT_DIR
} else {
libc::DT_REG
}
}
}
#[derive(Clone)]
enum Node {
File(PathBuf, NodeData),
MemoryFile(Buffer<&'static [u8]>, NodeData),
Dir(BTreeMap<OsString, Node>, NodeData),
}
impl Node {
fn new_dir(name: &OsStr, mode: u32, inode: u32) -> Node {
let mode = mode | libc::S_IFDIR;
let data = NodeData::new(name, P9_QTDIR, 0, mode, inode);
let entries= BTreeMap::new();
Node::Dir(entries, data)
}
fn new_file<S: Into<OsString>>(name: S, mode: u32, inode: u32, size: u64, local: &Path) -> Node {
let mode = mode | libc::S_IFREG;
let data = NodeData::new(name, P9_QTFILE, size, mode, inode);
let local = local.to_path_buf();
Node::File(local, data)
}
fn new_memory_file<S: Into<OsString>>(name: S, mode: u32, inode: u32, size: u64, bytes: &'static [u8]) -> Node {
let mode = mode | libc::S_IFREG;
let data = NodeData::new(name, P9_QTFILE, size, mode, inode);
let buffer = Buffer::new(bytes);
Node::MemoryFile(buffer, data)
}
fn node_data(&self) -> &NodeData {
match self {
Node::Dir(_, data) => data,
Node::File(_, data) => data,
Node::MemoryFile(_, data) => data,
}
}
fn qid(&self) -> Qid {
self.node_data().qid
}
fn write_stat(&self, pp: &mut PduParser) -> io::Result<()> {
self.node_data().write_stat(pp)
}
fn create_directory_entry(&self, offset: u64) -> P9DirEntry {
let data = self.node_data();
P9DirEntry::new(data.qid, offset, data.dtype(), data.name_str())
}
fn entries(&self) -> Option<&BTreeMap<OsString, Node>> {
match self {
Node::Dir(entries, ..) => Some(entries),
_ => None,
}
}
fn entries_mut(&mut self) -> Option<&mut BTreeMap<OsString, Node>> {
match self {
Node::Dir(entries, ..) => Some(entries),
_ => None,
}
}
fn descend(&self, name: &OsStr) -> io::Result<&Node> {
self.entries()
.ok_or(rawerr(libc::ENOTDIR))
.and_then(|entries|
entries.get(name)
.ok_or(rawerr(libc::ENOENT)))
}
fn descend_mut(&mut self, name: &OsStr) -> io::Result<&mut Node> {
self.entries_mut()
.ok_or(rawerr(libc::ENOTDIR))
.and_then(|entries|
entries.get_mut(name)
.ok_or(rawerr(libc::ENOENT)))
}
fn mkdir(&mut self, names: &[&OsStr], mode: u32, inodes: &mut Inodes) -> io::Result<()> {
if !names.is_empty() {
let entries = self.entries_mut().ok_or(rawerr(libc::ENOTDIR))?;
match entries.entry(names[0].to_os_string()) {
Entry::Occupied(mut entry) => {
entry.get_mut().mkdir(&names[1..], mode, inodes)?;
}
Entry::Vacant(entry) => {
let inode = inodes.next_inode();
let mut node = Node::new_dir(names[0], mode, inode);
node.mkdir(&names[1..], mode, inodes)?;
entry.insert(node);
}
}
}
Ok(())
}
fn populate_directory(&self) -> io::Result<Directory> {
match self {
Node::Dir(nodes, ..) => {
let mut offset = 0;
let mut directory = Directory::new();
for node in nodes.values() {
let entry = node.create_directory_entry(offset);
offset = entry.offset();
directory.push_entry(entry);
}
return Ok(directory)
},
_ => return Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
}
}
}
impl NodeData {
fn new<S: Into<OsString>>(name: S, qtype: u8, size: u64, mode: u32, inode: u32) -> Self {
NodeData {
name: name.into(),
qid: Self::create_qid(qtype, inode),
size, mode, inode,
}
}
fn create_qid(qtype: u8, inode: u32) -> Qid {
let qid_version = Self::generate_qid_version(inode);
let qid_path = inode as u64;
Qid::new(qtype, qid_version, qid_path)
}
fn generate_qid_version(inode: u32) -> u32 {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(v) => v.as_millis() as u32,
_ => inode,
}
}
fn write_stat(&self, pp: &mut PduParser) -> io::Result<()> {
const P9_STATS_BASIC: u64 = 0x000007ff;
pp.w64(P9_STATS_BASIC)?;
self.qid.write(pp)?;
pp.w32(self.mode)?;
pp.w32(0)?; // uid
pp.w32(0)?; // gid
pp.w64(1)?; // nlink
pp.w64(0)?; // rdev
pp.w64(self.size)?; // size
pp.w64(0)?; // blksize
pp.w64(0)?; // blocks
pp.w64(0)?; // atime
pp.w64(0)?; // atime nsec
pp.w64(0)?; // mtime
pp.w64(0)?; // mtime nsec
pp.w64(0)?; // ctime
pp.w64(0)?; // ctime nsec
pp.w64(0)?; // btime
pp.w64(0)?; // btime nsec
pp.w64(0)?;
pp.w64(0)?;
Ok(())
}
}
const BASE_INODE: u32 = 1000;
#[derive(Clone)]
struct Inodes {
inodes: HashSet<u32>,
current_inode: u32,
}
impl Inodes {
fn new() -> Self {
Inodes {
inodes: HashSet::new(),
current_inode: BASE_INODE,
}
}
fn next_inode(&mut self) -> u32 {
let mut inode = self.current_inode;
while self.inodes.contains(&inode) {
inode += 1;
}
self.inodes.insert(inode);
self.current_inode = inode + 1;
inode
}
fn file_inode(&mut self, path: &Path) -> u32 {
let meta = match path.symlink_metadata() {
Ok(meta) => meta,
Err(_) => return self.next_inode(),
};
let inode = meta.st_ino() as u32;
if self.inodes.contains(&inode) {
return self.next_inode();
}
self.inodes.insert(inode);
inode
}
}
#[derive(Clone)]
pub struct SyntheticFS {
paths_added: HashSet<PathBuf>,
root: Node,
inodes: Inodes,
euid_root: bool,
}
impl SyntheticFS {
pub fn new() -> Self {
let mut inodes = Inodes::new();
let root = Node::new_dir("/".as_ref(), 0o755, inodes.next_inode());
let euid_root = FileSystem::is_euid_root();
SyntheticFS {
root, inodes, euid_root, paths_added: HashSet::new(),
}
}
fn node_count(&self) -> usize {
self.inodes.inodes.len()
}
pub fn mkdirs<P: AsRef<Path>>(&mut self, paths: &[P]) {
for p in paths {
self.mkdir(p, 0o755);
}
}
pub fn mkdir<P: AsRef<Path>>(&mut self, path: P, mode: u32) {
let path = path.as_ref();
let names = match Self::path_names(path) {
Ok(names) => names,
Err(_) => {
warn!("cannot add directory because path is invalid: {}", path.display());
return;
}
};
if let Err(e) = self.root.mkdir(&names, mode, &mut self.inodes) {
warn!("failed to create directory {}: {}", path.display(), e);
}
}
#[allow(dead_code)]
pub fn add_memory_file<S: Into<OsString>, P: AsRef<Path>>(&mut self, dirpath: P, filename: S, mode: u32, bytes: &'static [u8]) -> io::Result<()> {
let dirpath = dirpath.as_ref();
let filename = filename.into();
self.mkdir(dirpath, 0o755);
let inode = self.inodes.next_inode();
let node = self.lookup_mut(dirpath)?;
let entries = node.entries_mut().ok_or(rawerr(libc::ENOTDIR))?;
entries.insert(OsString::from(filename.clone()), Node::new_memory_file(filename, mode, inode, bytes.len() as u64, bytes));
Ok(())
}
pub fn add_file<S: Into<OsString>, P: AsRef<Path>, Q: AsRef<Path>>(&mut self, dirpath: P, filename: S, mode: u32, realpath: Q) {
let dirpath = dirpath.as_ref();
let realpath = realpath.as_ref();
let filename = filename.into();
if let Err(e) = self._add_file(dirpath, &filename, mode, realpath) {
warn!("error adding file {:?} to {}: {}", filename, dirpath.display(), e);
}
}
pub fn _add_file<S: Into<OsString>>(&mut self, dirpath: &Path, filename: S, mode: u32, realpath: &Path) -> io::Result<()> {
let filename = filename.into();
self.mkdir(dirpath, 0o755);
let inode = self.inodes.file_inode(realpath);
let node = self.lookup_mut(dirpath)?;
let entries = node.entries_mut().ok_or(rawerr(libc::ENOTDIR))?;
let meta = realpath.metadata()?;
entries.insert(OsString::from(filename.clone()), Node::new_file(filename, mode, inode, meta.len(), realpath));
Ok(())
}
fn parse_ldd_line(line: &str) -> Option<PathBuf> {
for s in line.split_whitespace().take(3) {
if s.starts_with('/') {
let path = Path::new(s);
if path.exists() {
return Some(path.to_path_buf())
}
}
}
None
}
fn add_path(&mut self, path: &Path) -> io::Result<()> {
if let (Some(parent), Some(filename)) = (path.parent(), path.file_name()) {
let meta = path.metadata()?;
let mode = meta.permissions().mode();
self.add_file(parent, filename, mode, path);
}
Ok(())
}
fn ldd_command() -> io::Result<Command> {
let ldd = Path::new("/usr/bin/ldd");
let ldso = Path::new("/usr/lib/ld-linux-x86-64.so.2");
if ldd.exists() {
Ok(Command::new(ldd))
} else if ldso.exists() {
let mut cmd = Command::new(ldso);
cmd.arg("--list");
Ok(cmd)
} else {
Err(io::Error::new(io::ErrorKind::Other, "No ldd binary found"))
}
}
pub fn add_library_dependencies<P: AsRef<Path>>(&mut self, execpath: P) -> io::Result<()> {
let execpath = execpath.as_ref();
let mut cmd = Self::ldd_command()?;
let out = cmd
.arg(execpath.as_os_str())
.stdout(Stdio::piped())
.output()?;
let s = String::from_utf8(out.stdout).expect("");
for line in s.lines() {
if let Some(path) = Self::parse_ldd_line(line) {
if !self.paths_added.contains(&path) {
self.add_path(&path)?;
self.paths_added.insert(path);
}
}
}
Ok(())
}
pub fn add_executable<P: AsRef<Path>, Q: AsRef<Path>>(&mut self, dirpath: P, filename: &str, realpath: Q) -> io::Result<()> {
let realpath = realpath.as_ref();
self.add_library_dependencies(realpath)?;
self.add_file(dirpath, filename, 0o755, realpath);
Ok(())
}
fn lookup(&self, path: &Path) -> io::Result<&Node> {
let mut current = &self.root;
for name in Self::path_names(path)? {
current = current.descend(name)?;
}
Ok(current)
}
fn lookup_mut(&mut self, path: &Path) -> io::Result<&mut Node> {
let mut current = &mut self.root;
for name in Self::path_names(path)? {
current = current.descend_mut(name)?;
}
Ok(current)
}
fn path_names(path: &Path) -> io::Result<Vec<&OsStr>> {
if !path.is_absolute() {
return syserr(libc::EINVAL)
}
Ok(path.components().flat_map(|c| match c {
Component::Normal(name) => Some(name),
_ => None,
}).collect())
}
}
impl FileSystemOps for SyntheticFS {
fn read_qid(&self, path: &Path) -> io::Result<Qid> {
let node = self.lookup(path)?;
Ok(node.qid())
}
fn write_stat(&self, path: &Path, pp: &mut PduParser) -> io::Result<()> {
let node = self.lookup(path)?;
node.write_stat(pp)
}
fn open(&self, path: &Path, flags: u32) -> io::Result<P9File> {
match self.lookup(path)? {
Node::File(local, _) => {
// XXX filter flags
let file = FileSystem::open_with_flags(local, flags, self.euid_root)?;
Ok(P9File::from_file(file))
},
Node::Dir(..) => {
Ok(P9File::new_not_a_file())
},
Node::MemoryFile(buffer,..) => {
Ok(P9File::from_buffer(buffer.clone()))
}
}
}
fn create(&self, _path: &Path, _flags: u32, _mode: u32) -> io::Result<P9File> {
syserr(libc::EROFS)
}
fn write_statfs(&self, _path: &Path, pp: &mut PduParser) -> io::Result<()> {
//notify!("write_statfs({})", path.display());
let f_files = self.node_count() as u64;
pp.w32(0xABCD)?; // f_type
pp.w32(512)?; // f_bsize
pp.w64(0)?; // f_blocks
pp.w64(0)?; // f_bfree
pp.w64(0)?; // f_bavail
pp.w64(f_files)?; // f_files
pp.w64(0)?; // f_ffree
pp.w64(0)?; //
pp.w32(4096)?; // f_namelen
Ok(())
}
fn chown(&self, _path: &Path, _uid: u32, _gid: u32) -> io::Result<()> {
syserr(libc::EROFS)
}
fn set_mode(&self, _path: &Path, _mode: u32) -> io::Result<()> {
syserr(libc::EROFS)
}
fn touch(&self, _path: &Path, _which: FsTouch, _tv: (u64, u64)) -> io::Result<()> {
syserr(libc::EROFS)
}
fn truncate(&self, _path: &Path, _size: u64) -> io::Result<()> {
syserr(libc::EROFS)
}
fn readlink(&self, _path: &Path) -> io::Result<OsString> {
syserr(libc::EROFS)
}
fn symlink(&self, _target: &Path, _linkpath: &Path) -> io::Result<()> {
syserr(libc::EROFS)
}
fn link(&self, _target: &Path, _newpath: &Path) -> io::Result<()> {
syserr(libc::EROFS)
}
fn rename(&self, _from: &Path, _to: &Path) -> io::Result<()> {
syserr(libc::EROFS)
}
fn remove_file(&self, _path: &Path) -> io::Result<()> {
syserr(libc::EROFS)
}
fn remove_dir(&self, _path: &Path) -> io::Result<()> {
syserr(libc::EROFS)
}
fn create_dir(&self, _path: &Path, _mode: u32) -> io::Result<()> {
syserr(libc::EROFS)
}
fn readdir_populate(&self, path: &Path) -> io::Result<Directory> {
let node = self.lookup(path)?;
node.populate_directory()
}
}
fn rawerr(errno: i32) -> io::Error {
io::Error::from_raw_os_error(errno)
}
fn syserr<T>(errno: i32) -> io::Result<T> {
Err(rawerr(errno))
}