diff options
-rw-r--r-- | devices/src/virtio/block.rs | 139 | ||||
-rw-r--r-- | src/linux.rs | 52 | ||||
-rw-r--r-- | src/main.rs | 65 | ||||
-rw-r--r-- | vm_control/src/lib.rs | 29 |
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)) + } + } } } } |