From 92f73d73d04bdc973fe89e2c1682a1c3cc03fe49 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Tue, 4 Dec 2018 13:17:46 -0800 Subject: devices: block: add resize VmControl request This allows manual resizing of block devices at runtime via the command line ('crosvm disk resize '). The virtio config interrupt is asserted when the disk size changes so that the guest driver can update the block device to the updated size. Currently, there is no automatic policy for resizing disks - that will be implemented in another change. Additionally, this resize operation just changes the size of the block device; the filesystem will need to be resized by the guest (e.g. via the 'btrfs filesystem resize' command) as a separate step either before (shrinking) or after (expanding) the disk resize operation. BUG=chromium:858815 TEST=Start crosvm with a control socket (-s) and resize the disk with 'crosvm disk resize' from another shell. Change-Id: I01633a7af04bfbaffbd27b9227274406d2a2b9cb Signed-off-by: Daniel Verkamp Reviewed-on: https://chromium-review.googlesource.com/1394152 Tested-by: kokoro Reviewed-by: Zach Reizner Reviewed-by: Dylan Reid --- devices/src/virtio/block.rs | 139 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 110 insertions(+), 29 deletions(-) (limited to 'devices/src') diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs index ae212c3..b022fa9 100644 --- a/devices/src/virtio/block.rs +++ b/devices/src/virtio/block.rs @@ -6,6 +6,7 @@ use std::cmp; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::mem::{size_of, size_of_val}; use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::net::UnixDatagram; use std::result; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -17,15 +18,17 @@ use sync::Mutex; use sys_util::Error as SysError; use sys_util::Result as SysResult; use sys_util::{ - EventFd, FileSync, GuestAddress, GuestMemory, GuestMemoryError, PollContext, PollToken, - PunchHole, TimerFd, WriteZeroes, + EventFd, FileSetLen, FileSync, GuestAddress, GuestMemory, GuestMemoryError, PollContext, + PollToken, PunchHole, TimerFd, WriteZeroes, }; use data_model::{DataInit, Le16, Le32, Le64}; +use msg_socket::{MsgReceiver, MsgSender, MsgSocket}; +use vm_control::{VmRequest, VmResponse}; use super::{ - DescriptorChain, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_BLOCK, - VIRTIO_F_VERSION_1, + DescriptorChain, Queue, VirtioDevice, INTERRUPT_STATUS_CONFIG_CHANGED, + INTERRUPT_STATUS_USED_RING, TYPE_BLOCK, VIRTIO_F_VERSION_1, }; const QUEUE_SIZE: u16 = 256; @@ -112,8 +115,8 @@ const VIRTIO_BLK_DISCARD_WRITE_ZEROES_FLAG_UNMAP: u32 = 1 << 0; // Safe because it only has data and has no implicit padding. unsafe impl DataInit for virtio_blk_discard_write_zeroes {} -pub trait DiskFile: FileSync + PunchHole + Read + Seek + Write + WriteZeroes {} -impl DiskFile for D {} +pub trait DiskFile: FileSetLen + FileSync + PunchHole + Read + Seek + Write + WriteZeroes {} +impl DiskFile for D {} #[derive(Copy, Clone, Debug, PartialEq)] enum RequestType { @@ -470,6 +473,7 @@ struct Worker { queues: Vec, mem: GuestMemory, disk_image: T, + disk_size: Arc>, read_only: bool, interrupt_status: Arc, interrupt_evt: EventFd, @@ -529,17 +533,44 @@ impl Worker { used_count > 0 } + fn resize(&mut self, new_size: u64) -> VmResponse { + if self.read_only { + error!("Attempted to resize read-only block device"); + return VmResponse::Err(SysError::new(libc::EROFS)); + } + + info!("Resizing block device to {} bytes", new_size); + + if let Err(e) = self.disk_image.set_len(new_size) { + error!("Resizing disk failed! {}", e); + return VmResponse::Err(SysError::new(libc::EIO)); + } + + if let Ok(new_disk_size) = self.disk_image.seek(SeekFrom::End(0)) { + let mut disk_size = self.disk_size.lock(); + *disk_size = new_disk_size; + } + VmResponse::Ok + } + fn signal_used_queue(&self) { self.interrupt_status .fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst); self.interrupt_evt.write(1).unwrap(); } - fn run(&mut self, queue_evt: EventFd, kill_evt: EventFd) { + fn signal_config_changed(&self) { + self.interrupt_status + .fetch_or(INTERRUPT_STATUS_CONFIG_CHANGED as usize, Ordering::SeqCst); + self.interrupt_evt.write(1).unwrap(); + } + + fn run(&mut self, queue_evt: EventFd, kill_evt: EventFd, control_socket: UnixDatagram) { #[derive(PollToken)] enum Token { FlushTimer, QueueAvailable, + ControlRequest, InterruptResample, Kill, } @@ -553,9 +584,12 @@ impl Worker { }; let mut flush_timer_armed = false; + let control_socket = MsgSocket::::new(control_socket); + let poll_ctx: PollContext = match PollContext::new() .and_then(|pc| pc.add(&flush_timer, Token::FlushTimer).and(Ok(pc))) .and_then(|pc| pc.add(&queue_evt, Token::QueueAvailable).and(Ok(pc))) + .and_then(|pc| pc.add(&control_socket, Token::ControlRequest).and(Ok(pc))) .and_then(|pc| { pc.add(&self.interrupt_resample_evt, Token::InterruptResample) .and(Ok(pc)) @@ -579,6 +613,7 @@ impl Worker { }; let mut needs_interrupt = false; + let mut needs_config_interrupt = false; for event in events.iter_readable() { match event.token() { Token::FlushTimer => { @@ -599,6 +634,35 @@ impl Worker { needs_interrupt |= self.process_queue(0, &mut flush_timer, &mut flush_timer_armed); } + Token::ControlRequest => { + let req = match control_socket.recv() { + Ok(req) => req, + Err(e) => { + error!("control socket failed recv: {:?}", e); + break 'poll; + } + }; + + let resp = match req { + VmRequest::DiskResize { + disk_index: _, + new_size, + } => { + needs_config_interrupt = true; + self.resize(new_size) + } + // Only DiskResize makes sense - fail any other requests + _ => { + error!("block device received unexpected VmRequest"); + VmResponse::Err(SysError::new(libc::EINVAL)) + } + }; + + if let Err(e) = control_socket.send(&resp) { + error!("control socket failed send: {:?}", e); + break 'poll; + } + } Token::InterruptResample => { let _ = self.interrupt_resample_evt.read(); if self.interrupt_status.load(Ordering::SeqCst) != 0 { @@ -611,6 +675,9 @@ impl Worker { if needs_interrupt { self.signal_used_queue(); } + if needs_config_interrupt { + self.signal_config_changed(); + } } } } @@ -622,6 +689,7 @@ pub struct Block { disk_size: Arc>, avail_features: u64, read_only: bool, + control_socket: Option, } fn build_config_space(disk_size: u64) -> virtio_blk_config { @@ -643,7 +711,11 @@ impl Block { /// Create a new virtio block device that operates on the given file. /// /// The given file must be seekable and sizable. - pub fn new(mut disk_image: T, read_only: bool) -> SysResult> { + pub fn new( + mut disk_image: T, + read_only: bool, + control_socket: Option, + ) -> SysResult> { let disk_size = disk_image.seek(SeekFrom::End(0))? as u64; if disk_size % SECTOR_SIZE != 0 { warn!( @@ -668,6 +740,7 @@ impl Block { disk_size: Arc::new(Mutex::new(disk_size)), avail_features, read_only, + control_socket, }) } } @@ -689,6 +762,10 @@ impl VirtioDevice for Block { keep_fds.push(disk_image.as_raw_fd()); } + if let Some(ref control_socket) = self.control_socket { + keep_fds.push(control_socket.as_raw_fd()); + } + keep_fds } @@ -746,26 +823,30 @@ impl VirtioDevice for Block { self.kill_evt = Some(self_kill_evt); let read_only = self.read_only; + let disk_size = self.disk_size.clone(); if let Some(disk_image) = self.disk_image.take() { - let worker_result = - thread::Builder::new() - .name("virtio_blk".to_string()) - .spawn(move || { - let mut worker = Worker { - queues, - mem, - disk_image, - read_only, - interrupt_status: status, - interrupt_evt, - interrupt_resample_evt, - }; - worker.run(queue_evts.remove(0), kill_evt); - }); + if let Some(control_socket) = self.control_socket.take() { + let worker_result = + thread::Builder::new() + .name("virtio_blk".to_string()) + .spawn(move || { + let mut worker = Worker { + queues, + mem, + disk_image, + disk_size, + read_only, + interrupt_status: status, + interrupt_evt, + interrupt_resample_evt, + }; + worker.run(queue_evts.remove(0), kill_evt, control_socket); + }); - if let Err(e) = worker_result { - error!("failed to spawn virtio_blk worker: {}", e); - return; + if let Err(e) = worker_result { + error!("failed to spawn virtio_blk worker: {}", e); + return; + } } } } @@ -787,7 +868,7 @@ mod tests { let f = File::create(&path).unwrap(); f.set_len(0x1000).unwrap(); - let b = Block::new(f, true).unwrap(); + let b = Block::new(f, true, None).unwrap(); let mut num_sectors = [0u8; 4]; b.read_config(0, &mut num_sectors); // size is 0x1000, so num_sectors is 8 (4096/512). @@ -807,7 +888,7 @@ mod tests { // read-write block device { let f = File::create(&path).unwrap(); - let b = Block::new(f, false).unwrap(); + let b = Block::new(f, false, None).unwrap(); // writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 assert_eq!(0x100006200, b.features()); @@ -816,7 +897,7 @@ mod tests { // read-only block device { let f = File::create(&path).unwrap(); - let b = Block::new(f, true).unwrap(); + let b = Block::new(f, true, None).unwrap(); // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO // + VIRTIO_F_VERSION_1 assert_eq!(0x100000220, b.features()); -- cgit 1.4.1