blob: 78956c15966bd7099d0f720835027f73f0ae27d7 [file] [log] [blame]
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::BTreeMap;
use std::io;
use std::sync::Arc;
use anyhow::anyhow;
use base::error;
use base::warn;
use base::AsRawDescriptor;
use base::Error as SysError;
use base::RawDescriptor;
use base::Tube;
use base::WorkerThread;
use data_model::Le32;
use remain::sorted;
use resources::Alloc;
use sync::Mutex;
use thiserror::Error;
use virtio_sys::virtio_fs::virtio_fs_config;
use virtio_sys::virtio_fs::VIRTIO_FS_SHMCAP_ID_CACHE;
use vm_control::FsMappingRequest;
use vm_control::VmResponse;
use vm_memory::GuestMemory;
use zerocopy::AsBytes;
use crate::pci::PciAddress;
use crate::pci::PciBarConfiguration;
use crate::pci::PciBarPrefetchable;
use crate::pci::PciBarRegionType;
use crate::pci::PciCapability;
use crate::virtio::copy_config;
use crate::virtio::device_constants::fs::FS_MAX_TAG_LEN;
use crate::virtio::DeviceType;
use crate::virtio::Interrupt;
use crate::virtio::PciCapabilityType;
use crate::virtio::Queue;
use crate::virtio::VirtioDevice;
use crate::virtio::VirtioPciShmCap;
mod caps;
mod config;
mod expiring_map;
mod multikey;
pub mod passthrough;
mod read_dir;
mod worker;
pub use config::CachePolicy;
pub use config::Config;
use fuse::Server;
use passthrough::PassthroughFs;
pub use worker::process_fs_queue;
use worker::Worker;
const QUEUE_SIZE: u16 = 1024;
const FS_BAR_NUM: u8 = 4;
const FS_BAR_OFFSET: u64 = 0;
const FS_BAR_SIZE: u64 = 1 << 33;
/// Errors that may occur during the creation or operation of an Fs device.
#[sorted]
#[derive(Error, Debug)]
pub enum Error {
/// Failed to create the file system.
#[error("failed to create file system: {0}")]
CreateFs(io::Error),
/// Creating WaitContext failed.
#[error("failed to create WaitContext: {0}")]
CreateWaitContext(SysError),
/// Error happened in FUSE.
#[error("fuse error: {0}")]
FuseError(fuse::Error),
/// Failed to get the uids for the worker thread.
#[error("failed to get uids for the worker thread: {0}")]
GetResuid(SysError),
/// Failed to get the securebits for the worker thread.
#[error("failed to get securebits for the worker thread: {0}")]
GetSecurebits(SysError),
/// A request is missing readable descriptors.
#[error("request does not have any readable descriptors")]
NoReadableDescriptors,
/// A request is missing writable descriptors.
#[error("request does not have any writable descriptors")]
NoWritableDescriptors,
/// Error while reading from the virtio queue's Event.
#[error("failed to read from virtio queue Event: {0}")]
ReadQueueEvent(SysError),
/// Failed to set the securebits for the worker thread.
#[error("failed to set securebits for the worker thread: {0}")]
SetSecurebits(SysError),
/// Failed to signal the virio used queue.
#[error("failed to signal used queue: {0}")]
SignalUsedQueue(SysError),
/// The tag for the Fs device was too long to fit in the config space.
#[error("Fs device tag is too long: len = {0}, max = {}", FS_MAX_TAG_LEN)]
TagTooLong(usize),
/// Calling unshare to disassociate FS attributes from parent failed.
#[error("failed to unshare fs from parent: {0}")]
UnshareFromParent(SysError),
/// Error while polling for events.
#[error("failed to wait for events: {0}")]
WaitError(SysError),
}
impl From<fuse::Error> for Error {
fn from(err: fuse::Error) -> Error {
Error::FuseError(err)
}
}
pub type Result<T> = ::std::result::Result<T, Error>;
pub struct Fs {
cfg: virtio_fs_config,
tag: String,
fs: Option<PassthroughFs>,
queue_sizes: Box<[u16]>,
avail_features: u64,
acked_features: u64,
pci_bar: Option<Alloc>,
tube: Option<Tube>,
workers: Vec<WorkerThread<Result<()>>>,
}
impl Fs {
pub fn new(
base_features: u64,
tag: &str,
num_workers: usize,
fs_cfg: Config,
tube: Tube,
) -> Result<Fs> {
if tag.len() > FS_MAX_TAG_LEN {
return Err(Error::TagTooLong(tag.len()));
}
let mut cfg_tag = [0u8; FS_MAX_TAG_LEN];
cfg_tag[..tag.len()].copy_from_slice(tag.as_bytes());
let cfg = virtio_fs_config {
tag: cfg_tag,
num_request_queues: Le32::from(num_workers as u32),
};
let fs = PassthroughFs::new(tag, fs_cfg).map_err(Error::CreateFs)?;
// There is always a high priority queue in addition to the request queues.
let num_queues = num_workers + 1;
Ok(Fs {
cfg,
tag: tag.to_string(),
fs: Some(fs),
queue_sizes: vec![QUEUE_SIZE; num_queues].into_boxed_slice(),
avail_features: base_features,
acked_features: 0,
pci_bar: None,
tube: Some(tube),
workers: Vec::with_capacity(num_workers + 1),
})
}
}
impl VirtioDevice for Fs {
fn keep_rds(&self) -> Vec<RawDescriptor> {
let mut fds = self
.fs
.as_ref()
.map(PassthroughFs::keep_rds)
.unwrap_or_default();
if let Some(rd) = self.tube.as_ref().map(|s| s.as_raw_descriptor()) {
fds.push(rd);
}
fds
}
fn device_type(&self) -> DeviceType {
DeviceType::Fs
}
fn queue_max_sizes(&self) -> &[u16] {
&self.queue_sizes
}
fn features(&self) -> u64 {
self.avail_features
}
fn ack_features(&mut self, mut v: u64) {
// Check if the guest is ACK'ing a feature that we didn't claim to have.
let unrequested_features = v & !self.avail_features;
if unrequested_features != 0 {
warn!("virtio_fs got unknown feature ack: {:x}", v);
// Don't count these features as acked.
v &= !unrequested_features;
}
self.acked_features |= v;
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
copy_config(data, 0, self.cfg.as_bytes(), offset)
}
fn activate(
&mut self,
_guest_mem: GuestMemory,
interrupt: Interrupt,
queues: BTreeMap<usize, Queue>,
) -> anyhow::Result<()> {
if queues.len() != self.queue_sizes.len() {
return Err(anyhow!(
"expected {} queues, got {}",
self.queue_sizes.len(),
queues.len()
));
}
let fs = self.fs.take().expect("missing file system implementation");
let use_dax = fs.cfg().use_dax;
let server = Arc::new(Server::new(fs));
let socket = self.tube.take().expect("missing mapping socket");
let mut slot = 0;
// Set up shared memory for DAX.
// TODO(b/176129399): Remove cfg! once DAX is supported on ARM.
if cfg!(target_arch = "x86_64") && use_dax {
// Create the shared memory region now before we start processing requests.
let request = FsMappingRequest::AllocateSharedMemoryRegion(
self.pci_bar.as_ref().cloned().expect("No pci_bar"),
);
socket
.send(&request)
.expect("failed to send allocation message");
slot = match socket.recv() {
Ok(VmResponse::RegisterMemory { pfn: _, slot }) => slot,
Ok(VmResponse::Err(e)) => panic!("failed to allocate shared memory region: {}", e),
r => panic!(
"unexpected response to allocate shared memory region: {:?}",
r
),
};
}
let socket = Arc::new(Mutex::new(socket));
let mut watch_resample_event = true;
self.workers = queues
.into_iter()
.map(|(idx, queue)| {
let server = server.clone();
let irq = interrupt.clone();
let socket = Arc::clone(&socket);
let worker =
WorkerThread::start(format!("v_fs:{}:{}", self.tag, idx), move |kill_evt| {
let mut worker = Worker::new(queue, server, irq, socket, slot);
worker.run(kill_evt, watch_resample_event)
});
if watch_resample_event {
watch_resample_event = false;
}
worker
})
.collect();
Ok(())
}
fn get_device_bars(&mut self, address: PciAddress) -> Vec<PciBarConfiguration> {
if self.fs.as_ref().map_or(false, |fs| !fs.cfg().use_dax) {
return vec![];
}
self.pci_bar = Some(Alloc::PciBar {
bus: address.bus,
dev: address.dev,
func: address.func,
bar: FS_BAR_NUM,
});
vec![PciBarConfiguration::new(
FS_BAR_NUM as usize,
FS_BAR_SIZE,
PciBarRegionType::Memory64BitRegion,
PciBarPrefetchable::Prefetchable,
)]
}
fn get_device_caps(&self) -> Vec<Box<dyn PciCapability>> {
if self.fs.as_ref().map_or(false, |fs| !fs.cfg().use_dax) {
return vec![];
}
vec![Box::new(VirtioPciShmCap::new(
PciCapabilityType::SharedMemoryConfig,
FS_BAR_NUM,
FS_BAR_OFFSET,
FS_BAR_SIZE,
VIRTIO_FS_SHMCAP_ID_CACHE as u8,
))]
}
}