From 98a1e9361ebadcf9885f65c5a74d1a0e38e3e696 Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Fri, 20 Sep 2019 16:01:15 -0400 Subject: [PATCH] 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. --- rust/src/devices/mod.rs | 1 + rust/src/devices/virtio_9p/commands.rs | 404 ------------- rust/src/devices/virtio_9p/directory.rs | 95 ++++ rust/src/devices/virtio_9p/fid.rs | 230 -------- rust/src/devices/virtio_9p/file.rs | 366 ++++++++++++ rust/src/devices/virtio_9p/filesystem.rs | 507 +++++++---------- rust/src/devices/virtio_9p/mod.rs | 65 ++- rust/src/devices/virtio_9p/pdu.rs | 174 +++--- rust/src/devices/virtio_9p/readdir.rs | 131 ----- rust/src/devices/virtio_9p/server.rs | 684 +++++++++++++++++++++++ rust/src/devices/virtio_9p/synthetic.rs | 517 +++++++++++++++++ 11 files changed, 1973 insertions(+), 1201 deletions(-) delete mode 100644 rust/src/devices/virtio_9p/commands.rs create mode 100644 rust/src/devices/virtio_9p/directory.rs delete mode 100644 rust/src/devices/virtio_9p/fid.rs create mode 100644 rust/src/devices/virtio_9p/file.rs delete mode 100644 rust/src/devices/virtio_9p/readdir.rs create mode 100644 rust/src/devices/virtio_9p/server.rs create mode 100644 rust/src/devices/virtio_9p/synthetic.rs diff --git a/rust/src/devices/mod.rs b/rust/src/devices/mod.rs index 62979db..8f78c1f 100644 --- a/rust/src/devices/mod.rs +++ b/rust/src/devices/mod.rs @@ -8,6 +8,7 @@ mod virtio_block; pub use self::virtio_serial::VirtioSerial; pub use self::virtio_9p::VirtioP9; +pub use self::virtio_9p::SyntheticFS; pub use self::virtio_rng::VirtioRandom; pub use self::virtio_wl::VirtioWayland; pub use self::virtio_block::VirtioBlock; diff --git a/rust/src/devices/virtio_9p/commands.rs b/rust/src/devices/virtio_9p/commands.rs deleted file mode 100644 index 3036fb8..0000000 --- a/rust/src/devices/virtio_9p/commands.rs +++ /dev/null @@ -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() - } -} diff --git a/rust/src/devices/virtio_9p/directory.rs b/rust/src/devices/virtio_9p/directory.rs new file mode 100644 index 0000000..3c7ed2f --- /dev/null +++ b/rust/src/devices/virtio_9p/directory.rs @@ -0,0 +1,95 @@ +use std::{fs, io}; + +use crate::devices::virtio_9p::{ + pdu::PduParser, file::Qid, +}; + +pub struct Directory { + entries: Vec, +} + +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 { + 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(()) + } +} diff --git a/rust/src/devices/virtio_9p/fid.rs b/rust/src/devices/virtio_9p/fid.rs deleted file mode 100644 index b63493a..0000000 --- a/rust/src/devices/virtio_9p/fid.rs +++ /dev/null @@ -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, -} - -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(&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(&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 { - 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 { - 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 { - let path = self.fid(id).path.clone(); - self.filesystem.readlink(&path) - } - - pub fn metadata(&mut self, id: u32) -> io::Result { - 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 { - 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; - } -} - diff --git a/rust/src/devices/virtio_9p/file.rs b/rust/src/devices/virtio_9p/file.rs new file mode 100644 index 0000000..cab9676 --- /dev/null +++ b/rust/src/devices/virtio_9p/file.rs @@ -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>(Arc>>); +impl > Buffer { + 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 { + 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 { + 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 { + 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 { + 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 { + ops: T, + root: PathBuf, + fidmap: BTreeMap>, +} + +impl Fids { + pub fn new(root: PathBuf, ops: T) -> Self { + Fids { + ops, + root, + fidmap: BTreeMap::new(), + } + } + + pub fn fid(&self, id: u32) -> io::Result<&Fid> { + self.fidmap.get(&id).ok_or(Self::bad_fd_error()) + } + + pub fn fid_mut(&mut self, id: u32) -> io::Result<&mut Fid> { + self.fidmap.get_mut(&id).ok_or(Self::bad_fd_error()) + } + + pub fn read_fid(&self, pp: &mut PduParser) -> io::Result<&Fid> { + let id = pp.r32()?; + self.fid(id) + } + + pub fn read_new_path(&self, pp: &mut PduParser) -> io::Result { + 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 { + Fid::::path_join_name(qid, path, &self.root, name) + } + + pub fn clear(&mut self) { + self.fidmap.clear() + } + + pub fn add(&mut self, fid: Fid) { + 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> { + match self.fidmap.remove(&id) { + Some(fid) => Ok(fid), + None => Err(Self::bad_fd_error()) + } + } + + pub fn create>(&self, id: u32, path: P) -> io::Result> { + Fid::create(self.ops.clone(), id, path) + } + + pub fn read_qid(&self, path: &Path) -> io::Result { + self.ops.read_qid(path) + } + + fn bad_fd_error() -> io::Error { + io::Error::from_raw_os_error(libc::EBADF) + } +} + +pub struct Fid { + ops: T, + id: u32, + path: PathBuf, + qid: Qid, + file: Option, + directory: RefCell>, +} + +impl Fid { + fn create>(ops: T, id: u32, path: P) -> io::Result { + 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>(&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 { + Self::path_join_name(self.qid, self.path(), root, name) + } + + fn path_join_name(qid: Qid, path: &Path, root: &Path, name: &str) -> io::Result { + 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>{ + self.directory.borrow_mut() + } +} + +impl fmt::Display for Fid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Fid:({}, id={})", self.path().display(), self.id) + } +} + +fn system_error(errno: libc::c_int) -> io::Result { + Err(io::Error::from_raw_os_error(errno)) +} diff --git a/rust/src/devices/virtio_9p/filesystem.rs b/rust/src/devices/virtio_9p/filesystem.rs index 103c60c..ee22f85 100644 --- a/rust/src/devices/virtio_9p/filesystem.rs +++ b/rust/src/devices/virtio_9p/filesystem.rs @@ -1,46 +1,21 @@ -use std::mem; -use std::ffi::CString; -use std::ffi::OsString; -use std::os::unix::ffi::OsStrExt; -use std::fs::{self,File,Metadata,OpenOptions}; - +use std::ffi::{CString,OsString}; +use std::fs::{self, File, Metadata, OpenOptions}; 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 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 { Atime, @@ -49,195 +24,90 @@ pub enum FsTouch { MtimeNow, } -pub trait FileSystemOps { - fn open(&self, path: &Path, flags: u32) -> io::Result; - fn open_dir(&self, path: &Path) -> io::Result; - fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result; - fn stat(&self, path: &Path) -> io::Result; - fn statfs(&self, path: &Path) -> io::Result; +pub trait FileSystemOps: Clone+Sync+Send { + fn read_qid(&self, path: &Path) -> io::Result; + fn write_stat(&self, path: &Path, pp: &mut PduParser) -> io::Result<()>; + fn open(&self, path: &Path, flags: u32) -> io::Result; + fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result; + fn write_statfs(&self, path: &Path, pp: &mut PduParser) -> 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 truncate(&self, path: &Path, size: u64) -> io::Result<()>; fn readlink(&self, path: &Path) -> io::Result; - // 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; } #[derive(Clone)] pub struct FileSystem { - init_path: PathBuf, - resolver: PathResolver, + root: PathBuf, readonly: 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)), - } - } + euid_root: bool, } impl FileSystem { - pub fn new(root: PathBuf, init_path: PathBuf, readonly: bool) -> FileSystem { - FileSystem { resolver: PathResolver::new(root), init_path, readonly } + pub fn new(root: PathBuf, readonly: bool) -> FileSystem { + let euid_root = Self::is_euid_root(); + FileSystem { root, readonly, euid_root } } - fn fullpath(&self, path: &Path) -> io::Result { - if path.to_str().unwrap() == "/phinit" { - return Ok(self.init_path.clone()) + pub fn is_euid_root() -> bool { + unsafe { libc::geteuid() == 0 } + } + + pub fn create_with_flags(path: &Path, flags: u32, mode: u32, is_root: bool) -> io::Result { + 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 { + 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 { + let path = self.canonicalize(path)?; + path.symlink_metadata() + } + + fn canonicalize_parent(&self, path: &Path) -> io::Result { + 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 { + let canon = path.canonicalize()?; + if !canon.starts_with(&self.root) { + return Err(io::Error::from_raw_os_error(libc::EIO)) } - self.resolver.fullpath(path) - } - - - fn flags_to_open_options(&self, flags: u32) -> io::Result { - 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 { - 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 { - 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 { - 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 { - 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 { - 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) + Ok(canon) } } @@ -246,68 +116,80 @@ fn cstr(path: &Path) -> io::Result { } impl FileSystemOps for FileSystem { - fn open(&self, path: &Path, flags: u32) -> io::Result { - let fullpath = self.fullpath(path)?; - let meta = fullpath.metadata()?; - if meta.is_dir() { - 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 read_qid(&self, path: &Path) -> io::Result { + let meta = self.metadata(&path)?; + let qid = Qid::from_metadata(&meta); + Ok(qid) } - fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result { - let fullpath = self.fullpath(path)?; - let mut options = self.flags_to_open_options(flags)?; - options.create(true); - options.mode(mode & 0o777); - let file = options.open(&fullpath)?; - return Ok(FileDescriptor::File(file)) + fn write_stat(&self, path: &Path, pp: &mut PduParser) -> io::Result<()> { + let meta = self.metadata(path)?; + + const P9_STATS_BASIC: u64 = 0x000007ff; + pp.w64(P9_STATS_BASIC)?; + + 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 { - let fullpath = self.fullpath(path)?; - let read_dir = ReadDir::open(&fullpath)?; - return Ok(FileDescriptor::Dir(read_dir)) + fn open(&self, path: &Path, flags: u32) -> io::Result { + let path = self.canonicalize(path)?; + let file =FileSystem::open_with_flags(&path, flags, self.euid_root)?; + Ok(self.new_file(file)) } - fn stat(&self, path: &Path) -> io::Result { - let fullpath = self.fullpath(path)?; - let meta = fullpath.metadata()?; - Ok(meta) + fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result { + let path = self.canonicalize_parent(path)?; + let file = FileSystem::create_with_flags(&path, flags, mode, self.euid_root)?; + Ok(self.new_file(file)) } - fn statfs(&self, path: &Path) -> io::Result { - let fullpath = self.fullpath(path)?; - let path_cstr = cstr(&fullpath)?; - let mut stat: LibcStatFs; + fn write_statfs(&self, path: &Path, pp: &mut PduParser) -> io::Result<()> { + let path = self.canonicalize(path)?; + let path_cstr = cstr(&path)?; + + let mut statfs: libc::statfs64 = unsafe { mem::zeroed() }; unsafe { - stat = mem::zeroed(); - let ret = statfs(path_cstr.as_ptr(), &mut stat); + let ret = libc::statfs64(path_cstr.as_ptr(), &mut statfs); if ret < 0 { return Err(io::Error::last_os_error()); } } - let mut statfs = StatFs::new(); - statfs.f_type = stat.f_type as u32; - statfs.f_bsize = stat.f_bsize as u32; - statfs.f_blocks = stat.f_blocks; - statfs.f_bfree = stat.f_bfree; - statfs.f_bavail = stat.f_bavail; - statfs.f_files = stat.f_files; - statfs.f_ffree = stat.f_ffree; - statfs.f_namelen = stat.f_namelen as u32; - statfs.fsid = stat.f_fsid.val[0] as u64 | ((stat.f_fsid.val[1] as u64) << 32); - - Ok(statfs) - + pp.w32(statfs.f_type as u32)?; + pp.w32(statfs.f_bsize as u32)?; + pp.w64(statfs.f_blocks)?; + pp.w64(statfs.f_bfree)?; + pp.w64(statfs.f_bavail)?; + pp.w64(statfs.f_files)?; + pp.w64(statfs.f_ffree)?; + pp.w64(0)?; + pp.w32(statfs.f_namelen as u32)?; + Ok(()) } + fn chown(&self, path: &Path, uid: u32, gid: u32) -> io::Result<()> { - let fullpath = self.fullpath(path)?; - let path_cstr = cstr(&fullpath)?; + let path = self.canonicalize(path)?; + let path_cstr = cstr(&path)?; unsafe { if libc::chown(path_cstr.as_ptr(), uid, gid) < 0 { return Err(io::Error::last_os_error()); @@ -316,21 +198,14 @@ impl FileSystemOps for FileSystem { } } - fn chmod(&self, path: &Path, mode: u32) -> io::Result<()> { - // XXX see std::os::unix::fs::PermissionsExt for a better way - let fullpath = self.fullpath(path)?; - let path_cstr = cstr(&fullpath)?; - unsafe { - if libc::chmod(path_cstr.as_ptr(), mode) < 0 { - return Err(io::Error::last_os_error()); - } - Ok(()) - } + fn set_mode(&self, path: &Path, mode: u32) -> io::Result<()> { + let meta = self.metadata(path)?; + Ok(meta.permissions().set_mode(mode)) } fn touch(&self, path: &Path, which: FsTouch, tv: (u64, u64)) -> io::Result<()> { - let fullpath = self.fullpath(path)?; - let path_cstr = cstr(&fullpath)?; + let path = self.canonicalize(path)?; + let path_cstr = cstr(&path)?; let tval = libc::timespec { tv_sec: tv.0 as i64, @@ -352,8 +227,7 @@ impl FileSystemOps for FileSystem { FsTouch::MtimeNow => [omit, now], }; unsafe { - // XXX this could be wildly wrong but libc has wrong type - if libc::utimensat(-1, path_cstr.as_ptr(), ×.as_ptr() as *const _ as *const libc::timespec, 0) < 0 { + if libc::utimensat(-1, path_cstr.as_ptr(), times.as_ptr(), 0) < 0 { return Err(io::Error::last_os_error()); } } @@ -361,8 +235,8 @@ impl FileSystemOps for FileSystem { } fn truncate(&self, path: &Path, size: u64) -> io::Result<()> { - let fullpath = self.fullpath(path)?; - let path_cstr = cstr(&fullpath)?; + let path = self.canonicalize(path)?; + let path_cstr = cstr(&path)?; unsafe { if libc::truncate64(path_cstr.as_ptr(), size as i64) < 0 { return Err(io::Error::last_os_error()); @@ -371,42 +245,57 @@ impl FileSystemOps for FileSystem { Ok(()) } - // XXX fn readlink(&self, path: &Path) -> io::Result { - let fullpath = self.fullpath(path)?; - fs::read_link(&fullpath).map(|pbuf| pbuf.into_os_string()) + let path = self.canonicalize(path)?; + 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 { + 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) -} - - diff --git a/rust/src/devices/virtio_9p/mod.rs b/rust/src/devices/virtio_9p/mod.rs index e4ab943..bbe51b5 100644 --- a/rust/src/devices/virtio_9p/mod.rs +++ b/rust/src/devices/virtio_9p/mod.rs @@ -1,33 +1,37 @@ use std::sync::{Arc,RwLock}; use std::thread; -use std::path::{Path,PathBuf}; +use std::path::{PathBuf, Path}; use crate::memory::{GuestRam, MemoryManager}; use crate::virtio::{self,VirtioBus,VirtioDeviceOps, VirtQueue}; use crate::vm::Result; - -mod fid; -mod pdu; -mod commands; -mod readdir; -mod filesystem; - +use crate::devices::virtio_9p::server::Server; +use crate::devices::virtio_9p::filesystem::{FileSystem, FileSystemOps}; 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_9P_MOUNT_TAG: u64 = 0x1; +pub use synthetic::SyntheticFS; -pub struct VirtioP9 { +pub struct VirtioP9 { + filesystem: T, root_dir: PathBuf, - init_path: PathBuf, feature_bits: u64, + debug: bool, config: Vec, } -impl VirtioP9 { +impl VirtioP9 { fn create_config(tag_name: &str) -> Vec { let tag_len = tag_name.len() as u16; let mut config = Vec::with_capacity(tag_name.len() + 3); @@ -38,17 +42,18 @@ impl VirtioP9 { config } - fn new(tag_name: &str, root_dir: &str, init_path: &Path) -> Arc> { + fn new(filesystem: T, tag_name: &str, root_dir: &str, debug: bool) -> Arc> { Arc::new(RwLock::new(VirtioP9 { + filesystem, root_dir: PathBuf::from(root_dir), - init_path: init_path.to_path_buf(), feature_bits: 0, - config: VirtioP9::create_config(tag_name), + debug, + config: VirtioP9::::create_config(tag_name), })) } - pub fn create(vbus: &mut VirtioBus, tag_name: &str, root_dir: &str, init_path: &Path) -> Result<()> { - vbus.new_virtio_device(VIRTIO_ID_9P, VirtioP9::new(tag_name, root_dir, init_path)) + 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(filesystem, tag_name, root_dir, debug)) .set_num_queues(1) .set_features(VIRTIO_9P_MOUNT_TAG) .set_config_size(tag_name.len() + 3) @@ -56,7 +61,15 @@ impl VirtioP9 { } } -impl VirtioDeviceOps for VirtioP9 { +impl VirtioP9 { + + 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 VirtioDeviceOps for VirtioP9 { fn reset(&mut self) { println!("Reset called"); } @@ -70,22 +83,26 @@ impl VirtioDeviceOps for VirtioP9 { virtio::read_config_buffer(&self.config, offset, size) } - fn start(&mut self, memory: &MemoryManager, mut queues: Vec) { let vq = queues.pop().unwrap(); 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(); - 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) { - let mut commands = Commands::new(root_dir,init_path,memory.clone()); +fn run_device(memory: GuestRam, vq: VirtQueue, root_dir: &Path, filesystem: T, debug: bool) { + let mut server = Server::new(&root_dir, filesystem); + + if debug { + server.enable_debug(); + } vq.on_each_chain(|mut chain| { let mut pp = PduParser::new(&mut chain, memory.clone()); - commands.handle(&mut pp); + server.handle(&mut pp); }); } diff --git a/rust/src/devices/virtio_9p/pdu.rs b/rust/src/devices/virtio_9p/pdu.rs index f20db60..c3fe243 100644 --- a/rust/src/devices/virtio_9p/pdu.rs +++ b/rust/src/devices/virtio_9p/pdu.rs @@ -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::os::linux::fs::MetadataExt; use std::os::unix::ffi::OsStrExt; use std::ffi::OsStr; + +use libc; +use byteorder::{LittleEndian,ReadBytesExt,WriteBytesExt}; + +use crate::devices::virtio_9p::file::Qid; use crate::memory::GuestRam; use crate::virtio::Chain; -use super::filesystem::StatFs; - -use libc; - -const P9_STATS_BASIC: u64 = 0x000007ff; - const P9_HEADER_LEN: usize = 7; - -const P9_QTFILE: u8 = 0x00; -const P9_QTLINK: u8 = 0x01; -const _P9_QTSYMLINK: u8 = 0x02; - -const P9_QTDIR: u8 = 0x80; +const P9_RLERROR: u8 = 7; pub struct PduParser<'a> { memory: GuestRam, @@ -32,7 +22,7 @@ pub struct PduParser<'a> { reply_start_addr: u64, } -#[derive(Default)] +#[derive(Default,Debug)] pub struct P9Attr { valid: u32, mode: u32, @@ -46,15 +36,15 @@ pub struct P9Attr { } impl P9Attr { - const MODE: u32 = (1 << 0); - const UID: u32 = (1 << 1); - const GID: u32 = (1 << 2); - const SIZE: u32 = (1 << 3); - const ATIME: u32 = (1 << 4); - const MTIME: u32 = (1 << 5); - const CTIME: u32 = (1 << 6); - const ATIME_SET: u32 = (1 << 7); - const MTIME_SET: u32 = (1 << 8); + const MODE: u32 = (1 << 0); // 0x01 + const UID: u32 = (1 << 1); // 0x02 + const GID: u32 = (1 << 2); // 0x04 + const SIZE: u32 = (1 << 3); // 0x08 + const ATIME: u32 = (1 << 4); // 0x10 + const MTIME: u32 = (1 << 5); // 0x20 + const CTIME: u32 = (1 << 6); // 0x40 + const ATIME_SET: u32 = (1 << 7); // 0x80 + const MTIME_SET: u32 = (1 << 8); // 0x100 const MASK: u32 = 127; const NO_UID: u32 = 0xFFFFFFFF; @@ -75,6 +65,7 @@ impl P9Attr { self.valid & P9Attr::MASK == P9Attr::CTIME|| self.is_valid(P9Attr::UID|P9Attr::GID) } + pub fn has_size(&self) -> bool { self.is_valid(P9Attr::SIZE) } pub fn mode(&self) -> u32 { @@ -115,7 +106,6 @@ impl P9Attr { } } - impl <'a> 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 } @@ -129,8 +119,9 @@ impl <'a> PduParser<'a> { } pub fn read_done(&mut self) -> io::Result<()> { - // XXX unwrap - self.reply_start_addr = self.chain.current_write_address(8).unwrap(); + self.reply_start_addr = self.chain.current_write_address(8) + .ok_or(io::Error::from_raw_os_error(libc::EIO))?; + // reserve header self.w32(0)?; self.w8(0)?; @@ -138,20 +129,47 @@ impl <'a> PduParser<'a> { 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<()> { + 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 { self.read_done()?; } - - let err = match error.raw_os_error() { - Some(errno) => errno as u32, - None => 0, - }; - + self.w32(errno)?; self._w32_at(0,P9_HEADER_LEN as u32 + 4); self._w8_at(4, P9_RLERROR); self._w16_at(5, self.tag); - self._w32_at(7, err); self.chain.flush_chain(); Ok(()) } @@ -182,7 +200,6 @@ impl <'a> PduParser<'a> { self.memory.write_int::(self.reply_start_addr + offset as u64, val).unwrap(); } - pub fn write_done(&mut self) -> io::Result<()> { self._w32_at(0, self.chain.get_wlen() as u32); let cmd = self.cmd + 1; @@ -205,6 +222,16 @@ impl <'a> PduParser<'a> { Ok(s) } + pub fn read_string_list(&mut self) -> io::Result> { + 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 { let mut attr = P9Attr::new(); attr.parse(self)?; @@ -212,8 +239,9 @@ impl <'a> PduParser<'a> { } pub fn write_string(&mut self, str: &str) -> io::Result<()> { - self.w16(str.len() as u16)?; - self.chain.write_all(str.as_bytes()) + let bytes = 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<()> { @@ -221,70 +249,11 @@ impl <'a> PduParser<'a> { self.chain.write_all(str.as_bytes()) } - - fn is_lnk(meta: &Metadata) -> bool { - meta.st_mode() & libc::S_IFMT == libc::S_IFLNK - } - - 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_list(&mut self, list: &[Qid]) -> io::Result<()> { + self.w16(list.len() as u16)?; + for qid in list { + qid.write(self)?; } - } - - 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(()) } @@ -319,4 +288,3 @@ impl <'a> PduParser<'a> { self.chain.write_u64::(val) } } - diff --git a/rust/src/devices/virtio_9p/readdir.rs b/rust/src/devices/virtio_9p/readdir.rs deleted file mode 100644 index 4932a02..0000000 --- a/rust/src/devices/virtio_9p/readdir.rs +++ /dev/null @@ -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 { - Ok(CString::new(path.as_os_str().as_bytes())?) -} - -impl ReadDir { - pub fn open(path: &Path) -> io::Result { - 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 { - 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; - - fn next(&mut self) -> Option> { - 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() - } - } -} - diff --git a/rust/src/devices/virtio_9p/server.rs b/rust/src/devices/virtio_9p/server.rs new file mode 100644 index 0000000..6bfe2ae --- /dev/null +++ b/rust/src/devices/virtio_9p/server.rs @@ -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 { + root: PathBuf, + debug: bool, + msize: u32, + fids: Fids, + filesystem: T, +} + +fn system_error(errno: libc::c_int) -> io::Result { + Err(io::Error::from_raw_os_error(errno)) +} + +impl Server { + + 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> { + self.fids.fid_mut(id) + } + + fn read_fid(&self, pp: &mut PduParser) -> io::Result<&Fid> { + 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 { + 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> { + 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, 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, 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, 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> { + 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, 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, 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, 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, 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, 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 { + 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, u32, Vec)> { + 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(fids: &Fids, 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, 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, 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> { + 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) + } +} + diff --git a/rust/src/devices/virtio_9p/synthetic.rs b/rust/src/devices/virtio_9p/synthetic.rs new file mode 100644 index 0000000..90540de --- /dev/null +++ b/rust/src/devices/virtio_9p/synthetic.rs @@ -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, 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>(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>(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> { + match self { + Node::Dir(entries, ..) => Some(entries), + _ => None, + } + } + + fn entries_mut(&mut self) -> Option<&mut BTreeMap> { + 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 { + 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>(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, + 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, + 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>(&mut self, paths: &[P]) { + for p in paths { + self.mkdir(p, 0o755); + } + } + + pub fn mkdir>(&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, P: AsRef>(&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, P: AsRef, Q: AsRef>(&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>(&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 { + 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 { + 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>(&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, Q: AsRef>(&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> { + 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 { + 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 { + 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 { + 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 { + 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 { + let node = self.lookup(path)?; + node.populate_directory() + } +} + +fn rawerr(errno: i32) -> io::Error { + io::Error::from_raw_os_error(errno) +} +fn syserr(errno: i32) -> io::Result { + Err(rawerr(errno)) +} \ No newline at end of file