From patchwork Wed Sep 4 15:29:13 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Denis Plotnikov X-Patchwork-Id: 11130949 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 1FF69112C for ; Wed, 4 Sep 2019 15:32:55 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id E4CB320449 for ; Wed, 4 Sep 2019 15:32:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org E4CB320449 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=virtuozzo.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Received: from localhost ([::1]:34522 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1i5XHB-00007I-3i for patchwork-qemu-devel@patchwork.kernel.org; Wed, 04 Sep 2019 11:32:53 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:60386) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1i5XDr-0005u2-On for qemu-devel@nongnu.org; Wed, 04 Sep 2019 11:29:29 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1i5XDp-000312-L8 for qemu-devel@nongnu.org; Wed, 04 Sep 2019 11:29:27 -0400 Received: from relay.sw.ru ([185.231.240.75]:60744) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1i5XDp-0002zs-9b; Wed, 04 Sep 2019 11:29:25 -0400 Received: from [10.94.4.71] (helo=dptest2.qa.sw.ru) by relay.sw.ru with esmtp (Exim 4.92) (envelope-from ) id 1i5XDm-0000Me-Or; Wed, 04 Sep 2019 18:29:22 +0300 From: Denis Plotnikov To: armbru@redhat.com, qemu-devel@nongnu.org Date: Wed, 4 Sep 2019 18:29:13 +0300 Message-Id: <20190904152915.30755-2-dplotnikov@virtuozzo.com> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20190904152915.30755-1-dplotnikov@virtuozzo.com> References: <20190904152915.30755-1-dplotnikov@virtuozzo.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 185.231.240.75 Subject: [Qemu-devel] [PATCH v5 1/3] qcow2: introduce compression type feature X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, vsementsov@virtuozzo.com, den@virtuozzo.com, qemu-block@nongnu.org, mreitz@redhat.com Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" The patch adds some preparation parts for incompatible compression type feature to QCOW2 header that indicates that *all* compressed clusters must be (de)compressed using a certain compression type. It is implied that the compression type is set on the image creation and can be changed only later by image conversion, thus compression type defines the only compression algorithm used for the image. The goal of the feature is to add support of other compression algorithms to qcow2. For example, ZSTD which is more effective on compression than ZLIB. It works roughly 2x faster than ZLIB providing a comparable compression ratio and therefore provides a performance advantage in backup scenarios. The default compression is ZLIB. Images created with ZLIB compression type are backward compatible with older qemu versions. Signed-off-by: Denis Plotnikov Reviewed-by: Eric Blake --- block/qcow2.c | 91 +++++++++++++++++++++++++++++++++++++++ block/qcow2.h | 26 ++++++++--- docs/interop/qcow2.txt | 19 +++++++- include/block/block_int.h | 1 + qapi/block-core.json | 22 +++++++++- 5 files changed, 149 insertions(+), 10 deletions(-) diff --git a/block/qcow2.c b/block/qcow2.c index 039bdc2f7e..2884b9d9f2 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1197,6 +1197,32 @@ static int qcow2_update_options(BlockDriverState *bs, QDict *options, return ret; } +static int check_compression_type(BDRVQcow2State *s, Error **errp) +{ + switch (s->compression_type) { + case QCOW2_COMPRESSION_TYPE_ZLIB: + break; + + default: + error_setg(errp, "qcow2: unknown compression type: %u", + s->compression_type); + return -ENOTSUP; + } + + /* + * if the compression type differs from QCOW2_COMPRESSION_TYPE_ZLIB + * the incompatible feature flag must be set + */ + + if (s->compression_type != QCOW2_COMPRESSION_TYPE_ZLIB && + !(s->incompatible_features & QCOW2_INCOMPAT_COMPRESSION_TYPE)) { + error_setg(errp, "qcow2: Invalid compression type setting"); + return -EINVAL; + } + + return 0; +} + /* Called with s->lock held. */ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options, int flags, Error **errp) @@ -1312,6 +1338,35 @@ static int coroutine_fn qcow2_do_open(BlockDriverState *bs, QDict *options, s->compatible_features = header.compatible_features; s->autoclear_features = header.autoclear_features; + /* + * Handle compression type + * Older qcow2 images don't contain the compression type header. + * Distinguish them by the header length and use + * the only valid (default) compression type in that case + */ + if (header.header_length > offsetof(QCowHeader, compression_type)) { + /* sanity check that we can read a compression type */ + size_t min_len = offsetof(QCowHeader, compression_type) + + sizeof(header.compression_type); + if (header.header_length < min_len) { + error_setg(errp, + "Could not read compression type, " + "qcow2 header is too short"); + ret = -EINVAL; + goto fail; + } + + header.compression_type = be32_to_cpu(header.compression_type); + s->compression_type = header.compression_type; + } else { + s->compression_type = QCOW2_COMPRESSION_TYPE_ZLIB; + } + + ret = check_compression_type(s, errp); + if (ret) { + goto fail; + } + if (s->incompatible_features & ~QCOW2_INCOMPAT_MASK) { void *feature_table = NULL; qcow2_read_extensions(bs, header.header_length, ext_end, @@ -2516,6 +2571,12 @@ int qcow2_update_header(BlockDriverState *bs) total_size = bs->total_sectors * BDRV_SECTOR_SIZE; refcount_table_clusters = s->refcount_table_size >> (s->cluster_bits - 3); + ret = check_compression_type(s, NULL); + + if (ret) { + goto fail; + } + *header = (QCowHeader) { /* Version 2 fields */ .magic = cpu_to_be32(QCOW_MAGIC), @@ -2538,6 +2599,7 @@ int qcow2_update_header(BlockDriverState *bs) .autoclear_features = cpu_to_be64(s->autoclear_features), .refcount_order = cpu_to_be32(s->refcount_order), .header_length = cpu_to_be32(header_length), + .compression_type = cpu_to_be32(s->compression_type), }; /* For older versions, write a shorter header */ @@ -2635,6 +2697,11 @@ int qcow2_update_header(BlockDriverState *bs) .bit = QCOW2_COMPAT_LAZY_REFCOUNTS_BITNR, .name = "lazy refcounts", }, + { + .type = QCOW2_FEAT_TYPE_INCOMPATIBLE, + .bit = QCOW2_INCOMPAT_COMPRESSION_TYPE_BITNR, + .name = "compression type", + }, }; ret = header_ext_add(buf, QCOW2_EXT_MAGIC_FEATURE_TABLE, @@ -3202,6 +3269,7 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp) .refcount_table_offset = cpu_to_be64(cluster_size), .refcount_table_clusters = cpu_to_be32(1), .refcount_order = cpu_to_be32(refcount_order), + .compression_type = cpu_to_be32(QCOW2_COMPRESSION_TYPE_ZLIB), .header_length = cpu_to_be32(sizeof(*header)), }; @@ -3221,6 +3289,21 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp) cpu_to_be64(QCOW2_AUTOCLEAR_DATA_FILE_RAW); } + if (qcow2_opts->has_compression_type && + qcow2_opts->compression_type != QCOW2_COMPRESSION_TYPE_ZLIB) { + + switch (qcow2_opts->compression_type) { + default: + error_setg_errno(errp, -EINVAL, "Unknown compression type"); + goto out; + } + + header->compression_type = cpu_to_be32(qcow2_opts->compression_type); + + header->incompatible_features |= + cpu_to_be64(QCOW2_INCOMPAT_COMPRESSION_TYPE); + } + ret = blk_pwrite(blk, 0, header, cluster_size, 0); g_free(header); if (ret < 0) { @@ -3402,6 +3485,7 @@ static int coroutine_fn qcow2_co_create_opts(const char *filename, QemuOpts *opt { BLOCK_OPT_ENCRYPT, BLOCK_OPT_ENCRYPT_FORMAT }, { BLOCK_OPT_COMPAT_LEVEL, "version" }, { BLOCK_OPT_DATA_FILE_RAW, "data-file-raw" }, + { BLOCK_OPT_COMPRESSION_TYPE, "compression-type" }, { NULL, NULL }, }; @@ -4598,6 +4682,7 @@ static ImageInfoSpecific *qcow2_get_specific_info(BlockDriverState *bs, .data_file = g_strdup(s->image_data_file), .has_data_file_raw = has_data_file(bs), .data_file_raw = data_file_is_raw(bs), + .compression_type = s->compression_type, }; } else { /* if this assertion fails, this probably means a new version was @@ -5163,6 +5248,12 @@ static QemuOptsList qcow2_create_opts = { .help = "Width of a reference count entry in bits", .def_value_str = "16" }, + { + .name = BLOCK_OPT_COMPRESSION_TYPE, + .type = QEMU_OPT_STRING, + .help = "Compression method used for image clusters compression", + .def_value_str = "zlib" + }, { /* end of list */ } } }; diff --git a/block/qcow2.h b/block/qcow2.h index fc1b0d3c1e..659e4f852a 100644 --- a/block/qcow2.h +++ b/block/qcow2.h @@ -140,6 +140,7 @@ typedef struct QCowHeader { uint32_t refcount_order; uint32_t header_length; + uint32_t compression_type; } QEMU_PACKED QCowHeader; typedef struct QEMU_PACKED QCowSnapshotHeader { @@ -203,16 +204,20 @@ enum { /* Incompatible feature bits */ enum { - QCOW2_INCOMPAT_DIRTY_BITNR = 0, - QCOW2_INCOMPAT_CORRUPT_BITNR = 1, - QCOW2_INCOMPAT_DATA_FILE_BITNR = 2, - QCOW2_INCOMPAT_DIRTY = 1 << QCOW2_INCOMPAT_DIRTY_BITNR, - QCOW2_INCOMPAT_CORRUPT = 1 << QCOW2_INCOMPAT_CORRUPT_BITNR, - QCOW2_INCOMPAT_DATA_FILE = 1 << QCOW2_INCOMPAT_DATA_FILE_BITNR, + QCOW2_INCOMPAT_DIRTY_BITNR = 0, + QCOW2_INCOMPAT_CORRUPT_BITNR = 1, + QCOW2_INCOMPAT_DATA_FILE_BITNR = 2, + QCOW2_INCOMPAT_COMPRESSION_TYPE_BITNR = 3, + QCOW2_INCOMPAT_DIRTY = 1 << QCOW2_INCOMPAT_DIRTY_BITNR, + QCOW2_INCOMPAT_CORRUPT = 1 << QCOW2_INCOMPAT_CORRUPT_BITNR, + QCOW2_INCOMPAT_DATA_FILE = 1 << QCOW2_INCOMPAT_DATA_FILE_BITNR, + QCOW2_INCOMPAT_COMPRESSION_TYPE = + 1 << QCOW2_INCOMPAT_COMPRESSION_TYPE_BITNR, QCOW2_INCOMPAT_MASK = QCOW2_INCOMPAT_DIRTY | QCOW2_INCOMPAT_CORRUPT - | QCOW2_INCOMPAT_DATA_FILE, + | QCOW2_INCOMPAT_DATA_FILE + | QCOW2_INCOMPAT_COMPRESSION_TYPE, }; /* Compatible feature bits */ @@ -359,6 +364,13 @@ typedef struct BDRVQcow2State { bool metadata_preallocation_checked; bool metadata_preallocation; + /* + * Compression type used for the image. Default: 0 - ZLIB + * The image compression type is set on image creation. + * The only way to change the compression type is to convert the image + * with the desired compression type set + */ + Qcow2CompressionType compression_type; } BDRVQcow2State; typedef struct Qcow2COWRegion { diff --git a/docs/interop/qcow2.txt b/docs/interop/qcow2.txt index af5711e533..e1be8bd5c3 100644 --- a/docs/interop/qcow2.txt +++ b/docs/interop/qcow2.txt @@ -109,7 +109,12 @@ in the description of a field. An External Data File Name header extension may be present if this bit is set. - Bits 3-63: Reserved (set to 0) + Bit 3: Compression type bit. The bit must be set if + the compression type differs from default of zlib. + If the compression type is default the bit should + be unset. + + Bits 4-63: Reserved (set to 0) 80 - 87: compatible_features Bitmask of compatible features. An implementation can @@ -165,6 +170,18 @@ in the description of a field. Length of the header structure in bytes. For version 2 images, the length is always assumed to be 72 bytes. + 104 - 107: compression_type + Defines the compression method used for compressed clusters. + A single compression type is applied to all compressed image + clusters. + The compression type is set on image creation only. + The default compression type is zlib (value: 0). + When the compression type differs from the default + the compression type bit (incompatible feature bit 3) + must be set. + Available compression type values: + 0: zlib (default) + Directly after the image header, optional sections called header extensions can be stored. Each extension has a structure like the following: diff --git a/include/block/block_int.h b/include/block/block_int.h index 3aa1e832a8..4b254802e5 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -58,6 +58,7 @@ #define BLOCK_OPT_REFCOUNT_BITS "refcount_bits" #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_PROBE_BUF_SIZE 512 diff --git a/qapi/block-core.json b/qapi/block-core.json index 0d43d4f37c..2c002ca6a9 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -78,6 +78,8 @@ # # @bitmaps: A list of qcow2 bitmap details (since 4.0) # +# @compression-type: the image cluster compression method (since 4.2) +# # Since: 1.7 ## { 'struct': 'ImageInfoSpecificQCow2', @@ -89,7 +91,8 @@ '*corrupt': 'bool', 'refcount-bits': 'int', '*encrypt': 'ImageInfoSpecificQCow2Encryption', - '*bitmaps': ['Qcow2BitmapInfo'] + '*bitmaps': ['Qcow2BitmapInfo'], + 'compression-type': 'Qcow2CompressionType' } } ## @@ -4274,6 +4277,18 @@ 'data': [ 'v2', 'v3' ] } +## +# @Qcow2CompressionType: +# +# Compression type used in qcow2 image file +# +# @zlib: zlib compression, see +# +# Since: 4.2 +## +{ 'enum': 'Qcow2CompressionType', + 'data': [ 'zlib' ] } + ## # @BlockdevCreateOptionsQcow2: # @@ -4297,6 +4312,8 @@ # allowed values: off, falloc, full, metadata) # @lazy-refcounts True if refcounts may be updated lazily (default: off) # @refcount-bits Width of reference counts in bits (default: 16) +# @compression-type The image cluster compression method +# (default: zlib, since 4.2) # # Since: 2.12 ## @@ -4312,7 +4329,8 @@ '*cluster-size': 'size', '*preallocation': 'PreallocMode', '*lazy-refcounts': 'bool', - '*refcount-bits': 'int' } } + '*refcount-bits': 'int', + '*compression-type': 'Qcow2CompressionType' } } ## # @BlockdevCreateOptionsQed: From patchwork Wed Sep 4 15:29:14 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Denis Plotnikov X-Patchwork-Id: 11130945 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 9BCF9112C for ; Wed, 4 Sep 2019 15:32:46 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 7A66F208E4 for ; Wed, 4 Sep 2019 15:32:46 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 7A66F208E4 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=virtuozzo.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Received: from localhost ([::1]:34518 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1i5XGz-000840-NW for patchwork-qemu-devel@patchwork.kernel.org; Wed, 04 Sep 2019 11:32:42 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:60366) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1i5XDr-0005sk-0w for qemu-devel@nongnu.org; Wed, 04 Sep 2019 11:29:28 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1i5XDp-00030i-Fi for qemu-devel@nongnu.org; Wed, 04 Sep 2019 11:29:26 -0400 Received: from relay.sw.ru ([185.231.240.75]:60750) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1i5XDp-0002zt-81; Wed, 04 Sep 2019 11:29:25 -0400 Received: from [10.94.4.71] (helo=dptest2.qa.sw.ru) by relay.sw.ru with esmtp (Exim 4.92) (envelope-from ) id 1i5XDm-0000Me-S1; Wed, 04 Sep 2019 18:29:22 +0300 From: Denis Plotnikov To: armbru@redhat.com, qemu-devel@nongnu.org Date: Wed, 4 Sep 2019 18:29:14 +0300 Message-Id: <20190904152915.30755-3-dplotnikov@virtuozzo.com> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20190904152915.30755-1-dplotnikov@virtuozzo.com> References: <20190904152915.30755-1-dplotnikov@virtuozzo.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 185.231.240.75 Subject: [Qemu-devel] [PATCH v5 2/3] qcow2: rework the cluster compression routine X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, vsementsov@virtuozzo.com, den@virtuozzo.com, qemu-block@nongnu.org, mreitz@redhat.com Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" The patch allow to process image compression type defined in the image header and choose an appropriate method for image clusters (de)compression. Signed-off-by: Denis Plotnikov Reviewed-by: Eric Blake --- block/qcow2-threads.c | 77 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/block/qcow2-threads.c b/block/qcow2-threads.c index 3b1e63fe41..ebeef9e568 100644 --- a/block/qcow2-threads.c +++ b/block/qcow2-threads.c @@ -74,7 +74,9 @@ typedef struct Qcow2CompressData { } Qcow2CompressData; /* - * qcow2_compress() + * qcow2_zlib_compress() + * + * Compress @src_size bytes of data using zlib compression method * * @dest - destination buffer, @dest_size bytes * @src - source buffer, @src_size bytes @@ -83,8 +85,8 @@ typedef struct Qcow2CompressData { * -ENOMEM destination buffer is not enough to store compressed data * -EIO on any other error */ -static ssize_t qcow2_compress(void *dest, size_t dest_size, - const void *src, size_t src_size) +static ssize_t qcow2_zlib_compress(void *dest, size_t dest_size, + const void *src, size_t src_size) { ssize_t ret; z_stream strm; @@ -119,19 +121,19 @@ static ssize_t qcow2_compress(void *dest, size_t dest_size, } /* - * qcow2_decompress() + * qcow2_zlib_decompress() * * Decompress some data (not more than @src_size bytes) to produce exactly - * @dest_size bytes. + * @dest_size bytes using zlib compression method * * @dest - destination buffer, @dest_size bytes * @src - source buffer, @src_size bytes * * Returns: 0 on success - * -1 on fail + * -EIO on failure */ -static ssize_t qcow2_decompress(void *dest, size_t dest_size, - const void *src, size_t src_size) +static ssize_t qcow2_zlib_decompress(void *dest, size_t dest_size, + const void *src, size_t src_size) { int ret = 0; z_stream strm; @@ -144,7 +146,7 @@ static ssize_t qcow2_decompress(void *dest, size_t dest_size, ret = inflateInit2(&strm, -12); if (ret != Z_OK) { - return -1; + return -EIO; } ret = inflate(&strm, Z_FINISH); @@ -154,7 +156,7 @@ static ssize_t qcow2_decompress(void *dest, size_t dest_size, * @src buffer may be processed partly (because in qcow2 we know size of * compressed data with precision of one sector) */ - ret = -1; + ret = -EIO; } inflateEnd(&strm); @@ -189,20 +191,67 @@ qcow2_co_do_compress(BlockDriverState *bs, void *dest, size_t dest_size, return arg.ret; } +/* + * qcow2_co_compress() + * + * Compress @src_size bytes of data using the compression + * method defined by the image compression type + * + * @dest - destination buffer, @dest_size bytes + * @src - source buffer, @src_size bytes + * + * Returns: 0 on success + * a negative error code on failure + */ 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); + BDRVQcow2State *s = bs->opaque; + Qcow2CompressFunc fn; + + switch (s->compression_type) { + case QCOW2_COMPRESSION_TYPE_ZLIB: + fn = qcow2_zlib_compress; + break; + + default: + return -ENOTSUP; + } + + return qcow2_co_do_compress(bs, dest, dest_size, src, src_size, fn); } +/* + * qcow2_co_decompress() + * + * Decompress some data (not more than @src_size bytes) to produce exactly + * @dest_size bytes using the compression method defined by the image + * compression type + * + * @dest - destination buffer, @dest_size bytes + * @src - source buffer, @src_size bytes + * + * Returns: 0 on success + * a negative error code on failure + */ 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); + BDRVQcow2State *s = bs->opaque; + Qcow2CompressFunc fn; + + switch (s->compression_type) { + case QCOW2_COMPRESSION_TYPE_ZLIB: + fn = qcow2_zlib_decompress; + break; + + default: + return -ENOTSUP; + } + + return qcow2_co_do_compress(bs, dest, dest_size, src, src_size, fn); } From patchwork Wed Sep 4 15:29:15 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Denis Plotnikov X-Patchwork-Id: 11130947 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 68B03112C for ; Wed, 4 Sep 2019 15:32:50 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 49942208E4 for ; Wed, 4 Sep 2019 15:32:50 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 49942208E4 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=virtuozzo.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Received: from localhost ([::1]:34520 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1i5XH6-00087p-Cm for patchwork-qemu-devel@patchwork.kernel.org; Wed, 04 Sep 2019 11:32:48 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:60389) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1i5XDr-0005uC-Sh for qemu-devel@nongnu.org; Wed, 04 Sep 2019 11:29:29 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1i5XDp-00030q-Gg for qemu-devel@nongnu.org; Wed, 04 Sep 2019 11:29:27 -0400 Received: from relay.sw.ru ([185.231.240.75]:60756) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1i5XDp-0002zu-8Z; Wed, 04 Sep 2019 11:29:25 -0400 Received: from [10.94.4.71] (helo=dptest2.qa.sw.ru) by relay.sw.ru with esmtp (Exim 4.92) (envelope-from ) id 1i5XDn-0000Me-0L; Wed, 04 Sep 2019 18:29:23 +0300 From: Denis Plotnikov To: armbru@redhat.com, qemu-devel@nongnu.org Date: Wed, 4 Sep 2019 18:29:15 +0300 Message-Id: <20190904152915.30755-4-dplotnikov@virtuozzo.com> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20190904152915.30755-1-dplotnikov@virtuozzo.com> References: <20190904152915.30755-1-dplotnikov@virtuozzo.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 185.231.240.75 Subject: [Qemu-devel] [PATCH v5 3/3] qcow2: add zstd cluster compression X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: kwolf@redhat.com, vsementsov@virtuozzo.com, den@virtuozzo.com, qemu-block@nongnu.org, mreitz@redhat.com Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" zstd significantly reduces cluster compression time. It provides better compression performance maintaining the same level of compression ratio in comparison with zlib, which, at the moment, has been the only compression method available. The performance test results: Test compresses and decompresses qemu qcow2 image with just installed rhel-7.6 guest. Image cluster size: 64K. Image on disk size: 2.2G The test was conducted with brd disk to reduce the influence of disk subsystem to the test results. The results is given in seconds. compress cmd: time ./qemu-img convert -O qcow2 -c -o compression_type=[zlib|zstd] src.img [zlib|zstd]_compressed.img decompress cmd time ./qemu-img convert -O qcow2 [zlib|zstd]_compressed.img uncompressed.img compression decompression zlib zstd zlib zstd ------------------------------------------------------------ real 65.5 16.3 (-75 %) 1.9 1.6 (-16 %) user 65.0 15.8 5.3 2.5 sys 3.3 0.2 2.0 2.0 Both ZLIB and ZSTD gave the same compression ratio: 1.57 compressed image size in both cases: 1.4G Signed-off-by: Denis Plotnikov --- block/qcow2-threads.c | 107 +++++++++++++++++++++++++++++++++++++++++ block/qcow2.c | 7 +++ configure | 29 +++++++++++ docs/interop/qcow2.txt | 20 ++++++++ qapi/block-core.json | 3 +- 5 files changed, 165 insertions(+), 1 deletion(-) diff --git a/block/qcow2-threads.c b/block/qcow2-threads.c index ebeef9e568..b5aad202bf 100644 --- a/block/qcow2-threads.c +++ b/block/qcow2-threads.c @@ -28,6 +28,11 @@ #define ZLIB_CONST #include +#ifdef CONFIG_ZSTD +#include +#include +#endif + #include "qcow2.h" #include "block/thread-pool.h" #include "crypto.h" @@ -164,6 +169,98 @@ static ssize_t qcow2_zlib_decompress(void *dest, size_t dest_size, return ret; } +#ifdef CONFIG_ZSTD +/* + * qcow2_zstd_compress() + * + * Compress @src_size bytes of data using zstd compression method + * + * @dest - destination buffer, @dest_size bytes + * @src - source buffer, @src_size bytes + * + * 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) +{ + ssize_t ret; + uint32_t *c_size = dest; + /* steal some bytes to store compressed chunk size */ + char *d_buf = ((char *) dest) + sizeof(*c_size); + + /* sanity check that we can store the compressed data length */ + if (dest_size < sizeof(*c_size)) { + return -ENOMEM; + } + + dest_size -= sizeof(*c_size); + + ret = ZSTD_compress(d_buf, dest_size, src, src_size, 5); + + if (ZSTD_isError(ret)) { + if (ZSTD_getErrorCode(ret) == ZSTD_error_dstSize_tooSmall) { + return -ENOMEM; + } else { + return -EIO; + } + } + + /* store the compressed chunk size in the very beginning of the buffer */ + *c_size = cpu_to_be32(ret); + + return ret + sizeof(*c_size); +} + +/* + * qcow2_zstd_decompress() + * + * Decompress some data (not more than @src_size bytes) to produce exactly + * @dest_size bytes using zstd compression method + * + * @dest - destination buffer, @dest_size bytes + * @src - source buffer, @src_size bytes + * + * Returns: 0 on success + * -EIO on any error + */ + +static ssize_t qcow2_zstd_decompress(void *dest, size_t dest_size, + const void *src, size_t src_size) +{ + ssize_t ret; + /* + * zstd decompress wants to know the exact length of the data + * for that purpose, on the compression the length is stored in + * the very beginning of the compressed buffer + */ + uint32_t s_size; + const char *s_buf = ((const char *) src) + sizeof(s_size); + + /* sanity check that we can read the content length */ + if (src_size < sizeof(s_size)) { + return -EIO; + } + + s_size = be32_to_cpu(*(const uint32_t *) src); + + /* sanity check that the buffer is big enough to read the content */ + if (src_size - sizeof(s_size) < s_size) { + return -EIO; + } + + ret = ZSTD_decompress(dest, dest_size, s_buf, s_size); + + if (ZSTD_isError(ret)) { + return -EIO; + } + + return 0; +} +#endif + static int qcow2_compress_pool_func(void *opaque) { Qcow2CompressData *data = opaque; @@ -215,6 +312,11 @@ qcow2_co_compress(BlockDriverState *bs, void *dest, size_t dest_size, fn = qcow2_zlib_compress; break; +#ifdef CONFIG_ZSTD + case QCOW2_COMPRESSION_TYPE_ZSTD: + fn = qcow2_zstd_compress; + break; +#endif default: return -ENOTSUP; } @@ -247,6 +349,11 @@ qcow2_co_decompress(BlockDriverState *bs, void *dest, size_t dest_size, fn = qcow2_zlib_decompress; break; +#ifdef CONFIG_ZSTD + case QCOW2_COMPRESSION_TYPE_ZSTD: + fn = qcow2_zstd_decompress; + break; +#endif default: return -ENOTSUP; } diff --git a/block/qcow2.c b/block/qcow2.c index 2884b9d9f2..06f346e8cc 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -1201,6 +1201,9 @@ static int check_compression_type(BDRVQcow2State *s, Error **errp) { switch (s->compression_type) { case QCOW2_COMPRESSION_TYPE_ZLIB: +#ifdef CONFIG_ZSTD + case QCOW2_COMPRESSION_TYPE_ZSTD: +#endif break; default: @@ -3293,6 +3296,10 @@ qcow2_co_create(BlockdevCreateOptions *create_options, Error **errp) qcow2_opts->compression_type != QCOW2_COMPRESSION_TYPE_ZLIB) { switch (qcow2_opts->compression_type) { +#ifdef CONFIG_ZSTD + case QCOW2_COMPRESSION_TYPE_ZSTD: + break; +#endif default: error_setg_errno(errp, -EINVAL, "Unknown compression type"); goto out; diff --git a/configure b/configure index 714e7fb6a1..964126c569 100755 --- a/configure +++ b/configure @@ -441,6 +441,7 @@ opengl_dmabuf="no" cpuid_h="no" avx2_opt="" zlib="yes" +zstd="" capstone="" lzo="" snappy="" @@ -1358,6 +1359,10 @@ for opt do ;; --disable-lzfse) lzfse="no" ;; + --enable-zstd) zstd="yes" + ;; + --disable-zstd) zstd="no" + ;; --enable-guest-agent) guest_agent="yes" ;; --disable-guest-agent) guest_agent="no" @@ -1812,6 +1817,7 @@ disabled with --disable-FEATURE, default is enabled if available: (for reading bzip2-compressed dmg images) lzfse support of lzfse compression library (for reading lzfse-compressed dmg images) + zstd support of zstd compression library seccomp seccomp support coroutine-pool coroutine freelist (better performance) glusterfs GlusterFS backend @@ -2407,6 +2413,25 @@ EOF fi fi +######################################### +# zstd check + +if test "$zstd" != "no" ; then + cat > $TMPC << EOF +#include +int main(void) { ZSTD_versionNumber(); return 0; } +EOF + if compile_prog "" "-lzstd" ; then + LIBS="$LIBS -lzstd" + zstd="yes" + else + if test "$zstd" = "yes"; then + feature_not_found "zstd" "Install libzstd-devel" + fi + zstd="no" + fi +fi + ########################################## # libseccomp check @@ -6460,6 +6485,7 @@ echo "lzo support $lzo" echo "snappy support $snappy" echo "bzip2 support $bzip2" echo "lzfse support $lzfse" +echo "zstd support $zstd" echo "NUMA host support $numa" echo "libxml2 $libxml2" echo "tcmalloc support $tcmalloc" @@ -7306,6 +7332,9 @@ fi if test "$sheepdog" = "yes" ; then echo "CONFIG_SHEEPDOG=y" >> $config_host_mak fi +if test "$zstd" = "yes" ; then + echo "CONFIG_ZSTD=y" >> $config_host_mak +fi if test "$tcg_interpreter" = "yes"; then QEMU_INCLUDES="-iquote \$(SRC_PATH)/tcg/tci $QEMU_INCLUDES" diff --git a/docs/interop/qcow2.txt b/docs/interop/qcow2.txt index e1be8bd5c3..b975a34687 100644 --- a/docs/interop/qcow2.txt +++ b/docs/interop/qcow2.txt @@ -181,6 +181,7 @@ in the description of a field. must be set. Available compression type values: 0: zlib (default) + 1: zstd Directly after the image header, optional sections called header extensions can be stored. Each extension has a structure like the following: @@ -536,6 +537,9 @@ Compressed Clusters Descriptor (x = 62 - (cluster_bits - 8)): Another compressed cluster may map to the tail of the final sector used by this compressed cluster. + The layout of the compressed data depends on the compression + type used for the image (see compressed cluster layout). + If a cluster is unallocated, read requests shall read the data from the backing file (except if bit 0 in the Standard Cluster Descriptor is set). If there is no backing file or the backing file is smaller than the image, they shall read @@ -788,3 +792,19 @@ In the image file the 'enabled' state is reflected by the 'auto' flag. If this flag is set, the software must consider the bitmap as 'enabled' and start tracking virtual disk changes to this bitmap from the first write to the virtual disk. If this flag is not set then the bitmap is disabled. + +=== Compressed cluster layout === + +The compressed cluster data may have a different layout depending on the +compression type used for the image, and store specific data for the particular +compression type. + +Compressed data layout for the available compression types: +(x = data_space_length - 1) + + zlib : + Byte 0 - x: the compressed data content + all the space provided used for compressed data + zstd : + Byte 0 - 3: the length of compressed data in bytes + 4 - x: the compressed data content diff --git a/qapi/block-core.json b/qapi/block-core.json index 2c002ca6a9..9e458d5b40 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -4283,11 +4283,12 @@ # Compression type used in qcow2 image file # # @zlib: zlib compression, see +# @zstd: zstd compression, see # # Since: 4.2 ## { 'enum': 'Qcow2CompressionType', - 'data': [ 'zlib' ] } + 'data': [ 'zlib', { 'name': 'zstd', 'if': 'defined(CONFIG_ZSTD)' } ] } ## # @BlockdevCreateOptionsQcow2: