aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Wolf <kwolf@redhat.com>2012-02-02 12:32:31 +0100
committerKevin Wolf <kwolf@redhat.com>2012-02-09 16:17:51 +0100
commite24e49e6194626e4ec9f1aecce6d6a6847320bce (patch)
tree8e9ee8ff36e8056231c5a9750d903f1ff3a73768
parentecd880d9eeb59a0148d4761698448245ec4306e0 (diff)
qcow2: Update whole header at once
In order to switch the backing file, qcow2 issues multiple write requests that only changed a part of the image header. Any failure after the first one would leave the header in an corrupted state. With this patch, the whole header is written at once, so we can't fail in the middle. At the same time, this gives us a reusable functions that updates all fields of the qcow2 header and not only the backing file. Signed-off-by: Kevin Wolf <kwolf@redhat.com>
-rw-r--r--block/qcow2.c154
-rw-r--r--block/qcow2.h1
2 files changed, 95 insertions, 60 deletions
diff --git a/block/qcow2.c b/block/qcow2.c
index aa32e8d01a..9f8c2de285 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -669,103 +669,137 @@ static void qcow2_invalidate_cache(BlockDriverState *bs)
}
}
+static size_t header_ext_add(char *buf, uint32_t magic, const void *s,
+ size_t len, size_t buflen)
+{
+ QCowExtension *ext_backing_fmt = (QCowExtension*) buf;
+ size_t ext_len = sizeof(QCowExtension) + ((len + 7) & ~7);
+
+ if (buflen < ext_len) {
+ return -ENOSPC;
+ }
+
+ *ext_backing_fmt = (QCowExtension) {
+ .magic = cpu_to_be32(magic),
+ .len = cpu_to_be32(len),
+ };
+ memcpy(buf + sizeof(QCowExtension), s, len);
+
+ return ext_len;
+}
+
/*
- * 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.
+ * Updates the qcow2 header, including the variable length parts of it, 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)
+int qcow2_update_header(BlockDriverState *bs)
{
- size_t backing_file_len = 0;
- size_t backing_fmt_len = 0;
BDRVQcowState *s = bs->opaque;
- QCowExtension ext_backing_fmt = {0, 0};
+ QCowHeader *header;
+ char *buf;
+ size_t buflen = s->cluster_size;
int ret;
+ uint64_t total_size;
+ uint32_t refcount_table_clusters;
- /* 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(QCOW2_EXT_MAGIC_BACKING_FORMAT);
- backing_fmt_len = ((sizeof(ext_backing_fmt)
- + strlen(backing_fmt) + 7) & ~7);
- }
+ buf = qemu_blockalign(bs, buflen);
+ memset(buf, 0, s->cluster_size);
- /* Check if we can fit the new header into the first cluster */
- if (backing_file) {
- backing_file_len = strlen(backing_file);
- }
+ /* Header structure */
+ header = (QCowHeader*) buf;
- size_t header_size = sizeof(QCowHeader) + backing_file_len
- + backing_fmt_len;
-
- if (header_size > s->cluster_size) {
- return -ENOSPC;
+ if (buflen < sizeof(*header)) {
+ ret = -ENOSPC;
+ goto fail;
}
- /* 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);
+ total_size = bs->total_sectors * BDRV_SECTOR_SIZE;
+ refcount_table_clusters = s->refcount_table_size >> (s->cluster_bits - 3);
+
+ *header = (QCowHeader) {
+ .magic = cpu_to_be32(QCOW_MAGIC),
+ .version = cpu_to_be32(QCOW_VERSION),
+ .backing_file_offset = 0,
+ .backing_file_size = 0,
+ .cluster_bits = cpu_to_be32(s->cluster_bits),
+ .size = cpu_to_be64(total_size),
+ .crypt_method = cpu_to_be32(s->crypt_method_header),
+ .l1_size = cpu_to_be32(s->l1_size),
+ .l1_table_offset = cpu_to_be64(s->l1_table_offset),
+ .refcount_table_offset = cpu_to_be64(s->refcount_table_offset),
+ .refcount_table_clusters = cpu_to_be32(refcount_table_clusters),
+ .nb_snapshots = cpu_to_be32(s->nb_snapshots),
+ .snapshots_offset = cpu_to_be64(s->snapshots_offset),
+ };
- memcpy(buf + offset, backing_fmt, strlen(backing_fmt));
- offset += strlen(backing_fmt);
+ buf += sizeof(*header);
+ buflen -= sizeof(*header);
- memset(buf + offset, 0, padding);
- offset += padding;
+ /* Backing file format header extension */
+ if (*bs->backing_format) {
+ ret = header_ext_add(buf, QCOW2_EXT_MAGIC_BACKING_FORMAT,
+ bs->backing_format, strlen(bs->backing_format),
+ buflen);
+ if (ret < 0) {
+ goto fail;
}
- memcpy(buf + offset, backing_file, backing_file_len);
- backing_file_offset = sizeof(QCowHeader) + offset;
+ buf += ret;
+ buflen -= ret;
}
- ret = bdrv_pwrite_sync(bs->file, sizeof(QCowHeader), buf, ext_size);
+ /* End of header extensions */
+ ret = header_ext_add(buf, QCOW2_EXT_MAGIC_END, NULL, 0, buflen);
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);
+ buf += ret;
+ buflen -= ret;
- ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, backing_file_offset),
- &be_backing_file_offset, sizeof(uint64_t));
- if (ret < 0) {
- goto fail;
+ /* Backing file name */
+ if (*bs->backing_file) {
+ size_t backing_file_len = strlen(bs->backing_file);
+
+ if (buflen < backing_file_len) {
+ ret = -ENOSPC;
+ goto fail;
+ }
+
+ strncpy(buf, bs->backing_file, buflen);
+
+ header->backing_file_offset = cpu_to_be64(buf - ((char*) header));
+ header->backing_file_size = cpu_to_be32(backing_file_len);
}
- ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, backing_file_size),
- &be_backing_file_size, sizeof(uint32_t));
+ /* Write the new header */
+ ret = bdrv_pwrite(bs->file, 0, header, s->cluster_size);
if (ret < 0) {
goto fail;
}
ret = 0;
fail:
+ qemu_vfree(header);
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);
+ /* Backing file format doesn't make sense without a backing file */
+ if (backing_fmt && !backing_file) {
+ return -EINVAL;
+ }
+
+ pstrcpy(bs->backing_file, sizeof(bs->backing_file), backing_file ?: "");
+ pstrcpy(bs->backing_format, sizeof(bs->backing_format), backing_fmt ?: "");
+
+ return qcow2_update_header(bs);
}
static int preallocate(BlockDriverState *bs)
diff --git a/block/qcow2.h b/block/qcow2.h
index 99e45361f5..aae5f894e1 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -178,6 +178,7 @@ static inline int64_t align_offset(int64_t offset, int n)
/* qcow2.c functions */
int qcow2_backing_read1(BlockDriverState *bs, QEMUIOVector *qiov,
int64_t sector_num, int nb_sectors);
+int qcow2_update_header(BlockDriverState *bs);
/* qcow2-refcount.c functions */
int qcow2_refcount_init(BlockDriverState *bs);