block/qcow2-threads.c | 80 ++++++++++++++++++++++++++------ block/qcow2.c | 22 ++++++++- block/qcow2.h | 8 +++- include/block/block_int-common.h | 1 + qapi/block-core.json | 29 ++++++------ 5 files changed, 108 insertions(+), 32 deletions(-)
This patch introduces a new parameter to customize the compression level
in qemu-img convert.
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1959
Signed-off-by: Yifan Zhao <zhaoyifan@sjtu.edu.cn>
---
This patch adds a new compression_level option to qemu-img: convert,
allowing the user to specify the compression level to use.
It resolves an existing issue referenced in the commit message.
However, as a newbie to qemu, I'm not quite sure how to pass this new option
from qcow2_co_create to the compression function qcow2_co_do_compress.
Currently, the compression_level is written directly into the header of the
qcow2 file, which I don't think is appropriate.
I would appreciate getting more suggestions on how to implement this better.
block/qcow2-threads.c | 80 ++++++++++++++++++++++++++------
block/qcow2.c | 22 ++++++++-
block/qcow2.h | 8 +++-
include/block/block_int-common.h | 1 +
qapi/block-core.json | 29 ++++++------
5 files changed, 108 insertions(+), 32 deletions(-)
diff --git a/block/qcow2-threads.c b/block/qcow2-threads.c
index d6071a1eae..bc375384a9 100644
--- a/block/qcow2-threads.c
+++ b/block/qcow2-threads.c
@@ -67,7 +67,27 @@ qcow2_co_process(BlockDriverState *bs, ThreadPoolFunc *func, void *arg)
*/
typedef ssize_t (*Qcow2CompressFunc)(void *dest, size_t dest_size,
+ const void *src, size_t src_size,
+ int compression_level);
+typedef ssize_t (*Qcow2DecompressFunc)(void *dest, size_t dest_size,
const void *src, size_t src_size);
+
+enum Qcow2CompressFuncType {
+ QCOW2_COMPRESS_FUNC,
+ QCOW2_DECOMPRESS_FUNC,
+};
+
+typedef struct Qcow2CompressFuncUnion {
+ enum Qcow2CompressFuncType type;
+ union {
+ struct {
+ Qcow2CompressFunc f;
+ int compression_level;
+ } cfunc;
+ Qcow2DecompressFunc dfunc;
+ } u;
+} Qcow2CompressFuncUnion;
+
typedef struct Qcow2CompressData {
void *dest;
size_t dest_size;
@@ -75,7 +95,7 @@ typedef struct Qcow2CompressData {
size_t src_size;
ssize_t ret;
- Qcow2CompressFunc func;
+ Qcow2CompressFuncUnion func;
} Qcow2CompressData;
/*
@@ -85,20 +105,26 @@ typedef struct Qcow2CompressData {
*
* @dest - destination buffer, @dest_size bytes
* @src - source buffer, @src_size bytes
+ * @level - compression level
*
* Returns: compressed size on success
* -ENOMEM destination buffer is not enough to store compressed data
* -EIO on any other error
*/
static ssize_t qcow2_zlib_compress(void *dest, size_t dest_size,
- const void *src, size_t src_size)
+ const void *src, size_t src_size, int level)
{
ssize_t ret;
z_stream strm;
+ if (level == DEFAULT_COMPRESSION_LEVEL ||
+ level < Z_BEST_SPEED || level > Z_BEST_COMPRESSION) {
+ level = Z_DEFAULT_COMPRESSION;
+ }
+
/* best compression, small window, no zlib header */
memset(&strm, 0, sizeof(strm));
- ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ ret = deflateInit2(&strm, level, Z_DEFLATED,
-12, 9, Z_DEFAULT_STRATEGY);
if (ret != Z_OK) {
return -EIO;
@@ -180,13 +206,14 @@ static ssize_t qcow2_zlib_decompress(void *dest, size_t dest_size,
*
* @dest - destination buffer, @dest_size bytes
* @src - source buffer, @src_size bytes
+ * @level - compression level
*
* Returns: compressed size on success
* -ENOMEM destination buffer is not enough to store compressed data
* -EIO on any other error
*/
static ssize_t qcow2_zstd_compress(void *dest, size_t dest_size,
- const void *src, size_t src_size)
+ const void *src, size_t src_size, int level)
{
ssize_t ret;
size_t zstd_ret;
@@ -200,11 +227,22 @@ static ssize_t qcow2_zstd_compress(void *dest, size_t dest_size,
.size = src_size,
.pos = 0
};
- ZSTD_CCtx *cctx = ZSTD_createCCtx();
+ ZSTD_CCtx *cctx;
+ if (level == DEFAULT_COMPRESSION_LEVEL ||
+ level < ZSTD_minCLevel() || level > ZSTD_maxCLevel()) {
+ level = ZSTD_CLEVEL_DEFAULT;
+ }
+
+ cctx = ZSTD_createCCtx();
if (!cctx) {
return -EIO;
}
+
+ zstd_ret = ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, level);
+ if (ZSTD_isError(zstd_ret)) {
+ return -EIO;
+ }
/*
* Use the zstd streamed interface for symmetry with decompression,
* where streaming is essential since we don't record the exact
@@ -329,15 +367,22 @@ static int qcow2_compress_pool_func(void *opaque)
{
Qcow2CompressData *data = opaque;
- data->ret = data->func(data->dest, data->dest_size,
- data->src, data->src_size);
+ if (data->func.type == QCOW2_COMPRESS_FUNC) {
+ data->ret = data->func.u.cfunc.f(data->dest, data->dest_size,
+ data->src, data->src_size,
+ data->func.u.cfunc.compression_level);
+ } else {
+ data->ret = data->func.u.dfunc(data->dest, data->dest_size,
+ data->src, data->src_size);
+ }
return 0;
}
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)
+ const void *src, size_t src_size,
+ Qcow2CompressFuncUnion func)
{
Qcow2CompressData arg = {
.dest = dest,
@@ -366,19 +411,22 @@ qcow2_co_do_compress(BlockDriverState *bs, void *dest, size_t dest_size,
*/
ssize_t coroutine_fn
qcow2_co_compress(BlockDriverState *bs, void *dest, size_t dest_size,
- const void *src, size_t src_size)
+ const void *src, size_t src_size, int compression_level)
{
BDRVQcow2State *s = bs->opaque;
- Qcow2CompressFunc fn;
+ Qcow2CompressFuncUnion fn;
+
+ fn.type = QCOW2_COMPRESS_FUNC;
+ fn.u.cfunc.compression_level = compression_level;
switch (s->compression_type) {
case QCOW2_COMPRESSION_TYPE_ZLIB:
- fn = qcow2_zlib_compress;
+ fn.u.cfunc.f = qcow2_zlib_compress;
break;
#ifdef CONFIG_ZSTD
case QCOW2_COMPRESSION_TYPE_ZSTD:
- fn = qcow2_zstd_compress;
+ fn.u.cfunc.f = qcow2_zstd_compress;
break;
#endif
default:
@@ -406,16 +454,18 @@ qcow2_co_decompress(BlockDriverState *bs, void *dest, size_t dest_size,
const void *src, size_t src_size)
{
BDRVQcow2State *s = bs->opaque;
- Qcow2CompressFunc fn;
+ Qcow2CompressFuncUnion fn;
+
+ fn.type = QCOW2_DECOMPRESS_FUNC;
switch (s->compression_type) {
case QCOW2_COMPRESSION_TYPE_ZLIB:
- fn = qcow2_zlib_decompress;
+ fn.u.dfunc = qcow2_zlib_decompress;
break;
#ifdef CONFIG_ZSTD
case QCOW2_COMPRESSION_TYPE_ZSTD:
- fn = qcow2_zstd_decompress;
+ fn.u.dfunc = qcow2_zstd_decompress;
break;
#endif
default:
diff --git a/block/qcow2.c b/block/qcow2.c
index 13e032bd5e..e8d6b84d7f 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -1448,6 +1448,12 @@ qcow2_do_open(BlockDriverState *bs, QDict *options, int flags,
goto fail;
}
+ if (header.header_length > offsetof(QCowHeader, compression_level)) {
+ s->compression_level = header.compression_level;
+ } else {
+ s->compression_level = DEFAULT_COMPRESSION_LEVEL;
+ }
+
if (s->incompatible_features & ~QCOW2_INCOMPAT_MASK) {
void *feature_table = NULL;
qcow2_read_extensions(bs, header.header_length, ext_end,
@@ -2958,6 +2964,7 @@ int qcow2_update_header(BlockDriverState *bs)
.refcount_order = cpu_to_be32(s->refcount_order),
.header_length = cpu_to_be32(header_length),
.compression_type = s->compression_type,
+ .compression_level = s->compression_level,
};
/* For older versions, write a shorter header */
@@ -3508,6 +3515,7 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
uint64_t *refcount_table;
int ret;
uint8_t compression_type = QCOW2_COMPRESSION_TYPE_ZLIB;
+ uint8_t compression_level = DEFAULT_COMPRESSION_LEVEL;
assert(create_options->driver == BLOCKDEV_DRIVER_QCOW2);
qcow2_opts = &create_options->u.qcow2;
@@ -3686,6 +3694,10 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
compression_type = qcow2_opts->compression_type;
}
+ if (qcow2_opts->has_compression_level) {
+ compression_level = qcow2_opts->compression_level;
+ }
+
/* Create BlockBackend to write to the image */
blk = blk_co_new_with_bs(bs, BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL,
errp);
@@ -3710,6 +3722,7 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp)
.refcount_order = cpu_to_be32(refcount_order),
/* don't deal with endianness since compression_type is 1 byte long */
.compression_type = compression_type,
+ .compression_level = compression_level,
.header_length = cpu_to_be32(sizeof(*header)),
};
@@ -3930,6 +3943,7 @@ qcow2_co_create_opts(BlockDriver *drv, const char *filename, QemuOpts *opts,
{ BLOCK_OPT_COMPAT_LEVEL, "version" },
{ BLOCK_OPT_DATA_FILE_RAW, "data-file-raw" },
{ BLOCK_OPT_COMPRESSION_TYPE, "compression-type" },
+ { BLOCK_OPT_COMPRESSION_LEVEL, "compression-level"},
{ NULL, NULL },
};
@@ -4679,7 +4693,7 @@ qcow2_co_pwritev_compressed_task(BlockDriverState *bs,
out_buf = g_malloc(s->cluster_size);
out_len = qcow2_co_compress(bs, out_buf, s->cluster_size - 1,
- buf, s->cluster_size);
+ buf, s->cluster_size, s->compression_level);
if (out_len == -ENOMEM) {
/* could not compress: write normal cluster */
ret = qcow2_co_pwritev_part(bs, offset, bytes, qiov, qiov_offset, 0);
@@ -6098,6 +6112,12 @@ static QemuOptsList qcow2_create_opts = {
"compression", \
.def_value_str = "zlib" \
},
+ {
+ .name = BLOCK_OPT_COMPRESSION_LEVEL,
+ .type = QEMU_OPT_NUMBER,
+ .help = "Compression level used for image cluster "
+ "compression"
+ },
QCOW_COMMON_OPTIONS,
{ /* end of list */ }
}
diff --git a/block/qcow2.h b/block/qcow2.h
index a9e3481c6e..771f95cd9c 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -128,6 +128,8 @@
#define DEFAULT_CLUSTER_SIZE 65536
+#define DEFAULT_COMPRESSION_LEVEL INT8_MAX
+
#define QCOW2_OPT_DATA_FILE "data-file"
#define QCOW2_OPT_LAZY_REFCOUNTS "lazy-refcounts"
#define QCOW2_OPT_DISCARD_REQUEST "pass-discard-request"
@@ -176,9 +178,10 @@ typedef struct QCowHeader {
/* Additional fields */
uint8_t compression_type;
+ uint8_t compression_level;
/* header must be a multiple of 8 */
- uint8_t padding[7];
+ uint8_t padding[6];
} QEMU_PACKED QCowHeader;
QEMU_BUILD_BUG_ON(!QEMU_IS_ALIGNED(sizeof(QCowHeader), 8));
@@ -422,6 +425,7 @@ typedef struct BDRVQcow2State {
* is to convert the image with the desired compression type set.
*/
Qcow2CompressionType compression_type;
+ int compression_level;
} BDRVQcow2State;
typedef struct Qcow2COWRegion {
@@ -1060,7 +1064,7 @@ uint64_t qcow2_get_persistent_dirty_bitmap_size(BlockDriverState *bs,
ssize_t coroutine_fn
qcow2_co_compress(BlockDriverState *bs, void *dest, size_t dest_size,
- const void *src, size_t src_size);
+ const void *src, size_t src_size, int compression_level);
ssize_t coroutine_fn
qcow2_co_decompress(BlockDriverState *bs, void *dest, size_t dest_size,
const void *src, size_t src_size);
diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h
index 4e31d161c5..bf947b8cba 100644
--- a/include/block/block_int-common.h
+++ b/include/block/block_int-common.h
@@ -56,6 +56,7 @@
#define BLOCK_OPT_DATA_FILE "data_file"
#define BLOCK_OPT_DATA_FILE_RAW "data_file_raw"
#define BLOCK_OPT_COMPRESSION_TYPE "compression_type"
+#define BLOCK_OPT_COMPRESSION_LEVEL "compression_level"
#define BLOCK_OPT_EXTL2 "extended_l2"
#define BLOCK_PROBE_BUF_SIZE 512
diff --git a/qapi/block-core.json b/qapi/block-core.json
index ca390c5700..1957ab6d92 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -5083,20 +5083,21 @@
# Since: 2.12
##
{ 'struct': 'BlockdevCreateOptionsQcow2',
- 'data': { 'file': 'BlockdevRef',
- '*data-file': 'BlockdevRef',
- '*data-file-raw': 'bool',
- '*extended-l2': 'bool',
- 'size': 'size',
- '*version': 'BlockdevQcow2Version',
- '*backing-file': 'str',
- '*backing-fmt': 'BlockdevDriver',
- '*encrypt': 'QCryptoBlockCreateOptions',
- '*cluster-size': 'size',
- '*preallocation': 'PreallocMode',
- '*lazy-refcounts': 'bool',
- '*refcount-bits': 'int',
- '*compression-type':'Qcow2CompressionType' } }
+ 'data': { 'file': 'BlockdevRef',
+ '*data-file': 'BlockdevRef',
+ '*data-file-raw': 'bool',
+ '*extended-l2': 'bool',
+ 'size': 'size',
+ '*version': 'BlockdevQcow2Version',
+ '*backing-file': 'str',
+ '*backing-fmt': 'BlockdevDriver',
+ '*encrypt': 'QCryptoBlockCreateOptions',
+ '*cluster-size': 'size',
+ '*preallocation': 'PreallocMode',
+ '*lazy-refcounts': 'bool',
+ '*refcount-bits': 'int',
+ '*compression-type': 'Qcow2CompressionType',
+ '*compression-level':'int' } }
##
# @BlockdevCreateOptionsQed:
--
2.43.0
© 2016 - 2024 Red Hat, Inc.