pH/src/devices/virtio_9p/filesystem.rs
2019-09-20 17:29:55 -04:00

302 lines
9.7 KiB
Rust

use std::ffi::{CString,OsString};
use std::fs::{self, File, Metadata, OpenOptions};
use std::io;
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 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};
pub enum FsTouch {
Atime,
AtimeNow,
Mtime,
MtimeNow,
}
pub trait FileSystemOps: Clone+Sync+Send {
fn read_qid(&self, path: &Path) -> io::Result<Qid>;
fn write_stat(&self, path: &Path, pp: &mut PduParser) -> io::Result<()>;
fn open(&self, path: &Path, flags: u32) -> io::Result<P9File>;
fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result<P9File>;
fn write_statfs(&self, path: &Path, pp: &mut PduParser) -> io::Result<()>;
fn chown(&self, path: &Path, uid: u32, gid: 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<OsString>;
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)]
pub struct FileSystem {
root: PathBuf,
readonly: bool,
euid_root: bool,
}
impl FileSystem {
pub fn new(root: PathBuf, readonly: bool) -> FileSystem {
let euid_root = Self::is_euid_root();
FileSystem { root, readonly, euid_root }
}
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<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))
}
Ok(canon)
}
}
fn cstr(path: &Path) -> io::Result<CString> {
Ok(CString::new(path.as_os_str().as_bytes())?)
}
impl FileSystemOps for FileSystem {
fn read_qid(&self, path: &Path) -> io::Result<Qid> {
let meta = self.metadata(&path)?;
let qid = Qid::from_metadata(&meta);
Ok(qid)
}
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(&self, path: &Path, flags: u32) -> io::Result<P9File> {
let path = self.canonicalize(path)?;
let file =FileSystem::open_with_flags(&path, flags, self.euid_root)?;
Ok(self.new_file(file))
}
fn create(&self, path: &Path, flags: u32, mode: u32) -> io::Result<P9File> {
let path = self.canonicalize_parent(path)?;
let file = FileSystem::create_with_flags(&path, flags, mode, self.euid_root)?;
Ok(self.new_file(file))
}
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 {
let ret = libc::statfs64(path_cstr.as_ptr(), &mut statfs);
if ret < 0 {
return Err(io::Error::last_os_error());
}
}
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 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());
}
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 path = self.canonicalize(path)?;
let path_cstr = cstr(&path)?;
let tval = libc::timespec {
tv_sec: tv.0 as i64,
tv_nsec: tv.1 as i64,
};
let omit = libc::timespec {
tv_sec: 0,
tv_nsec: libc::UTIME_OMIT,
};
let now = libc::timespec {
tv_sec: 0,
tv_nsec: libc::UTIME_NOW,
};
let times = match which {
FsTouch::Atime => [tval, omit],
FsTouch::AtimeNow => [ now, omit ],
FsTouch::Mtime => [omit, tval ],
FsTouch::MtimeNow => [omit, now],
};
unsafe {
if libc::utimensat(-1, path_cstr.as_ptr(), times.as_ptr(), 0) < 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
fn truncate(&self, path: &Path, size: u64) -> io::Result<()> {
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());
}
}
Ok(())
}
fn readlink(&self, path: &Path) -> io::Result<OsString> {
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<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)
}
}