blob: 332a6164461cc8f01a0367d65a407e732defc55e [file] [log] [blame]
// Copyright 2017 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::fmt::{self, Display};
use std::fs::File;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::thread;
use base::{error, warn, Event, PollContext, PollToken};
use vm_memory::GuestMemory;
use super::{Interrupt, Queue, VirtioDevice, Writer, TYPE_RNG};
const QUEUE_SIZE: u16 = 256;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
#[derive(Debug)]
pub enum RngError {
/// Can't access /dev/urandom
AccessingRandomDev(io::Error),
}
pub type Result<T> = std::result::Result<T, RngError>;
impl Display for RngError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::RngError::*;
match self {
AccessingRandomDev(e) => write!(f, "failed to access /dev/urandom: {}", e),
}
}
}
struct Worker {
interrupt: Interrupt,
queue: Queue,
mem: GuestMemory,
random_file: File,
}
impl Worker {
fn process_queue(&mut self) -> bool {
let queue = &mut self.queue;
let mut needs_interrupt = false;
while let Some(avail_desc) = queue.pop(&self.mem) {
let index = avail_desc.index;
let random_file = &mut self.random_file;
let written = match Writer::new(self.mem.clone(), avail_desc)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
.and_then(|mut writer| writer.write_from(random_file, std::usize::MAX))
{
Ok(n) => n,
Err(e) => {
warn!("Failed to write random data to the guest: {}", e);
0
}
};
queue.add_used(&self.mem, index, written as u32);
needs_interrupt = true;
}
needs_interrupt
}
fn run(&mut self, queue_evt: Event, kill_evt: Event) {
#[derive(PollToken)]
enum Token {
QueueAvailable,
InterruptResample,
Kill,
}
let poll_ctx: PollContext<Token> = match PollContext::build_with(&[
(&queue_evt, Token::QueueAvailable),
(self.interrupt.get_resample_evt(), Token::InterruptResample),
(&kill_evt, Token::Kill),
]) {
Ok(pc) => pc,
Err(e) => {
error!("failed creating PollContext: {}", e);
return;
}
};
'poll: loop {
let events = match poll_ctx.wait() {
Ok(v) => v,
Err(e) => {
error!("failed polling for events: {}", e);
break;
}
};
let mut needs_interrupt = false;
for event in events.iter_readable() {
match event.token() {
Token::QueueAvailable => {
if let Err(e) = queue_evt.read() {
error!("failed reading queue Event: {}", e);
break 'poll;
}
needs_interrupt |= self.process_queue();
}
Token::InterruptResample => {
self.interrupt.interrupt_resample();
}
Token::Kill => break 'poll,
}
}
if needs_interrupt {
self.interrupt.signal_used_queue(self.queue.vector);
}
}
}
}
/// Virtio device for exposing entropy to the guest OS through virtio.
pub struct Rng {
kill_evt: Option<Event>,
worker_thread: Option<thread::JoinHandle<Worker>>,
random_file: Option<File>,
virtio_features: u64,
}
impl Rng {
/// Create a new virtio rng device that gets random data from /dev/urandom.
pub fn new(virtio_features: u64) -> Result<Rng> {
let random_file = File::open("/dev/urandom").map_err(RngError::AccessingRandomDev)?;
Ok(Rng {
kill_evt: None,
worker_thread: None,
random_file: Some(random_file),
virtio_features,
})
}
}
impl Drop for Rng {
fn drop(&mut self) {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
}
if let Some(worker_thread) = self.worker_thread.take() {
let _ = worker_thread.join();
}
}
}
impl VirtioDevice for Rng {
fn keep_fds(&self) -> Vec<RawFd> {
let mut keep_fds = Vec::new();
if let Some(random_file) = &self.random_file {
keep_fds.push(random_file.as_raw_fd());
}
keep_fds
}
fn device_type(&self) -> u32 {
TYPE_RNG
}
fn queue_max_sizes(&self) -> &[u16] {
QUEUE_SIZES
}
fn features(&self) -> u64 {
self.virtio_features
}
fn activate(
&mut self,
mem: GuestMemory,
interrupt: Interrupt,
mut queues: Vec<Queue>,
mut queue_evts: Vec<Event>,
) {
if queues.len() != 1 || queue_evts.len() != 1 {
return;
}
let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
Err(e) => {
error!("failed to create kill Event pair: {}", e);
return;
}
};
self.kill_evt = Some(self_kill_evt);
let queue = queues.remove(0);
if let Some(random_file) = self.random_file.take() {
let worker_result =
thread::Builder::new()
.name("virtio_rng".to_string())
.spawn(move || {
let mut worker = Worker {
interrupt,
queue,
mem,
random_file,
};
worker.run(queue_evts.remove(0), kill_evt);
worker
});
match worker_result {
Err(e) => {
error!("failed to spawn virtio_rng worker: {}", e);
return;
}
Ok(join_handle) => {
self.worker_thread = Some(join_handle);
}
}
}
}
fn reset(&mut self) -> bool {
if let Some(kill_evt) = self.kill_evt.take() {
if kill_evt.write(1).is_err() {
error!("{}: failed to notify the kill event", self.debug_label());
return false;
}
}
if let Some(worker_thread) = self.worker_thread.take() {
match worker_thread.join() {
Err(_) => {
error!("{}: failed to get back resources", self.debug_label());
return false;
}
Ok(worker) => {
self.random_file = Some(worker.random_file);
return true;
}
}
}
false
}
}