summary refs log tree commit diff
diff options
context:
space:
mode:
authorDaniel Verkamp <dverkamp@chromium.org>2018-12-04 13:17:46 -0800
committerchrome-bot <chrome-bot@chromium.org>2019-01-16 01:46:54 -0800
commit92f73d73d04bdc973fe89e2c1682a1c3cc03fe49 (patch)
tree8b9b52835951bb982ad3e5cc240b91f0dcacea3f
parent53429509833a8f1a227d977778069df93a4edd44 (diff)
downloadcrosvm-92f73d73d04bdc973fe89e2c1682a1c3cc03fe49.tar
crosvm-92f73d73d04bdc973fe89e2c1682a1c3cc03fe49.tar.gz
crosvm-92f73d73d04bdc973fe89e2c1682a1c3cc03fe49.tar.bz2
crosvm-92f73d73d04bdc973fe89e2c1682a1c3cc03fe49.tar.lz
crosvm-92f73d73d04bdc973fe89e2c1682a1c3cc03fe49.tar.xz
crosvm-92f73d73d04bdc973fe89e2c1682a1c3cc03fe49.tar.zst
crosvm-92f73d73d04bdc973fe89e2c1682a1c3cc03fe49.zip
devices: block: add resize VmControl request
This allows manual resizing of block devices at runtime via the command
line ('crosvm disk resize <index> <size>').  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 <dverkamp@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1394152
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
-rw-r--r--devices/src/virtio/block.rs139
-rw-r--r--src/linux.rs52
-rw-r--r--src/main.rs65
-rw-r--r--vm_control/src/lib.rs29
4 files changed, 248 insertions, 37 deletions
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<D: FileSync + PunchHole + Read + Seek + Write + WriteZeroes> DiskFile for D {}
+pub trait DiskFile: FileSetLen + FileSync + PunchHole + Read + Seek + Write + WriteZeroes {}
+impl<D: FileSetLen + FileSync + PunchHole + Read + Seek + Write + WriteZeroes> DiskFile for D {}
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 enum RequestType {
@@ -470,6 +473,7 @@ struct Worker<T: DiskFile> {
     queues: Vec<Queue>,
     mem: GuestMemory,
     disk_image: T,
+    disk_size: Arc<Mutex<u64>>,
     read_only: bool,
     interrupt_status: Arc<AtomicUsize>,
     interrupt_evt: EventFd,
@@ -529,17 +533,44 @@ impl<T: DiskFile> Worker<T> {
         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<T: DiskFile> Worker<T> {
         };
         let mut flush_timer_armed = false;
 
+        let control_socket = MsgSocket::<VmResponse, VmRequest>::new(control_socket);
+
         let poll_ctx: PollContext<Token> = 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<T: DiskFile> Worker<T> {
             };
 
             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<T: DiskFile> Worker<T> {
                         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<T: DiskFile> Worker<T> {
             if needs_interrupt {
                 self.signal_used_queue();
             }
+            if needs_config_interrupt {
+                self.signal_config_changed();
+            }
         }
     }
 }
@@ -622,6 +689,7 @@ pub struct Block<T: DiskFile> {
     disk_size: Arc<Mutex<u64>>,
     avail_features: u64,
     read_only: bool,
+    control_socket: Option<UnixDatagram>,
 }
 
 fn build_config_space(disk_size: u64) -> virtio_blk_config {
@@ -643,7 +711,11 @@ impl<T: DiskFile> Block<T> {
     /// 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<Block<T>> {
+    pub fn new(
+        mut disk_image: T,
+        read_only: bool,
+        control_socket: Option<UnixDatagram>,
+    ) -> SysResult<Block<T>> {
         let disk_size = disk_image.seek(SeekFrom::End(0))? as u64;
         if disk_size % SECTOR_SIZE != 0 {
             warn!(
@@ -668,6 +740,7 @@ impl<T: DiskFile> Block<T> {
             disk_size: Arc::new(Mutex::new(disk_size)),
             avail_features,
             read_only,
+            control_socket,
         })
     }
 }
@@ -689,6 +762,10 @@ impl<T: 'static + AsRawFd + DiskFile + Send> VirtioDevice for Block<T> {
             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<T: 'static + AsRawFd + DiskFile + Send> VirtioDevice for Block<T> {
         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());
diff --git a/src/linux.rs b/src/linux.rs
index 35bb98f..1bca0bf 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -28,7 +28,7 @@ use byteorder::{ByteOrder, LittleEndian};
 use devices::{self, PciDevice, VirtioPciDevice};
 use io_jail::{self, Minijail};
 use kvm::*;
-use msg_socket::{MsgReceiver, MsgSender, UnlinkMsgSocket};
+use msg_socket::{MsgReceiver, MsgSender, MsgSocket, UnlinkMsgSocket};
 use net_util::{Error as NetError, Tap};
 use qcow::{self, ImageType, QcowFile};
 use sys_util;
@@ -211,6 +211,7 @@ fn create_virtio_devs(
     _exit_evt: &EventFd,
     wayland_device_socket: UnixDatagram,
     balloon_device_socket: UnixDatagram,
+    disk_device_sockets: &mut Vec<UnixDatagram>,
 ) -> std::result::Result<Vec<(Box<PciDevice + 'static>, Option<Minijail>)>, Box<error::Error>> {
     static DEFAULT_PIVOT_ROOT: &str = "/var/empty";
 
@@ -223,6 +224,8 @@ fn create_virtio_devs(
     }
 
     for disk in &cfg.disks {
+        let disk_device_socket = disk_device_sockets.remove(0);
+
         // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
         let mut raw_image: File = if disk.path.parent() == Some(Path::new("/proc/self/fd")) {
             if !disk.path.is_file() {
@@ -256,16 +259,24 @@ fn create_virtio_devs(
             ImageType::Raw => {
                 // Access as a raw block device.
                 Box::new(
-                    devices::virtio::Block::new(raw_image, disk.read_only)
-                        .map_err(Error::BlockDeviceNew)?,
+                    devices::virtio::Block::new(
+                        raw_image,
+                        disk.read_only,
+                        Some(disk_device_socket),
+                    )
+                    .map_err(Error::BlockDeviceNew)?,
                 )
             }
             ImageType::Qcow2 => {
                 // Valid qcow header present
                 let qcow_image = QcowFile::from(raw_image).map_err(Error::QcowDeviceCreate)?;
                 Box::new(
-                    devices::virtio::Block::new(qcow_image, disk.read_only)
-                        .map_err(Error::BlockDeviceNew)?,
+                    devices::virtio::Block::new(
+                        qcow_image,
+                        disk.read_only,
+                        Some(disk_device_socket),
+                    )
+                    .map_err(Error::BlockDeviceNew)?,
                 )
             }
         };
@@ -780,17 +791,43 @@ pub fn run_config(cfg: Config) -> Result<()> {
     let (balloon_host_socket, balloon_device_socket) =
         UnixDatagram::pair().map_err(Error::CreateSocket)?;
 
+    // Create one control socket per disk.
+    let mut disk_device_sockets = Vec::new();
+    let mut disk_host_sockets = Vec::new();
+    let disk_count = cfg.disks.len();
+    for _ in 0..disk_count {
+        let (disk_host_socket, disk_device_socket) =
+            UnixDatagram::pair().map_err(Error::CreateSocket)?;
+        disk_device_sockets.push(disk_device_socket);
+        let disk_host_socket = MsgSocket::<VmRequest, VmResponse>::new(disk_host_socket);
+        disk_host_sockets.push(disk_host_socket);
+    }
+
     let linux = Arch::build_vm(components, |m, e| {
-        create_virtio_devs(cfg, m, e, wayland_device_socket, balloon_device_socket)
+        create_virtio_devs(
+            cfg,
+            m,
+            e,
+            wayland_device_socket,
+            balloon_device_socket,
+            &mut disk_device_sockets,
+        )
     })
     .map_err(Error::BuildingVm)?;
-    run_control(linux, control_sockets, balloon_host_socket, sigchld_fd)
+    run_control(
+        linux,
+        control_sockets,
+        balloon_host_socket,
+        &disk_host_sockets,
+        sigchld_fd,
+    )
 }
 
 fn run_control(
     mut linux: RunnableLinuxVm,
     control_sockets: Vec<UnlinkMsgSocket<VmResponse, VmRequest>>,
     balloon_host_socket: UnixDatagram,
+    disk_host_sockets: &[MsgSocket<VmRequest, VmResponse>],
     sigchld_fd: SignalFd,
 ) -> Result<()> {
     // Paths to get the currently available memory and the low memory threshold.
@@ -1033,6 +1070,7 @@ fn run_control(
                                     &mut linux.resources,
                                     &mut running,
                                     &balloon_host_socket,
+                                    disk_host_sockets,
                                 );
                                 if let Err(e) = socket.send(&response) {
                                     error!("failed to send VmResponse: {:?}", e);
diff --git a/src/main.rs b/src/main.rs
index 79d0432..9cb7b3c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -691,12 +691,76 @@ fn create_qcow2(mut args: std::env::Args) -> std::result::Result<(), ()> {
     Ok(())
 }
 
+fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
+    if args.len() < 2 {
+        print_help("crosvm disk", "SUBCOMMAND VM_SOCKET...", &[]);
+        println!("Manage attached virtual disk devices.");
+        println!("Subcommands:");
+        println!("  resize DISK_INDEX NEW_SIZE VM_SOCKET");
+    }
+    let subcommand: &str = &args.nth(0).unwrap();
+
+    let request = match subcommand {
+        "resize" => {
+            let disk_index = match args.nth(0).unwrap().parse::<usize>() {
+                Ok(n) => n,
+                Err(_) => {
+                    error!("Failed to parse disk index");
+                    return Err(());
+                }
+            };
+
+            let new_size = match args.nth(0).unwrap().parse::<u64>() {
+                Ok(n) => n,
+                Err(_) => {
+                    error!("Failed to parse disk size");
+                    return Err(());
+                }
+            };
+
+            VmRequest::DiskResize {
+                disk_index,
+                new_size,
+            }
+        }
+        _ => {
+            error!("Unknown disk subcommand '{}'", subcommand);
+            return Err(());
+        }
+    };
+
+    let mut return_result = Ok(());
+    for socket_path in args {
+        match UnixDatagram::unbound().and_then(|s| {
+            s.connect(&socket_path)?;
+            Ok(s)
+        }) {
+            Ok(s) => {
+                let sender = Sender::<VmRequest>::new(s);
+                if let Err(e) = sender.send(&request) {
+                    error!(
+                        "failed to send disk request to socket at '{}': {:?}",
+                        socket_path, e
+                    );
+                }
+            }
+            Err(e) => {
+                error!("failed to connect to socket at '{}': {}", socket_path, e);
+                return_result = Err(());
+            }
+        }
+    }
+
+    return_result
+}
+
 fn print_usage() {
     print_help("crosvm", "[stop|run]", &[]);
     println!("Commands:");
     println!("    stop - Stops crosvm instances via their control sockets.");
     println!("    run  - Start a new crosvm instance.");
     println!("    create_qcow2  - Create a new qcow2 disk image file.");
+    println!("    disk - Manage attached virtual disk devices.")
 }
 
 fn crosvm_main() -> std::result::Result<(), ()> {
@@ -721,6 +785,7 @@ fn crosvm_main() -> std::result::Result<(), ()> {
         Some("run") => run_vm(args),
         Some("balloon") => balloon_vms(args),
         Some("create_qcow2") => create_qcow2(args),
+        Some("disk") => disk_cmd(args),
         Some(c) => {
             println!("invalid subcommand: {:?}", c);
             print_usage();
diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs
index 1c28994..e2fe995 100644
--- a/vm_control/src/lib.rs
+++ b/vm_control/src/lib.rs
@@ -15,6 +15,7 @@ extern crate kvm;
 extern crate libc;
 extern crate msg_socket;
 extern crate resources;
+#[macro_use]
 extern crate sys_util;
 
 use std::fs::File;
@@ -26,7 +27,7 @@ use libc::{EINVAL, ENODEV};
 
 use byteorder::{LittleEndian, WriteBytesExt};
 use kvm::{Datamatch, IoeventAddress, Vm};
-use msg_socket::{MsgOnSocket, MsgResult};
+use msg_socket::{MsgOnSocket, MsgReceiver, MsgResult, MsgSender, MsgSocket};
 use resources::{GpuMemoryDesc, SystemAllocator};
 use sys_util::{Error as SysError, EventFd, GuestAddress, MemoryMapping, MmapError, Result};
 
@@ -91,6 +92,9 @@ pub enum VmRequest {
         height: u32,
         format: u32,
     },
+    /// Resize a disk chosen by `disk_index` to `new_size` in bytes.
+    /// `disk_index` is a 0-based count of `--disk`, `--rwdisk`, and `-r` command-line options.
+    DiskResize { disk_index: usize, new_size: u64 },
 }
 
 fn register_memory(
@@ -133,6 +137,7 @@ impl VmRequest {
         sys_allocator: &mut SystemAllocator,
         running: &mut bool,
         balloon_host_socket: &UnixDatagram,
+        disk_host_sockets: &[MsgSocket<VmRequest, VmResponse>],
     ) -> VmResponse {
         *running = true;
         match *self {
@@ -199,6 +204,28 @@ impl VmRequest {
                     Err(e) => VmResponse::Err(e),
                 }
             }
+            VmRequest::DiskResize {
+                disk_index,
+                new_size: _,
+            } => {
+                // Forward the request to the block device process via its control socket.
+                if let Some(sock) = disk_host_sockets.get(disk_index) {
+                    if let Err(e) = sock.send(self) {
+                        error!("disk socket send failed: {:?}", e);
+                        VmResponse::Err(SysError::new(EINVAL))
+                    } else {
+                        match sock.recv() {
+                            Ok(result) => result,
+                            Err(e) => {
+                                error!("disk socket recv failed: {:?}", e);
+                                VmResponse::Err(SysError::new(EINVAL))
+                            }
+                        }
+                    }
+                } else {
+                    VmResponse::Err(SysError::new(ENODEV))
+                }
+            }
         }
     }
 }