mod sys;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::bail;
use argh::FromArgs;
use base::warn;
use base::RawDescriptor;
use base::Tube;
use base::WorkerThread;
use data_model::Le32;
use fuse::Server;
use hypervisor::ProtectionType;
use sync::Mutex;
pub use sys::start_device as run_fs_device;
use virtio_sys::virtio_fs::virtio_fs_config;
use vm_memory::GuestMemory;
use vmm_vhost::message::VhostUserProtocolFeatures;
use vmm_vhost::VHOST_USER_F_PROTOCOL_FEATURES;
use zerocopy::AsBytes;
use crate::virtio;
use crate::virtio::copy_config;
use crate::virtio::device_constants::fs::FS_MAX_TAG_LEN;
use crate::virtio::fs::passthrough::PassthroughFs;
use crate::virtio::fs::Config;
use crate::virtio::fs::Result as FsResult;
use crate::virtio::fs::Worker;
use crate::virtio::vhost::user::device::handler::Error as DeviceError;
use crate::virtio::vhost::user::device::handler::VhostUserDevice;
use crate::virtio::Queue;
const MAX_QUEUE_NUM: usize = 2; struct FsBackend {
server: Arc<fuse::Server<PassthroughFs>>,
tag: String,
avail_features: u64,
workers: BTreeMap<usize, WorkerThread<FsResult<Queue>>>,
keep_rds: Vec<RawDescriptor>,
}
impl FsBackend {
#[allow(unused_variables)]
pub fn new(
tag: &str,
shared_dir: &str,
skip_pivot_root: bool,
cfg: Option<Config>,
) -> anyhow::Result<Self> {
if tag.len() > FS_MAX_TAG_LEN {
bail!(
"fs tag is too long: {} (max supported: {})",
tag.len(),
FS_MAX_TAG_LEN
);
}
let avail_features = virtio::base_features(ProtectionType::Unprotected)
| 1 << VHOST_USER_F_PROTOCOL_FEATURES;
#[allow(unused_mut)]
let mut fs = PassthroughFs::new(tag, cfg.unwrap_or_default())?;
#[cfg(feature = "fs_runtime_ugid_map")]
if skip_pivot_root {
fs.set_root_dir(shared_dir.to_string())?;
}
let mut keep_rds: Vec<RawDescriptor> = [0, 1, 2].to_vec();
keep_rds.append(&mut fs.keep_rds());
let server = Arc::new(Server::new(fs));
Ok(FsBackend {
server,
tag: tag.to_owned(),
avail_features,
workers: Default::default(),
keep_rds,
})
}
}
impl VhostUserDevice for FsBackend {
fn max_queue_num(&self) -> usize {
MAX_QUEUE_NUM
}
fn features(&self) -> u64 {
self.avail_features
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
let mut config = virtio_fs_config {
tag: [0; FS_MAX_TAG_LEN],
num_request_queues: Le32::from(1),
};
config.tag[..self.tag.len()].copy_from_slice(self.tag.as_bytes());
copy_config(data, 0, config.as_bytes(), offset);
}
fn reset(&mut self) {
for worker in std::mem::take(&mut self.workers).into_values() {
let _ = worker.stop();
}
}
fn start_queue(
&mut self,
idx: usize,
queue: virtio::Queue,
_mem: GuestMemory,
) -> anyhow::Result<()> {
if self.workers.contains_key(&idx) {
warn!("Starting new queue handler without stopping old handler");
self.stop_queue(idx)?;
}
let (_, fs_device_tube) = Tube::pair()?;
let tube = Arc::new(Mutex::new(fs_device_tube));
let server = self.server.clone();
let irq = queue.interrupt().clone();
let slot: u32 = 0;
let worker = WorkerThread::start(format!("v_fs:{}:{}", self.tag, idx), move |kill_evt| {
let mut worker = Worker::new(queue, server, irq, tube, slot);
worker.run(kill_evt, false)?;
Ok(worker.queue)
});
self.workers.insert(idx, worker);
Ok(())
}
fn stop_queue(&mut self, idx: usize) -> anyhow::Result<virtio::Queue> {
if let Some(worker) = self.workers.remove(&idx) {
let queue = match worker.stop() {
Ok(queue) => queue,
Err(_) => panic!("failed to recover queue from worker"),
};
Ok(queue)
} else {
Err(anyhow::Error::new(DeviceError::WorkerNotFound))
}
}
fn enter_suspended_state(&mut self) -> anyhow::Result<()> {
Ok(())
}
fn snapshot(&mut self) -> anyhow::Result<serde_json::Value> {
bail!("snapshot not implemented for vhost-user fs");
}
fn restore(&mut self, _data: serde_json::Value) -> anyhow::Result<()> {
bail!("snapshot not implemented for vhost-user fs");
}
}
#[derive(FromArgs)]
#[argh(subcommand, name = "fs")]
pub struct Options {
#[argh(option, arg_name = "PATH", hidden_help)]
socket: Option<String>,
#[argh(option, arg_name = "PATH")]
socket_path: Option<String>,
#[argh(option, arg_name = "FD")]
fd: Option<RawDescriptor>,
#[argh(option, arg_name = "TAG")]
tag: String,
#[argh(option, arg_name = "DIR")]
shared_dir: PathBuf,
#[argh(option, arg_name = "UIDMAP")]
uid_map: Option<String>,
#[argh(option, arg_name = "GIDMAP")]
gid_map: Option<String>,
#[argh(option, arg_name = "CFG")]
cfg: Option<Config>,
#[argh(option, arg_name = "UID", default = "0")]
uid: u32,
#[argh(option, arg_name = "GID", default = "0")]
gid: u32,
#[argh(switch)]
disable_sandbox: bool,
#[argh(option, arg_name = "skip_pivot_root", default = "false")]
#[allow(dead_code)]
skip_pivot_root: bool,
}