summaryrefslogtreecommitdiff
path: root/block/qcow2.c
diff options
context:
space:
mode:
Diffstat (limited to 'block/qcow2.c')
-rw-r--r--block/qcow2.c154
1 files changed, 94 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)