// 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; #[cfg(feature = "arc_quota")] mod arc_ioctl; 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::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 for Error { fn from(err: fuse::Error) -> Error { Error::FuseError(err) } } pub type Result = ::std::result::Result; pub struct Fs { cfg: virtio_fs_config, tag: String, fs: Option, queue_sizes: Box<[u16]>, avail_features: u64, acked_features: u64, pci_bar: Option, tube: Option, workers: Vec>>, } impl Fs { pub fn new( base_features: u64, tag: &str, num_workers: usize, fs_cfg: Config, tube: Tube, ) -> Result { 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 { 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, ) -> 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 { 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 { 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> { 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, ))] } }