diff --git a/qcow_utils/src/qcow_utils.h b/qcow_utils/src/qcow_utils.h index 30c971565d..90aa23baf0 100644 --- a/qcow_utils/src/qcow_utils.h +++ b/qcow_utils/src/qcow_utils.h @@ -13,6 +13,10 @@ extern "C" { // Create a basic, empty qcow2 file that can grow to `virtual_size` at `path`. int create_qcow_with_size(const char *path, uint64_t virtual_size); +// Attempt to resize the disk image at `path` to `virtual_size` bytes if +// the disk image is currently smaller than the requested size. +int expand_disk_image(const char *path, uint64_t virtual_size); + // Copy the source disk image from `src_fd` into `dst_fd` as a qcow2 image file. // Returns 0 on success or a negated errno value on failure. int convert_to_qcow2(int src_fd, int dst_fd); diff --git a/qcow_utils/src/qcow_utils.rs b/qcow_utils/src/qcow_utils.rs index faf7aed14b..534d8864c2 100644 --- a/qcow_utils/src/qcow_utils.rs +++ b/qcow_utils/src/qcow_utils.rs @@ -6,16 +6,22 @@ extern crate libc; extern crate qcow; +extern crate sys_util; -use libc::{EBADFD, EINVAL, EIO}; +use libc::{EBADFD, EINVAL, EIO, ENOSYS}; use std::ffi::CStr; use std::fs::{File, OpenOptions}; +use std::io::{Seek, SeekFrom}; use std::mem::forget; use std::os::raw::{c_char, c_int}; use std::os::unix::io::FromRawFd; use std::panic::catch_unwind; use qcow::{ImageType, QcowFile}; +use sys_util::{flock, FileSetLen, FlockOperation}; + +trait DiskFile: FileSetLen + Seek {} +impl DiskFile for D {} #[no_mangle] pub unsafe extern "C" fn create_qcow_with_size(path: *const c_char, virtual_size: u64) -> c_int { @@ -46,6 +52,63 @@ pub unsafe extern "C" fn create_qcow_with_size(path: *const c_char, virtual_size } } +#[no_mangle] +pub unsafe extern "C" fn expand_disk_image(path: *const c_char, virtual_size: u64) -> c_int { + // NULL pointers are checked, but this will access any other invalid pointer passed from C + // code. It's the caller's responsibility to pass a valid pointer. + if path.is_null() { + return -EINVAL; + } + let c_str = CStr::from_ptr(path); + let file_path = match c_str.to_str() { + Ok(s) => s, + Err(_) => return -EINVAL, + }; + + let raw_image = match OpenOptions::new().read(true).write(true).open(file_path) { + Ok(f) => f, + Err(_) => return -EIO, + }; + + // Lock the disk image to prevent other processes from using it. + if let Err(_) = flock(&raw_image, FlockOperation::LockExclusive, true) { + return -EIO; + } + + let image_type = match qcow::detect_image_type(&raw_image) { + Ok(t) => t, + Err(_) => return -EINVAL, + }; + + let mut disk_image: Box = match image_type { + ImageType::Raw => Box::new(raw_image), + ImageType::Qcow2 => match QcowFile::from(raw_image) { + Ok(f) => Box::new(f), + Err(_) => return -EINVAL, + }, + }; + + // For safety against accidentally shrinking the disk image due to a + // programming error elsewhere, verify that the new size is larger than + // the current size. This is safe against races due to the exclusive + // flock() taken above - this is only an advisory lock, but it is also + // acquired by other instances of this function as well as crosvm + // itself when running a VM, so this should be safe in all cases that + // can access a disk image in normal operation. + let current_size = match disk_image.seek(SeekFrom::End(0)) { + Ok(len) => len, + Err(_) => return -EIO, + }; + if current_size >= virtual_size { + return 0; + } + + match disk_image.set_len(virtual_size) { + Ok(_) => 0, + Err(_) => -ENOSYS, + } +} + #[no_mangle] pub unsafe extern "C" fn convert_to_qcow2(src_fd: c_int, dst_fd: c_int) -> c_int { // The caller is responsible for passing valid file descriptors.