summary refs log tree commit diff
diff options
context:
space:
mode:
-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))
+                }
+            }
         }
     }
 }