#define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
#define QCOW2_EXT_MAGIC_CRYPTO_HEADER 0x0537be77
#define QCOW2_EXT_MAGIC_BITMAPS 0x23852875
+#define QCOW2_EXT_MAGIC_DATA_FILE 0x44415441
+
+static int coroutine_fn
+qcow2_co_preadv_compressed(BlockDriverState *bs,
+ uint64_t file_cluster_offset,
+ uint64_t offset,
+ uint64_t bytes,
+ QEMUIOVector *qiov);
static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename)
{
/* Zero fill remaining space in cluster so it has predictable
* content in case of future spec changes */
clusterlen = size_to_clusters(s, headerlen) * s->cluster_size;
- assert(qcow2_pre_write_overlap_check(bs, 0, ret, clusterlen) == 0);
+ assert(qcow2_pre_write_overlap_check(bs, 0, ret, clusterlen, false) == 0);
ret = bdrv_pwrite_zeroes(bs->file,
ret + headerlen,
clusterlen - headerlen, 0);
"pread fail from offset %" PRIu64, offset);
return 1;
}
- be32_to_cpus(&ext.magic);
- be32_to_cpus(&ext.len);
+ ext.magic = be32_to_cpu(ext.magic);
+ ext.len = be32_to_cpu(ext.len);
offset += sizeof(ext);
#ifdef DEBUG_EXT
printf("ext.magic = 0x%x\n", ext.magic);
"Unable to read CRYPTO header extension");
return ret;
}
- be64_to_cpus(&s->crypto_header.offset);
- be64_to_cpus(&s->crypto_header.length);
+ s->crypto_header.offset = be64_to_cpu(s->crypto_header.offset);
+ s->crypto_header.length = be64_to_cpu(s->crypto_header.length);
if ((s->crypto_header.offset % s->cluster_size) != 0) {
error_setg(errp, "Encryption header offset '%" PRIu64 "' is "
}
s->crypto = qcrypto_block_open(s->crypto_opts, "encrypt.",
qcow2_crypto_hdr_read_func,
- bs, cflags, errp);
+ bs, cflags, 1, errp);
if (!s->crypto) {
return -EINVAL;
}
return -EINVAL;
}
- be32_to_cpus(&bitmaps_ext.nb_bitmaps);
- be64_to_cpus(&bitmaps_ext.bitmap_directory_size);
- be64_to_cpus(&bitmaps_ext.bitmap_directory_offset);
+ bitmaps_ext.nb_bitmaps = be32_to_cpu(bitmaps_ext.nb_bitmaps);
+ bitmaps_ext.bitmap_directory_size =
+ be64_to_cpu(bitmaps_ext.bitmap_directory_size);
+ bitmaps_ext.bitmap_directory_offset =
+ be64_to_cpu(bitmaps_ext.bitmap_directory_offset);
if (bitmaps_ext.nb_bitmaps > QCOW2_MAX_BITMAPS) {
error_setg(errp,
#endif
break;
+ case QCOW2_EXT_MAGIC_DATA_FILE:
+ {
+ s->image_data_file = g_malloc0(ext.len + 1);
+ ret = bdrv_pread(bs->file, offset, s->image_data_file, ext.len);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret,
+ "ERROR: Could not read data file name");
+ return ret;
+ }
+#ifdef DEBUG_EXT
+ printf("Qcow2: Got external data file %s\n", s->image_data_file);
+#endif
+ break;
+ }
+
default:
/* unknown magic - save it in case we need to rewrite the header */
/* If you add a new feature, make sure to also update the fast
BDRVQcow2State *s = bs->opaque;
uint64_t combined_cache_size, l2_cache_max_setting;
bool l2_cache_size_set, refcount_cache_size_set, combined_cache_size_set;
+ bool l2_cache_entry_size_set;
int min_refcount_cache = MIN_REFCOUNT_CACHE_SIZE * s->cluster_size;
uint64_t virtual_disk_size = bs->total_sectors * BDRV_SECTOR_SIZE;
uint64_t max_l2_cache = virtual_disk_size / (s->cluster_size / 8);
combined_cache_size_set = qemu_opt_get(opts, QCOW2_OPT_CACHE_SIZE);
l2_cache_size_set = qemu_opt_get(opts, QCOW2_OPT_L2_CACHE_SIZE);
refcount_cache_size_set = qemu_opt_get(opts, QCOW2_OPT_REFCOUNT_CACHE_SIZE);
+ l2_cache_entry_size_set = qemu_opt_get(opts, QCOW2_OPT_L2_CACHE_ENTRY_SIZE);
combined_cache_size = qemu_opt_get_size(opts, QCOW2_OPT_CACHE_SIZE, 0);
l2_cache_max_setting = qemu_opt_get_size(opts, QCOW2_OPT_L2_CACHE_SIZE,
}
}
}
+
+ /*
+ * If the L2 cache is not enough to cover the whole disk then
+ * default to 4KB entries. Smaller entries reduce the cost of
+ * loads and evictions and increase I/O performance.
+ */
+ if (*l2_cache_size < max_l2_cache && !l2_cache_entry_size_set) {
+ *l2_cache_entry_size = MIN(s->cluster_size, 4096);
+ }
+
/* l2_cache_size and refcount_cache_size are ensured to have at least
* their minimum values in qcow2_update_options_prepare() */
error_setg_errno(errp, -ret, "Could not read qcow2 header");
goto fail;
}
- be32_to_cpus(&header.magic);
- be32_to_cpus(&header.version);
- be64_to_cpus(&header.backing_file_offset);
- be32_to_cpus(&header.backing_file_size);
- be64_to_cpus(&header.size);
- be32_to_cpus(&header.cluster_bits);
- be32_to_cpus(&header.crypt_method);
- be64_to_cpus(&header.l1_table_offset);
- be32_to_cpus(&header.l1_size);
- be64_to_cpus(&header.refcount_table_offset);
- be32_to_cpus(&header.refcount_table_clusters);
- be64_to_cpus(&header.snapshots_offset);
- be32_to_cpus(&header.nb_snapshots);
+ header.magic = be32_to_cpu(header.magic);
+ header.version = be32_to_cpu(header.version);
+ header.backing_file_offset = be64_to_cpu(header.backing_file_offset);
+ header.backing_file_size = be32_to_cpu(header.backing_file_size);
+ header.size = be64_to_cpu(header.size);
+ header.cluster_bits = be32_to_cpu(header.cluster_bits);
+ header.crypt_method = be32_to_cpu(header.crypt_method);
+ header.l1_table_offset = be64_to_cpu(header.l1_table_offset);
+ header.l1_size = be32_to_cpu(header.l1_size);
+ header.refcount_table_offset = be64_to_cpu(header.refcount_table_offset);
+ header.refcount_table_clusters =
+ be32_to_cpu(header.refcount_table_clusters);
+ header.snapshots_offset = be64_to_cpu(header.snapshots_offset);
+ header.nb_snapshots = be32_to_cpu(header.nb_snapshots);
if (header.magic != QCOW_MAGIC) {
error_setg(errp, "Image is not in qcow2 format");
header.refcount_order = 4;
header.header_length = 72;
} else {
- be64_to_cpus(&header.incompatible_features);
- be64_to_cpus(&header.compatible_features);
- be64_to_cpus(&header.autoclear_features);
- be32_to_cpus(&header.refcount_order);
- be32_to_cpus(&header.header_length);
+ header.incompatible_features =
+ be64_to_cpu(header.incompatible_features);
+ header.compatible_features = be64_to_cpu(header.compatible_features);
+ header.autoclear_features = be64_to_cpu(header.autoclear_features);
+ header.refcount_order = be32_to_cpu(header.refcount_order);
+ header.header_length = be32_to_cpu(header.header_length);
if (header.header_length < 104) {
error_setg(errp, "qcow2 header too short");
goto fail;
}
for(i = 0;i < s->l1_size; i++) {
- be64_to_cpus(&s->l1_table[i]);
+ s->l1_table[i] = be64_to_cpu(s->l1_table[i]);
}
}
goto fail;
}
- s->cluster_cache_offset = -1;
s->flags = flags;
ret = qcow2_refcount_init(bs);
goto fail;
}
+ /* Open external data file */
+ s->data_file = bdrv_open_child(NULL, options, "data-file", bs, &child_file,
+ true, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (s->incompatible_features & QCOW2_INCOMPAT_DATA_FILE) {
+ if (!s->data_file && s->image_data_file) {
+ s->data_file = bdrv_open_child(s->image_data_file, options,
+ "data-file", bs, &child_file,
+ false, errp);
+ if (!s->data_file) {
+ ret = -EINVAL;
+ goto fail;
+ }
+ }
+ if (!s->data_file) {
+ error_setg(errp, "'data-file' is required for this image");
+ ret = -EINVAL;
+ goto fail;
+ }
+ } else {
+ if (s->data_file) {
+ error_setg(errp, "'data-file' can only be set for images with an "
+ "external data file");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ s->data_file = bs->file;
+
+ if (data_file_is_raw(bs)) {
+ error_setg(errp, "data-file-raw requires a data file");
+ ret = -EINVAL;
+ goto fail;
+ }
+ }
+
/* qcow2_read_extension may have set up the crypto context
* if the crypt method needs a header region, some methods
* don't need header extensions, so must check here
cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
}
s->crypto = qcrypto_block_open(s->crypto_opts, "encrypt.",
- NULL, NULL, cflags, errp);
+ NULL, NULL, cflags, 1, errp);
if (!s->crypto) {
ret = -EINVAL;
goto fail;
goto fail;
}
ret = bdrv_pread(bs->file, header.backing_file_offset,
- bs->backing_file, len);
+ bs->auto_backing_file, len);
if (ret < 0) {
error_setg_errno(errp, -ret, "Could not read backing file name");
goto fail;
}
- bs->backing_file[len] = '\0';
- s->image_backing_file = g_strdup(bs->backing_file);
+ bs->auto_backing_file[len] = '\0';
+ pstrcpy(bs->backing_file, sizeof(bs->backing_file),
+ bs->auto_backing_file);
+ s->image_backing_file = g_strdup(bs->auto_backing_file);
}
/* Internal snapshots */
return ret;
fail:
+ g_free(s->image_data_file);
+ if (has_data_file(bs)) {
+ bdrv_unref_child(bs, s->data_file);
+ }
g_free(s->unknown_header_fields);
cleanup_unknown_header_ext(bs);
qcow2_free_snapshots(bs);
/* From bdrv_co_create. */
qcow2_open_entry(&qoc);
} else {
+ assert(qemu_get_current_aio_context() == qemu_get_aio_context());
qemu_coroutine_enter(qemu_coroutine_create(qcow2_open_entry, &qoc));
BDRV_POLL_WHILE(bs, qoc.ret == -EINPROGRESS);
}
if (bs->encrypted) {
/* Encryption works on a sector granularity */
- bs->bl.request_alignment = BDRV_SECTOR_SIZE;
+ bs->bl.request_alignment = qcrypto_block_get_sector_size(s->crypto);
}
bs->bl.pwrite_zeroes_alignment = s->cluster_size;
bs->bl.pdiscard_alignment = s->cluster_size;
*pnum = bytes;
- if (cluster_offset != 0 && ret != QCOW2_CLUSTER_COMPRESSED &&
+ if ((ret == QCOW2_CLUSTER_NORMAL || ret == QCOW2_CLUSTER_ZERO_ALLOC) &&
!s->crypto) {
index_in_cluster = offset & (s->cluster_size - 1);
*map = cluster_offset | index_in_cluster;
- *file = bs->file->bs;
+ *file = s->data_file->bs;
status |= BDRV_BLOCK_OFFSET_VALID;
}
if (ret == QCOW2_CLUSTER_ZERO_PLAIN || ret == QCOW2_CLUSTER_ZERO_ALLOC) {
break;
case QCOW2_CLUSTER_COMPRESSED:
- /* add AIO support for compressed blocks ? */
- ret = qcow2_decompress_cluster(bs, cluster_offset);
+ qemu_co_mutex_unlock(&s->lock);
+ ret = qcow2_co_preadv_compressed(bs, cluster_offset,
+ offset, cur_bytes,
+ &hd_qiov);
+ qemu_co_mutex_lock(&s->lock);
if (ret < 0) {
goto fail;
}
- qemu_iovec_from_buf(&hd_qiov, 0,
- s->cluster_cache + offset_in_cluster,
- cur_bytes);
break;
case QCOW2_CLUSTER_NORMAL:
*/
if (!cluster_data) {
cluster_data =
- qemu_try_blockalign(bs->file->bs,
+ qemu_try_blockalign(s->data_file->bs,
QCOW_MAX_CRYPT_CLUSTERS
* s->cluster_size);
if (cluster_data == NULL) {
BLKDBG_EVENT(bs->file, BLKDBG_READ_AIO);
qemu_co_mutex_unlock(&s->lock);
- ret = bdrv_co_preadv(bs->file,
+ ret = bdrv_co_preadv(s->data_file,
cluster_offset + offset_in_cluster,
cur_bytes, &hd_qiov, 0);
qemu_co_mutex_lock(&s->lock);
qemu_iovec_init(&hd_qiov, qiov->niov);
- s->cluster_cache_offset = -1; /* disable compressed cache */
-
qemu_co_mutex_lock(&s->lock);
while (bytes != 0) {
}
ret = qcow2_pre_write_overlap_check(bs, 0,
- cluster_offset + offset_in_cluster, cur_bytes);
+ cluster_offset + offset_in_cluster, cur_bytes, true);
if (ret < 0) {
goto fail;
}
BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO);
trace_qcow2_writev_data(qemu_coroutine_self(),
cluster_offset + offset_in_cluster);
- ret = bdrv_co_pwritev(bs->file,
+ ret = bdrv_co_pwritev(s->data_file,
cluster_offset + offset_in_cluster,
cur_bytes, &hd_qiov, 0);
qemu_co_mutex_lock(&s->lock);
g_free(s->unknown_header_fields);
cleanup_unknown_header_ext(bs);
+ g_free(s->image_data_file);
g_free(s->image_backing_file);
g_free(s->image_backing_format);
- g_free(s->cluster_cache);
- qemu_vfree(s->cluster_data);
+ if (has_data_file(bs)) {
+ bdrv_unref_child(bs, s->data_file);
+ }
+
qcow2_refcount_close(bs);
qcow2_free_snapshots(bs);
}
buflen -= ret;
}
+ /* External data file header extension */
+ if (has_data_file(bs) && s->image_data_file) {
+ ret = header_ext_add(buf, QCOW2_EXT_MAGIC_DATA_FILE,
+ s->image_data_file, strlen(s->image_data_file),
+ buflen);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ buf += ret;
+ buflen -= ret;
+ }
+
/* Full disk encryption header pointer extension */
if (s->crypto_header.offset != 0) {
- cpu_to_be64s(&s->crypto_header.offset);
- cpu_to_be64s(&s->crypto_header.length);
+ s->crypto_header.offset = cpu_to_be64(s->crypto_header.offset);
+ s->crypto_header.length = cpu_to_be64(s->crypto_header.length);
ret = header_ext_add(buf, QCOW2_EXT_MAGIC_CRYPTO_HEADER,
&s->crypto_header, sizeof(s->crypto_header),
buflen);
- be64_to_cpus(&s->crypto_header.offset);
- be64_to_cpus(&s->crypto_header.length);
+ s->crypto_header.offset = be64_to_cpu(s->crypto_header.offset);
+ s->crypto_header.length = be64_to_cpu(s->crypto_header.length);
if (ret < 0) {
goto fail;
}
.bit = QCOW2_INCOMPAT_CORRUPT_BITNR,
.name = "corrupt bit",
},
+ {
+ .type = QCOW2_FEAT_TYPE_INCOMPATIBLE,
+ .bit = QCOW2_INCOMPAT_DATA_FILE_BITNR,
+ .name = "external data file",
+ },
{
.type = QCOW2_FEAT_TYPE_COMPATIBLE,
.bit = QCOW2_COMPAT_LAZY_REFCOUNTS_BITNR,
{
BDRVQcow2State *s = bs->opaque;
+ /* Adding a backing file means that the external data file alone won't be
+ * enough to make sense of the content */
+ if (backing_file && data_file_is_raw(bs)) {
+ return -EINVAL;
+ }
+
if (backing_file && strlen(backing_file) > 1023) {
return -EINVAL;
}
+ pstrcpy(bs->auto_backing_file, sizeof(bs->auto_backing_file),
+ backing_file ?: "");
pstrcpy(bs->backing_file, sizeof(bs->backing_file), backing_file ?: "");
pstrcpy(bs->backing_format, sizeof(bs->backing_format), backing_fmt ?: "");
*/
BlockBackend *blk = NULL;
BlockDriverState *bs = NULL;
+ BlockDriverState *data_bs = NULL;
QCowHeader *header;
size_t cluster_size;
int version;
}
refcount_order = ctz32(qcow2_opts->refcount_bits);
+ if (qcow2_opts->data_file_raw && !qcow2_opts->data_file) {
+ error_setg(errp, "data-file-raw requires data-file");
+ ret = -EINVAL;
+ goto out;
+ }
+ if (qcow2_opts->data_file_raw && qcow2_opts->has_backing_file) {
+ error_setg(errp, "Backing file and data-file-raw cannot be used at "
+ "the same time");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (qcow2_opts->data_file) {
+ if (version < 3) {
+ error_setg(errp, "External data files are only supported with "
+ "compatibility level 1.1 and above (use version=v3 or "
+ "greater)");
+ ret = -EINVAL;
+ goto out;
+ }
+ data_bs = bdrv_open_blockdev_ref(qcow2_opts->data_file, errp);
+ if (bs == NULL) {
+ ret = -EIO;
+ goto out;
+ }
+ }
/* Create BlockBackend to write to the image */
blk = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
goto out;
}
- if (qcow2_opts->preallocation == PREALLOC_MODE_FULL ||
- qcow2_opts->preallocation == PREALLOC_MODE_FALLOC)
- {
- int64_t prealloc_size =
- qcow2_calc_prealloc_size(qcow2_opts->size, cluster_size,
- refcount_order);
-
- ret = blk_truncate(blk, prealloc_size, qcow2_opts->preallocation, errp);
- if (ret < 0) {
- goto out;
- }
- }
-
/* Write the header */
QEMU_BUILD_BUG_ON((1 << MIN_CLUSTER_BITS) < sizeof(*header));
header = g_malloc0(cluster_size);
header->compatible_features |=
cpu_to_be64(QCOW2_COMPAT_LAZY_REFCOUNTS);
}
+ if (data_bs) {
+ header->incompatible_features |=
+ cpu_to_be64(QCOW2_INCOMPAT_DATA_FILE);
+ }
+ if (qcow2_opts->data_file_raw) {
+ header->autoclear_features |=
+ cpu_to_be64(QCOW2_AUTOCLEAR_DATA_FILE_RAW);
+ }
ret = blk_pwrite(blk, 0, header, cluster_size, 0);
g_free(header);
options = qdict_new();
qdict_put_str(options, "driver", "qcow2");
qdict_put_str(options, "file", bs->node_name);
+ if (data_bs) {
+ qdict_put_str(options, "data-file", data_bs->node_name);
+ }
blk = blk_new_open(NULL, NULL, options,
BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_NO_FLUSH,
&local_err);
abort();
}
+ /* Set the external data file if necessary */
+ if (data_bs) {
+ BDRVQcow2State *s = blk_bs(blk)->opaque;
+ s->image_data_file = g_strdup(data_bs->filename);
+ }
+
/* Create a full header (including things like feature table) */
ret = qcow2_update_header(blk_bs(blk));
if (ret < 0) {
}
/* Okay, now that we have a valid image, let's give it the right size */
- ret = blk_truncate(blk, qcow2_opts->size, PREALLOC_MODE_OFF, errp);
+ ret = blk_truncate(blk, qcow2_opts->size, qcow2_opts->preallocation, errp);
if (ret < 0) {
error_prepend(errp, "Could not resize image: ");
goto out;
}
}
- /* And if we're supposed to preallocate metadata, do that now */
- if (qcow2_opts->preallocation != PREALLOC_MODE_OFF) {
- BDRVQcow2State *s = blk_bs(blk)->opaque;
- qemu_co_mutex_lock(&s->lock);
- ret = preallocate_co(blk_bs(blk), 0, qcow2_opts->size);
- qemu_co_mutex_unlock(&s->lock);
-
- if (ret < 0) {
- error_setg_errno(errp, -ret, "Could not preallocate metadata");
- goto out;
- }
- }
-
blk_unref(blk);
blk = NULL;
options = qdict_new();
qdict_put_str(options, "driver", "qcow2");
qdict_put_str(options, "file", bs->node_name);
+ if (data_bs) {
+ qdict_put_str(options, "data-file", data_bs->node_name);
+ }
blk = blk_new_open(NULL, NULL, options,
BDRV_O_RDWR | BDRV_O_NO_BACKING | BDRV_O_NO_IO,
&local_err);
out:
blk_unref(blk);
bdrv_unref(bs);
+ bdrv_unref(data_bs);
return ret;
}
QDict *qdict;
Visitor *v;
BlockDriverState *bs = NULL;
+ BlockDriverState *data_bs = NULL;
Error *local_err = NULL;
const char *val;
int ret;
{ BLOCK_OPT_REFCOUNT_BITS, "refcount-bits" },
{ BLOCK_OPT_ENCRYPT, BLOCK_OPT_ENCRYPT_FORMAT },
{ BLOCK_OPT_COMPAT_LEVEL, "version" },
+ { BLOCK_OPT_DATA_FILE_RAW, "data-file-raw" },
{ NULL, NULL },
};
goto finish;
}
+ /* Create and open an external data file (protocol layer) */
+ val = qdict_get_try_str(qdict, BLOCK_OPT_DATA_FILE);
+ if (val) {
+ ret = bdrv_create_file(val, opts, errp);
+ if (ret < 0) {
+ goto finish;
+ }
+
+ data_bs = bdrv_open(val, NULL, NULL,
+ BDRV_O_RDWR | BDRV_O_RESIZE | BDRV_O_PROTOCOL,
+ errp);
+ if (data_bs == NULL) {
+ ret = -EIO;
+ goto finish;
+ }
+
+ qdict_del(qdict, BLOCK_OPT_DATA_FILE);
+ qdict_put_str(qdict, "data-file", data_bs->node_name);
+ }
+
/* Set 'driver' and 'node' options */
qdict_put_str(qdict, "driver", "qcow2");
qdict_put_str(qdict, "file", bs->node_name);
finish:
qobject_unref(qdict);
bdrv_unref(bs);
+ bdrv_unref(data_bs);
qapi_free_BlockdevCreateOptions(create_options);
return ret;
}
goto out;
case QCOW2_CLUSTER_NORMAL:
- child = bs->file;
+ child = s->data_file;
copy_offset += offset_into_cluster(s, src_offset);
if ((copy_offset & 511) != 0) {
ret = -EIO;
QCowL2Meta *l2meta = NULL;
assert(!bs->encrypted);
- s->cluster_cache_offset = -1; /* disable compressed cache */
qemu_co_mutex_lock(&s->lock);
assert((cluster_offset & 511) == 0);
ret = qcow2_pre_write_overlap_check(bs, 0,
- cluster_offset + offset_in_cluster, cur_bytes);
+ cluster_offset + offset_in_cluster, cur_bytes, true);
if (ret < 0) {
goto fail;
}
qemu_co_mutex_unlock(&s->lock);
ret = bdrv_co_copy_range_to(src, src_offset,
- bs->file,
+ s->data_file,
cluster_offset + offset_in_cluster,
cur_bytes, read_flags, write_flags);
qemu_co_mutex_lock(&s->lock);
}
/* cannot proceed if image has bitmaps */
- if (s->nb_bitmaps) {
- /* TODO: resize bitmaps in the image */
- error_setg(errp, "Can't resize an image which has bitmaps");
+ if (qcow2_truncate_bitmaps_check(bs, errp)) {
ret = -ENOTSUP;
goto fail;
}
int64_t old_file_size, new_file_size;
uint64_t nb_new_data_clusters, nb_new_l2_tables;
+ /* With a data file, preallocation means just allocating the metadata
+ * and forwarding the truncate request to the data file */
+ if (has_data_file(bs)) {
+ ret = preallocate_co(bs, old_length, offset);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "Preallocation failed");
+ goto fail;
+ }
+ break;
+ }
+
old_file_size = bdrv_getlength(bs->file->bs);
if (old_file_size < 0) {
error_setg_errno(errp, -old_file_size,
bs->total_sectors = offset / BDRV_SECTOR_SIZE;
+ if (has_data_file(bs)) {
+ if (prealloc == PREALLOC_MODE_METADATA) {
+ prealloc = PREALLOC_MODE_OFF;
+ }
+ ret = bdrv_co_truncate(s->data_file, offset, prealloc, errp);
+ if (ret < 0) {
+ goto fail;
+ }
+ }
+
/* write updated header.size */
offset = cpu_to_be64(offset);
ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, size),
/*
* qcow2_compress()
*
- * @dest - destination buffer, at least of @size-1 bytes
- * @src - source buffer, @size bytes
+ * @dest - destination buffer, @dest_size bytes
+ * @src - source buffer, @src_size bytes
*
* Returns: compressed size on success
- * -1 if compression is inefficient
+ * -1 destination buffer is not enough to store compressed data
* -2 on any other error
*/
-static ssize_t qcow2_compress(void *dest, const void *src, size_t size)
+static ssize_t qcow2_compress(void *dest, size_t dest_size,
+ const void *src, size_t src_size)
{
ssize_t ret;
z_stream strm;
memset(&strm, 0, sizeof(strm));
ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
-12, 9, Z_DEFAULT_STRATEGY);
- if (ret != 0) {
+ if (ret != Z_OK) {
return -2;
}
/* strm.next_in is not const in old zlib versions, such as those used on
* OpenBSD/NetBSD, so cast the const away */
- strm.avail_in = size;
+ strm.avail_in = src_size;
strm.next_in = (void *) src;
- strm.avail_out = size - 1;
+ strm.avail_out = dest_size;
strm.next_out = dest;
ret = deflate(&strm, Z_FINISH);
if (ret == Z_STREAM_END) {
- ret = size - 1 - strm.avail_out;
+ ret = dest_size - strm.avail_out;
} else {
ret = (ret == Z_OK ? -1 : -2);
}
return ret;
}
+/*
+ * qcow2_decompress()
+ *
+ * Decompress some data (not more than @src_size bytes) to produce exactly
+ * @dest_size bytes.
+ *
+ * @dest - destination buffer, @dest_size bytes
+ * @src - source buffer, @src_size bytes
+ *
+ * Returns: 0 on success
+ * -1 on fail
+ */
+static ssize_t qcow2_decompress(void *dest, size_t dest_size,
+ const void *src, size_t src_size)
+{
+ int ret = 0;
+ z_stream strm;
+
+ memset(&strm, 0, sizeof(strm));
+ strm.avail_in = src_size;
+ strm.next_in = (void *) src;
+ strm.avail_out = dest_size;
+ strm.next_out = dest;
+
+ ret = inflateInit2(&strm, -12);
+ if (ret != Z_OK) {
+ return -1;
+ }
+
+ ret = inflate(&strm, Z_FINISH);
+ if ((ret != Z_STREAM_END && ret != Z_BUF_ERROR) || strm.avail_out != 0) {
+ /* We approve Z_BUF_ERROR because we need @dest buffer to be filled, but
+ * @src buffer may be processed partly (because in qcow2 we know size of
+ * compressed data with precision of one sector) */
+ ret = -1;
+ }
+
+ inflateEnd(&strm);
+
+ return ret;
+}
+
#define MAX_COMPRESS_THREADS 4
+typedef ssize_t (*Qcow2CompressFunc)(void *dest, size_t dest_size,
+ const void *src, size_t src_size);
typedef struct Qcow2CompressData {
void *dest;
+ size_t dest_size;
const void *src;
- size_t size;
+ size_t src_size;
ssize_t ret;
+
+ Qcow2CompressFunc func;
} Qcow2CompressData;
static int qcow2_compress_pool_func(void *opaque)
{
Qcow2CompressData *data = opaque;
- data->ret = qcow2_compress(data->dest, data->src, data->size);
+ data->ret = data->func(data->dest, data->dest_size,
+ data->src, data->src_size);
return 0;
}
qemu_coroutine_enter(opaque);
}
-/* See qcow2_compress definition for parameters description */
-static ssize_t qcow2_co_compress(BlockDriverState *bs,
- void *dest, const void *src, size_t size)
+static ssize_t coroutine_fn
+qcow2_co_do_compress(BlockDriverState *bs, void *dest, size_t dest_size,
+ const void *src, size_t src_size, Qcow2CompressFunc func)
{
BDRVQcow2State *s = bs->opaque;
BlockAIOCB *acb;
ThreadPool *pool = aio_get_thread_pool(bdrv_get_aio_context(bs));
Qcow2CompressData arg = {
.dest = dest,
+ .dest_size = dest_size,
.src = src,
- .size = size,
+ .src_size = src_size,
+ .func = func,
};
while (s->nb_compress_threads >= MAX_COMPRESS_THREADS) {
return arg.ret;
}
+static ssize_t coroutine_fn
+qcow2_co_compress(BlockDriverState *bs, void *dest, size_t dest_size,
+ const void *src, size_t src_size)
+{
+ return qcow2_co_do_compress(bs, dest, dest_size, src, src_size,
+ qcow2_compress);
+}
+
+static ssize_t coroutine_fn
+qcow2_co_decompress(BlockDriverState *bs, void *dest, size_t dest_size,
+ const void *src, size_t src_size)
+{
+ return qcow2_co_do_compress(bs, dest, dest_size, src, src_size,
+ qcow2_decompress);
+}
+
/* XXX: put compressed sectors first, then all the cluster aligned
tables to avoid losing bytes in alignment */
static coroutine_fn int
{
BDRVQcow2State *s = bs->opaque;
QEMUIOVector hd_qiov;
- struct iovec iov;
int ret;
size_t out_len;
uint8_t *buf, *out_buf;
- int64_t cluster_offset;
+ uint64_t cluster_offset;
+
+ if (has_data_file(bs)) {
+ return -ENOTSUP;
+ }
if (bytes == 0) {
/* align end of file to a sector boundary to ease reading with
sector based I/Os */
- cluster_offset = bdrv_getlength(bs->file->bs);
- if (cluster_offset < 0) {
- return cluster_offset;
+ int64_t len = bdrv_getlength(bs->file->bs);
+ if (len < 0) {
+ return len;
}
- return bdrv_co_truncate(bs->file, cluster_offset, PREALLOC_MODE_OFF,
- NULL);
+ return bdrv_co_truncate(bs->file, len, PREALLOC_MODE_OFF, NULL);
}
if (offset_into_cluster(s, offset)) {
out_buf = g_malloc(s->cluster_size);
- out_len = qcow2_co_compress(bs, out_buf, buf, s->cluster_size);
+ out_len = qcow2_co_compress(bs, out_buf, s->cluster_size - 1,
+ buf, s->cluster_size);
if (out_len == -2) {
ret = -EINVAL;
goto fail;
}
qemu_co_mutex_lock(&s->lock);
- cluster_offset =
- qcow2_alloc_compressed_cluster_offset(bs, offset, out_len);
- if (!cluster_offset) {
+ ret = qcow2_alloc_compressed_cluster_offset(bs, offset, out_len,
+ &cluster_offset);
+ if (ret < 0) {
qemu_co_mutex_unlock(&s->lock);
- ret = -EIO;
goto fail;
}
- cluster_offset &= s->cluster_offset_mask;
- ret = qcow2_pre_write_overlap_check(bs, 0, cluster_offset, out_len);
+ ret = qcow2_pre_write_overlap_check(bs, 0, cluster_offset, out_len, true);
qemu_co_mutex_unlock(&s->lock);
if (ret < 0) {
goto fail;
}
- iov = (struct iovec) {
- .iov_base = out_buf,
- .iov_len = out_len,
- };
- qemu_iovec_init_external(&hd_qiov, &iov, 1);
+ qemu_iovec_init_buf(&hd_qiov, out_buf, out_len);
- BLKDBG_EVENT(bs->file, BLKDBG_WRITE_COMPRESSED);
- ret = bdrv_co_pwritev(bs->file, cluster_offset, out_len, &hd_qiov, 0);
+ BLKDBG_EVENT(s->data_file, BLKDBG_WRITE_COMPRESSED);
+ ret = bdrv_co_pwritev(s->data_file, cluster_offset, out_len, &hd_qiov, 0);
if (ret < 0) {
goto fail;
}
return ret;
}
+static int coroutine_fn
+qcow2_co_preadv_compressed(BlockDriverState *bs,
+ uint64_t file_cluster_offset,
+ uint64_t offset,
+ uint64_t bytes,
+ QEMUIOVector *qiov)
+{
+ BDRVQcow2State *s = bs->opaque;
+ int ret = 0, csize, nb_csectors;
+ uint64_t coffset;
+ uint8_t *buf, *out_buf;
+ QEMUIOVector local_qiov;
+ int offset_in_cluster = offset_into_cluster(s, offset);
+
+ coffset = file_cluster_offset & s->cluster_offset_mask;
+ nb_csectors = ((file_cluster_offset >> s->csize_shift) & s->csize_mask) + 1;
+ csize = nb_csectors * 512 - (coffset & 511);
+
+ buf = g_try_malloc(csize);
+ if (!buf) {
+ return -ENOMEM;
+ }
+ qemu_iovec_init_buf(&local_qiov, buf, csize);
+
+ out_buf = qemu_blockalign(bs, s->cluster_size);
+
+ BLKDBG_EVENT(bs->file, BLKDBG_READ_COMPRESSED);
+ ret = bdrv_co_preadv(bs->file, coffset, csize, &local_qiov, 0);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ if (qcow2_co_decompress(bs, out_buf, s->cluster_size, buf, csize) < 0) {
+ ret = -EIO;
+ goto fail;
+ }
+
+ qemu_iovec_from_buf(qiov, 0, out_buf + offset_in_cluster, bytes);
+
+fail:
+ qemu_vfree(out_buf);
+ g_free(buf);
+
+ return ret;
+}
+
static int make_completely_empty(BlockDriverState *bs)
{
BDRVQcow2State *s = bs->opaque;
return ret;
}
+static ssize_t qcow2_measure_crypto_hdr_init_func(QCryptoBlock *block,
+ size_t headerlen, void *opaque, Error **errp)
+{
+ size_t *headerlenp = opaque;
+
+ /* Stash away the payload size */
+ *headerlenp = headerlen;
+ return 0;
+}
+
+static ssize_t qcow2_measure_crypto_hdr_write_func(QCryptoBlock *block,
+ size_t offset, const uint8_t *buf, size_t buflen,
+ void *opaque, Error **errp)
+{
+ /* Discard the bytes, we're not actually writing to an image */
+ return buflen;
+}
+
+/* Determine the number of bytes for the LUKS payload */
+static bool qcow2_measure_luks_headerlen(QemuOpts *opts, size_t *len,
+ Error **errp)
+{
+ QDict *opts_qdict;
+ QDict *cryptoopts_qdict;
+ QCryptoBlockCreateOptions *cryptoopts;
+ QCryptoBlock *crypto;
+
+ /* Extract "encrypt." options into a qdict */
+ opts_qdict = qemu_opts_to_qdict(opts, NULL);
+ qdict_extract_subqdict(opts_qdict, &cryptoopts_qdict, "encrypt.");
+ qobject_unref(opts_qdict);
+
+ /* Build QCryptoBlockCreateOptions object from qdict */
+ qdict_put_str(cryptoopts_qdict, "format", "luks");
+ cryptoopts = block_crypto_create_opts_init(cryptoopts_qdict, errp);
+ qobject_unref(cryptoopts_qdict);
+ if (!cryptoopts) {
+ return false;
+ }
+
+ /* Fake LUKS creation in order to determine the payload size */
+ crypto = qcrypto_block_create(cryptoopts, "encrypt.",
+ qcow2_measure_crypto_hdr_init_func,
+ qcow2_measure_crypto_hdr_write_func,
+ len, errp);
+ qapi_free_QCryptoBlockCreateOptions(cryptoopts);
+ if (!crypto) {
+ return false;
+ }
+
+ qcrypto_block_free(crypto);
+ return true;
+}
+
static BlockMeasureInfo *qcow2_measure(QemuOpts *opts, BlockDriverState *in_bs,
Error **errp)
{
uint64_t virtual_size; /* disk size as seen by guest */
uint64_t refcount_bits;
uint64_t l2_tables;
+ uint64_t luks_payload_size = 0;
size_t cluster_size;
int version;
char *optstr;
PreallocMode prealloc;
bool has_backing_file;
+ bool has_luks;
/* Parse image creation options */
cluster_size = qcow2_opt_get_cluster_size_del(opts, &local_err);
has_backing_file = !!optstr;
g_free(optstr);
+ optstr = qemu_opt_get_del(opts, BLOCK_OPT_ENCRYPT_FORMAT);
+ has_luks = optstr && strcmp(optstr, "luks") == 0;
+ g_free(optstr);
+
+ if (has_luks) {
+ size_t headerlen;
+
+ if (!qcow2_measure_luks_headerlen(opts, &headerlen, &local_err)) {
+ goto err;
+ }
+
+ luks_payload_size = ROUND_UP(headerlen, cluster_size);
+ }
+
virtual_size = qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0);
virtual_size = ROUND_UP(virtual_size, cluster_size);
info = g_new(BlockMeasureInfo, 1);
info->fully_allocated =
qcow2_calc_prealloc_size(virtual_size, cluster_size,
- ctz32(refcount_bits));
+ ctz32(refcount_bits)) + luks_payload_size;
/* Remove data clusters that are not required. This overestimates the
* required size because metadata needed for the fully allocated file is
return 0;
}
-static ImageInfoSpecific *qcow2_get_specific_info(BlockDriverState *bs)
+static ImageInfoSpecific *qcow2_get_specific_info(BlockDriverState *bs,
+ Error **errp)
{
BDRVQcow2State *s = bs->opaque;
ImageInfoSpecific *spec_info;
QCryptoBlockInfo *encrypt_info = NULL;
+ Error *local_err = NULL;
if (s->crypto != NULL) {
- encrypt_info = qcrypto_block_get_info(s->crypto, &error_abort);
+ encrypt_info = qcrypto_block_get_info(s->crypto, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
}
spec_info = g_new(ImageInfoSpecific, 1);
*spec_info = (ImageInfoSpecific){
.type = IMAGE_INFO_SPECIFIC_KIND_QCOW2,
- .u.qcow2.data = g_new(ImageInfoSpecificQCow2, 1),
+ .u.qcow2.data = g_new0(ImageInfoSpecificQCow2, 1),
};
if (s->qcow_version == 2) {
*spec_info->u.qcow2.data = (ImageInfoSpecificQCow2){
.refcount_bits = s->refcount_bits,
};
} else if (s->qcow_version == 3) {
+ Qcow2BitmapInfoList *bitmaps;
+ bitmaps = qcow2_get_bitmap_info_list(bs, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ qapi_free_ImageInfoSpecific(spec_info);
+ return NULL;
+ }
*spec_info->u.qcow2.data = (ImageInfoSpecificQCow2){
.compat = g_strdup("1.1"),
.lazy_refcounts = s->compatible_features &
QCOW2_INCOMPAT_CORRUPT,
.has_corrupt = true,
.refcount_bits = s->refcount_bits,
+ .has_bitmaps = !!bitmaps,
+ .bitmaps = bitmaps,
+ .has_data_file = !!s->image_data_file,
+ .data_file = g_strdup(s->image_data_file),
+ .has_data_file_raw = has_data_file(bs),
+ .data_file_raw = data_file_is_raw(bs),
};
} else {
/* if this assertion fails, this probably means a new version was
return -ENOTSUP;
}
+ if (has_data_file(bs)) {
+ error_setg(errp, "Cannot downgrade an image with a data file");
+ return -ENOTSUP;
+ }
+
/* clear incompatible features */
if (s->incompatible_features & QCOW2_INCOMPAT_DIRTY) {
ret = qcow2_mark_clean(bs);
BDRVQcow2State *s = bs->opaque;
int old_version = s->qcow_version, new_version = old_version;
uint64_t new_size = 0;
- const char *backing_file = NULL, *backing_format = NULL;
+ const char *backing_file = NULL, *backing_format = NULL, *data_file = NULL;
bool lazy_refcounts = s->use_lazy_refcounts;
+ bool data_file_raw = data_file_is_raw(bs);
const char *compat = NULL;
uint64_t cluster_size = s->cluster_size;
bool encrypt;
"may not exceed 64 bits");
return -EINVAL;
}
+ } else if (!strcmp(desc->name, BLOCK_OPT_DATA_FILE)) {
+ data_file = qemu_opt_get(opts, BLOCK_OPT_DATA_FILE);
+ if (data_file && !has_data_file(bs)) {
+ error_setg(errp, "data-file can only be set for images that "
+ "use an external data file");
+ return -EINVAL;
+ }
+ } else if (!strcmp(desc->name, BLOCK_OPT_DATA_FILE_RAW)) {
+ data_file_raw = qemu_opt_get_bool(opts, BLOCK_OPT_DATA_FILE_RAW,
+ data_file_raw);
+ if (data_file_raw && !data_file_is_raw(bs)) {
+ error_setg(errp, "data-file-raw cannot be set on existing "
+ "images");
+ return -EINVAL;
+ }
} else {
/* if this point is reached, this probably means a new option was
* added without having it covered here */
}
}
+ /* data-file-raw blocks backing files, so clear it first if requested */
+ if (data_file_raw) {
+ s->autoclear_features |= QCOW2_AUTOCLEAR_DATA_FILE_RAW;
+ } else {
+ s->autoclear_features &= ~QCOW2_AUTOCLEAR_DATA_FILE_RAW;
+ }
+
+ if (data_file) {
+ g_free(s->image_data_file);
+ s->image_data_file = *data_file ? g_strdup(data_file) : NULL;
+ }
+
+ ret = qcow2_update_header(bs);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "Failed to update the image header");
+ return ret;
+ }
+
if (backing_file || backing_format) {
ret = qcow2_change_backing_file(bs,
backing_file ?: s->image_backing_file,
.type = QEMU_OPT_STRING,
.help = "Image format of the base image"
},
+ {
+ .name = BLOCK_OPT_DATA_FILE,
+ .type = QEMU_OPT_STRING,
+ .help = "File name of an external data file"
+ },
+ {
+ .name = BLOCK_OPT_DATA_FILE_RAW,
+ .type = QEMU_OPT_BOOL,
+ .help = "The external data file must stay valid as a raw image"
+ },
{
.name = BLOCK_OPT_ENCRYPT,
.type = QEMU_OPT_BOOL,
}
};
+static const char *const qcow2_strong_runtime_opts[] = {
+ "encrypt." BLOCK_CRYPTO_OPT_QCOW_KEY_SECRET,
+
+ NULL
+};
+
BlockDriver bdrv_qcow2 = {
.format_name = "qcow2",
.instance_size = sizeof(BDRVQcow2State),
.bdrv_inactivate = qcow2_inactivate,
.create_opts = &qcow2_create_opts,
+ .strong_runtime_opts = qcow2_strong_runtime_opts,
.bdrv_co_check = qcow2_co_check,
.bdrv_amend_options = qcow2_amend_options,