blob: 2f4afbc019db634bc3643ecd2218109a73d8f6b7 [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::convert::TryFrom;
use std::convert::TryInto;
use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;
use base::error;
use base::syscall;
use base::Event;
use base::EventToken;
use base::Protection;
use base::SafeDescriptor;
use base::Tube;
use base::WaitContext;
use fuse::filesystem::FileSystem;
use fuse::filesystem::ZeroCopyReader;
use fuse::filesystem::ZeroCopyWriter;
use sync::Mutex;
use vm_control::FsMappingRequest;
use vm_control::VmResponse;
use crate::virtio::fs::Error;
use crate::virtio::fs::Result;
use crate::virtio::Interrupt;
use crate::virtio::Queue;
use crate::virtio::Reader;
use crate::virtio::Writer;
impl fuse::Reader for Reader {}
impl fuse::Writer for Writer {
type ClosureWriter = Self;
fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
where
F: Fn(&mut Self) -> io::Result<usize>,
{
let mut writer = Writer::split_at(self, offset);
f(&mut writer)
}
fn has_sufficient_buffer(&self, size: u32) -> bool {
self.available_bytes() >= size as usize
}
}
impl ZeroCopyReader for Reader {
fn read_to(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
self.read_to_at(f, count, off)
}
}
impl ZeroCopyWriter for Writer {
fn write_from(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
self.write_from_at(f, count, off)
}
}
struct Mapper {
tube: Arc<Mutex<Tube>>,
slot: u32,
}
impl Mapper {
fn new(tube: Arc<Mutex<Tube>>, slot: u32) -> Self {
Self { tube, slot }
}
fn process_request(&self, request: &FsMappingRequest) -> io::Result<()> {
let tube = self.tube.lock();
tube.send(request).map_err(|e| {
error!("failed to send request {:?}: {}", request, e);
io::Error::from_raw_os_error(libc::EINVAL)
})?;
match tube.recv() {
Ok(VmResponse::Ok) => Ok(()),
Ok(VmResponse::Err(e)) => Err(e.into()),
r => {
error!("failed to process {:?}: {:?}", request, r);
Err(io::Error::from_raw_os_error(libc::EIO))
}
}
}
}
impl fuse::Mapper for Mapper {
fn map(
&self,
mem_offset: u64,
size: usize,
fd: &dyn AsRawFd,
file_offset: u64,
prot: Protection,
) -> io::Result<()> {
let mem_offset: usize = mem_offset.try_into().map_err(|e| {
error!("mem_offset {} is too big: {}", mem_offset, e);
io::Error::from_raw_os_error(libc::EINVAL)
})?;
let fd = SafeDescriptor::try_from(fd)?;
let request = FsMappingRequest::CreateMemoryMapping {
slot: self.slot,
fd,
size,
file_offset,
prot,
mem_offset,
};
self.process_request(&request)
}
fn unmap(&self, offset: u64, size: u64) -> io::Result<()> {
let offset: usize = offset.try_into().map_err(|e| {
error!("offset {} is too big: {}", offset, e);
io::Error::from_raw_os_error(libc::EINVAL)
})?;
let size: usize = size.try_into().map_err(|e| {
error!("size {} is too big: {}", size, e);
io::Error::from_raw_os_error(libc::EINVAL)
})?;
let request = FsMappingRequest::RemoveMemoryMapping {
slot: self.slot,
offset,
size,
};
self.process_request(&request)
}
}
pub struct Worker<F: FileSystem + Sync> {
queue: Queue,
server: Arc<fuse::Server<F>>,
irq: Interrupt,
tube: Arc<Mutex<Tube>>,
slot: u32,
}
pub fn process_fs_queue<F: FileSystem + Sync>(
interrupt: &Interrupt,
queue: &mut Queue,
server: &Arc<fuse::Server<F>>,
tube: &Arc<Mutex<Tube>>,
slot: u32,
) -> Result<()> {
let mapper = Mapper::new(Arc::clone(tube), slot);
while let Some(mut avail_desc) = queue.pop() {
let total =
server.handle_message(&mut avail_desc.reader, &mut avail_desc.writer, &mapper)?;
queue.add_used(avail_desc, total as u32);
queue.trigger_interrupt(interrupt);
}
Ok(())
}
impl<F: FileSystem + Sync> Worker<F> {
pub fn new(
queue: Queue,
server: Arc<fuse::Server<F>>,
irq: Interrupt,
tube: Arc<Mutex<Tube>>,
slot: u32,
) -> Worker<F> {
Worker {
queue,
server,
irq,
tube,
slot,
}
}
pub fn run(&mut self, kill_evt: Event, watch_resample_event: bool) -> Result<()> {
let mut ruid: libc::uid_t = 0;
let mut euid: libc::uid_t = 0;
let mut suid: libc::uid_t = 0;
// SAFETY: Safe because this doesn't modify any memory and we check the return value.
syscall!(unsafe { libc::getresuid(&mut ruid, &mut euid, &mut suid) })
.map_err(Error::GetResuid)?;
// Only need to set SECBIT_NO_SETUID_FIXUP for threads which could change uid.
if ruid == 0 || ruid != euid || ruid != suid {
// We need to set the no setuid fixup secure bit so that we don't drop capabilities when
// changing the thread uid/gid. Without this, creating new entries can fail in some
// corner cases.
const SECBIT_NO_SETUID_FIXUP: i32 = 1 << 2;
let mut securebits = syscall!(
// SAFETY:
// Safe because this doesn't modify any memory and we check the return value.
unsafe { libc::prctl(libc::PR_GET_SECUREBITS) }
)
.map_err(Error::GetSecurebits)?;
securebits |= SECBIT_NO_SETUID_FIXUP;
syscall!(
// SAFETY:
// Safe because this doesn't modify any memory and we check the return value.
unsafe { libc::prctl(libc::PR_SET_SECUREBITS, securebits) }
)
.map_err(Error::SetSecurebits)?;
}
// To avoid extra locking, unshare filesystem attributes from parent. This includes the
// current working directory and umask.
syscall!(
// SAFETY: Safe because this doesn't modify any memory and we check the return value.
unsafe { libc::unshare(libc::CLONE_FS) }
)
.map_err(Error::UnshareFromParent)?;
#[derive(EventToken)]
enum Token {
// A request is ready on the queue.
QueueReady,
// Check if any interrupts need to be re-asserted.
InterruptResample,
// The parent thread requested an exit.
Kill,
}
let wait_ctx = WaitContext::build_with(&[
(self.queue.event(), Token::QueueReady),
(&kill_evt, Token::Kill),
])
.map_err(Error::CreateWaitContext)?;
if watch_resample_event {
if let Some(resample_evt) = self.irq.get_resample_evt() {
wait_ctx
.add(resample_evt, Token::InterruptResample)
.map_err(Error::CreateWaitContext)?;
}
}
loop {
let events = wait_ctx.wait().map_err(Error::WaitError)?;
for event in events.iter().filter(|e| e.is_readable) {
match event.token {
Token::QueueReady => {
self.queue.event().wait().map_err(Error::ReadQueueEvent)?;
if let Err(e) = process_fs_queue(
&self.irq,
&mut self.queue,
&self.server,
&self.tube,
self.slot,
) {
error!("virtio-fs transport error: {}", e);
return Err(e);
}
}
Token::InterruptResample => {
self.irq.interrupt_resample();
}
Token::Kill => return Ok(()),
}
}
}
}
}