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