blob: 65d98e3389d7f0d02c3c4046eabc9477138b186b [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
mod sys;
use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Context;
use argh::FromArgs;
use base::error;
use base::warn;
use base::AsRawDescriptors;
use base::Event;
use base::RawDescriptor;
use base::Tube;
use cros_async::EventAsync;
use cros_async::Executor;
use data_model::Le32;
use fuse::Server;
use futures::future::AbortHandle;
use futures::future::Abortable;
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::message::VhostUserVirtioFeatures;
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::Config;
use crate::virtio::fs::passthrough::PassthroughFs;
use crate::virtio::fs::process_fs_queue;
use crate::virtio::vhost::user::device::handler::sys::Doorbell;
use crate::virtio::vhost::user::device::handler::Error as DeviceError;
use crate::virtio::vhost::user::device::handler::VhostUserBackend;
use crate::virtio::vhost::user::device::handler::WorkerState;
use crate::virtio::Queue;
const MAX_QUEUE_NUM: usize = 2; /* worker queue and high priority queue */
async fn handle_fs_queue(
queue: Rc<RefCell<virtio::Queue>>,
mem: GuestMemory,
doorbell: Doorbell,
kick_evt: EventAsync,
server: Arc<fuse::Server<PassthroughFs>>,
tube: Arc<Mutex<Tube>>,
) {
// Slot is always going to be 0 because we do not support DAX
let slot: u32 = 0;
loop {
if let Err(e) = kick_evt.next_val().await {
error!("Failed to read kick event for fs queue: {}", e);
break;
}
if let Err(e) = process_fs_queue(&mem, &doorbell, &queue, &server, &tube, slot) {
error!("Process FS queue failed: {}", e);
break;
}
}
}
struct FsBackend {
ex: Executor,
server: Arc<fuse::Server<PassthroughFs>>,
tag: [u8; FS_MAX_TAG_LEN],
avail_features: u64,
acked_features: u64,
acked_protocol_features: VhostUserProtocolFeatures,
workers: [Option<WorkerState<Rc<RefCell<Queue>>, ()>>; MAX_QUEUE_NUM],
keep_rds: Vec<RawDescriptor>,
}
impl FsBackend {
pub fn new(ex: &Executor, tag: &str, 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 mut fs_tag = [0u8; FS_MAX_TAG_LEN];
fs_tag[..tag.len()].copy_from_slice(tag.as_bytes());
let avail_features = virtio::base_features(ProtectionType::Unprotected)
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
// Use default passthroughfs config
let fs = PassthroughFs::new(tag, cfg.unwrap_or_default())?;
let mut keep_rds: Vec<RawDescriptor> = [0, 1, 2].to_vec();
keep_rds.append(&mut fs.keep_rds());
let ex = ex.clone();
keep_rds.extend(ex.as_raw_descriptors());
let server = Arc::new(Server::new(fs));
Ok(FsBackend {
ex,
server,
tag: fs_tag,
avail_features,
acked_features: 0,
acked_protocol_features: VhostUserProtocolFeatures::empty(),
workers: Default::default(),
keep_rds,
})
}
}
impl VhostUserBackend for FsBackend {
fn max_queue_num(&self) -> usize {
MAX_QUEUE_NUM
}
fn features(&self) -> u64 {
self.avail_features
}
fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
let unrequested_features = value & !self.avail_features;
if unrequested_features != 0 {
bail!("invalid features are given: {:#x}", unrequested_features);
}
self.acked_features |= value;
Ok(())
}
fn acked_features(&self) -> u64 {
self.acked_features
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
}
fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
let features = VhostUserProtocolFeatures::from_bits(features)
.ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
let supported = self.protocol_features();
self.acked_protocol_features = features & supported;
Ok(())
}
fn acked_protocol_features(&self) -> u64 {
self.acked_protocol_features.bits()
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
let config = virtio_fs_config {
tag: self.tag,
num_request_queues: Le32::from(1),
};
copy_config(data, 0, config.as_bytes(), offset);
}
fn reset(&mut self) {
for worker in self.workers.iter_mut().filter_map(Option::take) {
worker.abort_handle.abort();
}
}
fn start_queue(
&mut self,
idx: usize,
queue: virtio::Queue,
mem: GuestMemory,
doorbell: Doorbell,
kick_evt: Event,
) -> anyhow::Result<()> {
if self.workers[idx].is_some() {
warn!("Starting new queue handler without stopping old handler");
self.stop_queue(idx)?;
}
let kick_evt = EventAsync::new(kick_evt, &self.ex)
.context("failed to create EventAsync for kick_evt")?;
let (handle, registration) = AbortHandle::new_pair();
let (_, fs_device_tube) = Tube::pair()?;
let queue = Rc::new(RefCell::new(queue));
let queue_task = self.ex.spawn_local(Abortable::new(
handle_fs_queue(
queue.clone(),
mem,
doorbell,
kick_evt,
self.server.clone(),
Arc::new(Mutex::new(fs_device_tube)),
),
registration,
));
self.workers[idx] = Some(WorkerState {
abort_handle: handle,
queue_task,
queue,
});
Ok(())
}
fn stop_queue(&mut self, idx: usize) -> anyhow::Result<virtio::Queue> {
if let Some(worker) = self.workers.get_mut(idx).and_then(Option::take) {
worker.abort_handle.abort();
// Wait for queue_task to be aborted.
let _ = self.ex.run_until(async { worker.queue_task.await });
let queue = match Rc::try_unwrap(worker.queue) {
Ok(queue_cell) => queue_cell.into_inner(),
Err(_) => panic!("failed to recover queue from worker"),
};
Ok(queue)
} else {
Err(anyhow::Error::new(DeviceError::WorkerNotFound))
}
}
}
#[derive(FromArgs)]
#[argh(subcommand, name = "fs")]
/// FS Device
pub struct Options {
#[argh(option, arg_name = "PATH")]
/// path to a vhost-user socket
socket: Option<String>,
#[argh(option, arg_name = "STRING")]
/// VFIO-PCI device name (e.g. '0000:00:07.0')
vfio: Option<String>,
#[argh(option, arg_name = "TAG")]
/// the virtio-fs tag
tag: String,
#[argh(option, arg_name = "DIR")]
/// path to a directory to share
shared_dir: PathBuf,
#[argh(option, arg_name = "UIDMAP")]
/// uid map to use
uid_map: Option<String>,
#[argh(option, arg_name = "GIDMAP")]
/// gid map to use
gid_map: Option<String>,
#[argh(option, arg_name = "CFG")]
/// colon-separated options for configuring a directory to be
/// shared with the VM through virtio-fs. The format is the same as
/// `crosvm run --shared-dir` flag except only the keys related to virtio-fs
/// are valid here.
cfg: Option<Config>,
#[argh(option, arg_name = "UID", default = "0")]
/// uid of the device process in the new user namespace created by minijail.
/// These two options (uid/gid) are useful when the crosvm process cannot
/// get CAP_SETGID/CAP_SETUID but an identity mapping of the current
/// user/group between the VM and the host is required.
/// Say the current user and the crosvm process has uid 5000, a user can use
/// "uid=5000" and "uidmap=5000 5000 1" such that files owned by user 5000
/// still appear to be owned by user 5000 in the VM. These 2 options are
/// useful only when there is 1 user in the VM accessing shared files.
/// If multiple users want to access the shared file, gid/uid options are
/// useless. It'd be better to create a new user namespace and give
/// CAP_SETUID/CAP_SETGID to the crosvm.
/// Default: 0.
uid: u32,
#[argh(option, arg_name = "GID", default = "0")]
/// gid of the device process in the new user namespace created by minijail.
/// Default: 0.
gid: u32,
}