summary refs log blame commit diff
path: root/devices/src/virtio/fs/mod.rs
blob: 9cac98b389df8607bb6ba595649e4b3236cf09e4 (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                                         



                                 

                                                                     






                                                                                              

                





























































                                                                                         

                                                         































                                                                                                    




                                                                                                











































































                                                                                          


























                                                                                
                             









                                                                                                 
                                      
 
                                            


















                                                                                               
                                                                   

                   



                                             
















                                                                          
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::ffi::FromBytesWithNulError;
use std::fmt;
use std::io;
use std::mem;
use std::os::unix::io::RawFd;
use std::sync::Arc;
use std::thread;

use data_model::{DataInit, Le32};
use sys_util::{error, warn, Error as SysError, EventFd, GuestMemory};

use crate::virtio::{
    copy_config, DescriptorError, Interrupt, Queue, VirtioDevice, TYPE_FS, VIRTIO_F_VERSION_1,
};

mod filesystem;
#[allow(dead_code)]
mod fuse;
#[cfg(fuzzing)]
pub mod fuzzing;
mod multikey;
pub mod passthrough;
mod server;
mod worker;

use passthrough::PassthroughFs;
use server::Server;
use worker::Worker;

// The fs device does not have a fixed number of queues.
const QUEUE_SIZE: u16 = 1024;

/// The maximum allowable length of the tag used to identify a specific virtio-fs device.
pub const FS_MAX_TAG_LEN: usize = 36;

/// kernel/include/uapi/linux/virtio_fs.h
#[repr(C, packed)]
#[derive(Clone, Copy)]
struct Config {
    /// Filesystem name (UTF-8, not NUL-terminated, padded with NULs)
    tag: [u8; FS_MAX_TAG_LEN],
    /// Number of request queues
    num_queues: Le32,
}

// Safe because all members are plain old data and any value is valid.
unsafe impl DataInit for Config {}

/// Errors that may occur during the creation or operation of an Fs device.
#[derive(Debug)]
pub enum Error {
    /// The tag for the Fs device was too long to fit in the config space.
    TagTooLong(usize),
    /// Failed to create the file system.
    CreateFs(io::Error),
    /// Creating PollContext failed.
    CreatePollContext(SysError),
    /// Error while polling for events.
    PollError(SysError),
    /// Error while reading from the virtio queue's EventFd.
    ReadQueueEventFd(SysError),
    /// A request is missing readable descriptors.
    NoReadableDescriptors,
    /// A request is missing writable descriptors.
    NoWritableDescriptors,
    /// Failed to signal the virio used queue.
    SignalUsedQueue(SysError),
    /// Failed to decode protocol messages.
    DecodeMessage(io::Error),
    /// Failed to encode protocol messages.
    EncodeMessage(io::Error),
    /// One or more parameters are missing.
    MissingParameter,
    /// A C string parameter is invalid.
    InvalidCString(FromBytesWithNulError),
    /// The `len` field of the header is too small.
    InvalidHeaderLength,
    /// A `DescriptorChain` contains invalid data.
    InvalidDescriptorChain(DescriptorError),
    /// The `size` field of the `SetxattrIn` message does not match the length
    /// of the decoded value.
    InvalidXattrSize((u32, usize)),
    /// Requested too many `iovec`s for an `ioctl` retry.
    TooManyIovecs((usize, usize)),
}

impl ::std::error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use Error::*;
        match self {
            TagTooLong(len) => write!(
                f,
                "Fs device tag is too long: len = {}, max = {}",
                len, FS_MAX_TAG_LEN
            ),
            CreateFs(err) => write!(f, "failed to create file system: {}", err),
            CreatePollContext(err) => write!(f, "failed to create PollContext: {}", err),
            PollError(err) => write!(f, "failed to poll events: {}", err),
            ReadQueueEventFd(err) => write!(f, "failed to read from virtio queue EventFd: {}", err),
            NoReadableDescriptors => write!(f, "request does not have any readable descriptors"),
            NoWritableDescriptors => write!(f, "request does not have any writable descriptors"),
            SignalUsedQueue(err) => write!(f, "failed to signal used queue: {}", err),
            DecodeMessage(err) => write!(f, "failed to decode fuse message: {}", err),
            EncodeMessage(err) => write!(f, "failed to encode fuse message: {}", err),
            MissingParameter => write!(f, "one or more parameters are missing"),
            InvalidHeaderLength => write!(f, "the `len` field of the header is too small"),
            InvalidCString(err) => write!(f, "a c string parameter is invalid: {}", err),
            InvalidDescriptorChain(err) => write!(f, "DescriptorChain is invalid: {}", err),
            InvalidXattrSize((size, len)) => write!(
                f,
                "The `size` field of the `SetxattrIn` message does not match the length of the\
                 decoded value: size = {}, value.len() = {}",
                size, len
            ),
            TooManyIovecs((count, max)) => write!(
                f,
                "requested too many `iovec`s for an `ioctl` retry reply: requested {}, max: {}",
                count, max
            ),
        }
    }
}

pub type Result<T> = ::std::result::Result<T, Error>;

pub struct Fs {
    cfg: Config,
    fs: Option<PassthroughFs>,
    queue_sizes: Box<[u16]>,
    avail_features: u64,
    acked_features: u64,
    workers: Vec<(EventFd, thread::JoinHandle<Result<()>>)>,
}

impl Fs {
    pub fn new(tag: &str, num_workers: usize, fs_cfg: passthrough::Config) -> 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 = Config {
            tag: cfg_tag,
            num_queues: Le32::from(num_workers as u32),
        };

        let fs = PassthroughFs::new(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,
            fs: Some(fs),
            queue_sizes: vec![QUEUE_SIZE; num_queues].into_boxed_slice(),
            avail_features: 1 << VIRTIO_F_VERSION_1,
            acked_features: 0,
            workers: Vec::with_capacity(num_workers + 1),
        })
    }

    fn stop_workers(&mut self) {
        for (kill_evt, handle) in mem::replace(&mut self.workers, Vec::new()) {
            if let Err(e) = kill_evt.write(1) {
                error!("failed to kill virtio-fs worker thread: {}", e);
                continue;
            }

            // Only wait on the child thread if we were able to send it a kill event.
            match handle.join() {
                Ok(r) => {
                    if let Err(e) = r {
                        error!("virtio-fs worker thread exited with error: {}", e)
                    }
                }
                Err(e) => error!("virtio-fs worker thread panicked: {:?}", e),
            }
        }
    }
}

impl VirtioDevice for Fs {
    fn keep_fds(&self) -> Vec<RawFd> {
        self.fs
            .as_ref()
            .map(PassthroughFs::keep_fds)
            .unwrap_or_else(Vec::new)
    }

    fn device_type(&self) -> u32 {
        TYPE_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_slice(), offset)
    }

    fn activate(
        &mut self,
        guest_mem: GuestMemory,
        interrupt: Interrupt,
        queues: Vec<Queue>,
        queue_evts: Vec<EventFd>,
    ) {
        if queues.len() != self.queue_sizes.len() || queue_evts.len() != self.queue_sizes.len() {
            return;
        }

        let fs = self.fs.take().expect("missing file system implementation");

        let server = Arc::new(Server::new(fs));
        let irq = Arc::new(interrupt);

        let mut watch_resample_event = true;
        for (idx, (queue, evt)) in queues.into_iter().zip(queue_evts.into_iter()).enumerate() {
            let (self_kill_evt, kill_evt) =
                match EventFd::new().and_then(|e| Ok((e.try_clone()?, e))) {
                    Ok(v) => v,
                    Err(e) => {
                        error!("fs: failed creating kill EventFd pair: {}", e);
                        self.stop_workers();
                        return;
                    }
                };

            let mem = guest_mem.clone();
            let server = server.clone();
            let irq = irq.clone();

            let worker_result = thread::Builder::new()
                .name(format!("virtio-fs worker {}", idx))
                .spawn(move || {
                    let mut worker = Worker::new(mem, queue, server, irq);
                    worker.run(evt, kill_evt, watch_resample_event)
                });

            if watch_resample_event {
                watch_resample_event = false;
            }

            match worker_result {
                Ok(worker) => self.workers.push((self_kill_evt, worker)),
                Err(e) => {
                    error!("fs: failed to spawn virtio_fs worker: {}", e);
                    self.stop_workers();
                    return;
                }
            }
        }
    }
}

impl Drop for Fs {
    fn drop(&mut self) {
        self.stop_workers()
    }
}