summary refs log tree commit diff
path: root/qcow_utils
diff options
context:
space:
mode:
authorDaniel Verkamp <dverkamp@chromium.org>2019-02-07 16:52:08 -0800
committerchrome-bot <chrome-bot@chromium.org>2019-02-14 05:27:21 -0800
commit348ccf1102895acf5a064d388ab8249c575ccafb (patch)
treec01f525690521b5b3a5f15dd01952db1ca0bc33f /qcow_utils
parentd39dd9af7172a4d7fdafaa37fa8569e02e0de556 (diff)
downloadcrosvm-348ccf1102895acf5a064d388ab8249c575ccafb.tar
crosvm-348ccf1102895acf5a064d388ab8249c575ccafb.tar.gz
crosvm-348ccf1102895acf5a064d388ab8249c575ccafb.tar.bz2
crosvm-348ccf1102895acf5a064d388ab8249c575ccafb.tar.lz
crosvm-348ccf1102895acf5a064d388ab8249c575ccafb.tar.xz
crosvm-348ccf1102895acf5a064d388ab8249c575ccafb.tar.zst
crosvm-348ccf1102895acf5a064d388ab8249c575ccafb.zip
qcow_utils: add disk image expand function
This exports a new C API to resize a disk image.  The new function is
intended to only expand (increase in size) to avoid accidentally
truncating user data due to bugs elsewhere.

BUG=chromium:858815
TEST=build_test.py

Change-Id: I6f834209aba693618e0f51d920e7b73d4f2a9dfc
Signed-off-by: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1464384
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'qcow_utils')
-rw-r--r--qcow_utils/src/qcow_utils.h4
-rw-r--r--qcow_utils/src/qcow_utils.rs65
2 files changed, 68 insertions, 1 deletions
diff --git a/qcow_utils/src/qcow_utils.h b/qcow_utils/src/qcow_utils.h
index 30c9715..90aa23b 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 faf7aed..534d886 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<D: FileSetLen + Seek> DiskFile for D {}
 
 #[no_mangle]
 pub unsafe extern "C" fn create_qcow_with_size(path: *const c_char, virtual_size: u64) -> c_int {
@@ -47,6 +53,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<DiskFile> = 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.
     // The caller retains ownership of the file descriptors.