aboutsummaryrefslogtreecommitdiff
path: root/block
diff options
context:
space:
mode:
authorKevin Wolf <kwolf@redhat.com>2010-01-12 12:55:17 +0100
committerAnthony Liguori <aliguori@us.ibm.com>2010-01-13 17:14:15 -0600
commit756e6736a12a46330d9532d5f861ba15b38886d8 (patch)
treeb8b36409fef483b90fe9a79384aab38e3ea07dd2 /block
parentb783e409bf17b92f4af8dc5d6bd040d0092f33e0 (diff)
block: Add bdrv_change_backing_file
Introduce the functions needed to change the backing file of an image. The function is implemented for qcow2. Signed-off-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Diffstat (limited to 'block')
-rw-r--r--block/qcow2.c101
1 files changed, 101 insertions, 0 deletions
diff --git a/block/qcow2.c b/block/qcow2.c
index 984264b3ea..4f9ef91715 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -658,6 +658,105 @@ static void qcow_close(BlockDriverState *bs)
bdrv_delete(s->hd);
}
+/*
+ * Updates the variable length parts of the qcow2 header, i.e. the backing file
+ * name and all extensions. qcow2 was not designed to allow such changes, so if
+ * we run out of space (we can only use the first cluster) this function may
+ * fail.
+ *
+ * Returns 0 on success, -errno in error cases.
+ */
+static int qcow2_update_ext_header(BlockDriverState *bs,
+ const char *backing_file, const char *backing_fmt)
+{
+ size_t backing_file_len = 0;
+ size_t backing_fmt_len = 0;
+ BDRVQcowState *s = bs->opaque;
+ QCowExtension ext_backing_fmt = {0, 0};
+ int ret;
+
+ /* Backing file format doesn't make sense without a backing file */
+ if (backing_fmt && !backing_file) {
+ return -EINVAL;
+ }
+
+ /* Prepare the backing file format extension if needed */
+ if (backing_fmt) {
+ ext_backing_fmt.len = cpu_to_be32(strlen(backing_fmt));
+ ext_backing_fmt.magic = cpu_to_be32(QCOW_EXT_MAGIC_BACKING_FORMAT);
+ backing_fmt_len = ((sizeof(ext_backing_fmt)
+ + strlen(backing_fmt) + 7) & ~7);
+ }
+
+ /* Check if we can fit the new header into the first cluster */
+ if (backing_file) {
+ backing_file_len = strlen(backing_file);
+ }
+
+ size_t header_size = sizeof(QCowHeader) + backing_file_len
+ + backing_fmt_len;
+
+ if (header_size > s->cluster_size) {
+ return -ENOSPC;
+ }
+
+ /* Rewrite backing file name and qcow2 extensions */
+ size_t ext_size = header_size - sizeof(QCowHeader);
+ uint8_t buf[ext_size];
+ size_t offset = 0;
+ size_t backing_file_offset = 0;
+
+ if (backing_file) {
+ if (backing_fmt) {
+ int padding = backing_fmt_len -
+ (sizeof(ext_backing_fmt) + strlen(backing_fmt));
+
+ memcpy(buf + offset, &ext_backing_fmt, sizeof(ext_backing_fmt));
+ offset += sizeof(ext_backing_fmt);
+
+ memcpy(buf + offset, backing_fmt, strlen(backing_fmt));
+ offset += strlen(backing_fmt);
+
+ memset(buf + offset, 0, padding);
+ offset += padding;
+ }
+
+ memcpy(buf + offset, backing_file, backing_file_len);
+ backing_file_offset = sizeof(QCowHeader) + offset;
+ }
+
+ ret = bdrv_pwrite(s->hd, sizeof(QCowHeader), buf, ext_size);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ /* Update header fields */
+ uint64_t be_backing_file_offset = cpu_to_be64(backing_file_offset);
+ uint32_t be_backing_file_size = cpu_to_be32(backing_file_len);
+
+ ret = bdrv_pwrite(s->hd, offsetof(QCowHeader, backing_file_offset),
+ &be_backing_file_offset, sizeof(uint64_t));
+ if (ret < 0) {
+ goto fail;
+ }
+
+ ret = bdrv_pwrite(s->hd, offsetof(QCowHeader, backing_file_size),
+ &be_backing_file_size, sizeof(uint32_t));
+ if (ret < 0) {
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ return ret;
+}
+
+static int qcow2_change_backing_file(BlockDriverState *bs,
+ const char *backing_file, const char *backing_fmt)
+{
+ return qcow2_update_ext_header(bs, backing_file, backing_fmt);
+}
+
static int get_bits_from_size(size_t size)
{
int res = 0;
@@ -1137,6 +1236,8 @@ static BlockDriver bdrv_qcow2 = {
.bdrv_save_vmstate = qcow_save_vmstate,
.bdrv_load_vmstate = qcow_load_vmstate,
+ .bdrv_change_backing_file = qcow2_change_backing_file,
+
.create_options = qcow_create_options,
.bdrv_check = qcow_check,
};