[Devel] [PATCH v2] dm-qcow2: add zstd decompression

Alexander Atanasov alexander.atanasov at virtuozzo.com
Wed Jul 10 17:42:41 MSK 2024


Hi,

On 10.07.24 12:55, Andrey Zhadchenko wrote:
> Adjust qcow2 header parsing to accommodate changes: compression_type
> field maybe be absent, except if the compression_type bit in
> incompatible features is set.
>
> Add decompress_zstd_clu(), update process_compressed_read() to
> allocate memory depending on compression type.
>
> https://virtuozzo.atlassian.net/browse/PSBM-157138
> Signed-off-by: Andrey Zhadchenko <andrey.zhadchenko at virtuozzo.com>
> ---
> v2:
>   - Slightly improved header parsing
>   - Changed default 'if' paths to zlib decompression
>   - Updated decompress_zstd_clu() to stop decompression early if there
> is still potential input, but cluster is already fully inflated



LGTM.


>   drivers/md/Kconfig           |  1 +
>   drivers/md/dm-qcow2-map.c    | 76 ++++++++++++++++++++++++++++++++----
>   drivers/md/dm-qcow2-target.c | 19 ++++-----
>   drivers/md/dm-qcow2.h        |  7 +++-
>   4 files changed, 85 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig
> index 919dc99b5d72..1d7b9ceb89f6 100644
> --- a/drivers/md/Kconfig
> +++ b/drivers/md/Kconfig
> @@ -684,6 +684,7 @@ config DM_QCOW2
>   	tristate "QCOW2 target support"
>   	depends on BLK_DEV_DM
>   	depends on ZLIB_INFLATE
> +	depends on ZSTD_DECOMPRESS
>   	help
>   	  Driver for attaching QCOW2 files as block devices. It cares
>   	  about performance-critical actions like actual IO and
> diff --git a/drivers/md/dm-qcow2-map.c b/drivers/md/dm-qcow2-map.c
> index 8f871c0522aa..9cb46fa2cd12 100644
> --- a/drivers/md/dm-qcow2-map.c
> +++ b/drivers/md/dm-qcow2-map.c
> @@ -9,6 +9,7 @@
>   #include <linux/blk-mq.h>
>   #include <linux/zlib.h>
>   #include <linux/error-injection.h>
> +#include <linux/zstd.h>
>   
>   #include "dm.h"
>   #include "dm-rq.h"
> @@ -2941,13 +2942,56 @@ static int decompress_zlib_clu(struct qcow2 *qcow2, struct qcow2_bvec *qvec,
>   	return -EIO;
>   }
>   
> +static int decompress_zstd_clu(struct qcow2 *qcow2, struct qcow2_bvec *qvec,
> +			       u16 page0_off, int count, void *buf, zstd_dctx *dctx)
> +{
> +	unsigned int off = page0_off;
> +	zstd_out_buffer output;
> +	zstd_in_buffer input;
> +	size_t ret;
> +	void *from;
> +	int i;
> +
> +	ret = zstd_reset_dstream(dctx);
> +	if (zstd_is_error(ret))
> +		return -EIO;
> +
> +	output.dst = buf,
> +	output.size = qcow2->clu_size,
> +	output.pos = 0;
> +
> +	for (i = 0; i < qvec->nr_pages && count > 0; i++, off = 0) {
> +		from = kmap(qvec->bvec[i].bv_page);
> +		input.src = from + off;
> +		input.size = min_t(int, qvec->bvec[i].bv_len - off, count);
> +		input.pos = 0;
> +		count -= input.size;
> +
> +		ret = zstd_decompress_stream(dctx, &output, &input);
> +		kunmap(qvec->bvec[i].bv_page);
> +
> +		if (output.pos >= qcow2->clu_size)
> +			break;
> +
> +		if (zstd_is_error(ret))
> +			break;
> +	}
> +
> +	if (!zstd_is_error(ret) && output.pos == qcow2->clu_size)
> +		return output.pos;
> +	return -EIO;
> +}
> +
> +
>   static int extract_one_compressed(struct qcow2 *qcow2, void *buf,
>   				  struct qcow2_bvec *qvec,
> -				  u16 page0_off, u32 qvec_len)
> +				  u16 page0_off, u32 qvec_len,
> +				  void *arg)
>   {
> -	void *ws = buf + qcow2->clu_size;
> -
> -	return decompress_zlib_clu(qcow2, qvec, page0_off, qvec_len, buf, ws);
> +	if (qcow2->hdr.compression_type == QCOW2_COMPRESSION_TYPE_ZSTD)
> +		return decompress_zstd_clu(qcow2, qvec, page0_off, qvec_len, buf, arg);
> +	else
> +		return decompress_zlib_clu(qcow2, qvec, page0_off, qvec_len, buf, arg);
>   }
>   
>   static int copy_buf_to_bvec_iter(const struct bio_vec *bvec,
> @@ -3621,25 +3665,43 @@ static void process_compressed_read(struct qcow2 *qcow2, struct list_head *read_
>   	struct qcow2_bvec *qvec;
>   	struct qio_ext *ext;
>   	blk_status_t ret;
> -	void *buf = NULL;
> +	void *buf = NULL, *arg;
>   	struct qio *qio;
>   	bool for_cow;
> +	size_t dctxlen;
>   
>   	if (list_empty(read_list))
>   		return;
>   
> -	buf = kmalloc(qcow2->clu_size + zlib_inflate_workspacesize(), GFP_NOIO);
> +	if (qcow2->hdr.compression_type == QCOW2_COMPRESSION_TYPE_ZSTD)
> +		dctxlen = zstd_dstream_workspace_bound(qcow2->clu_size);
> +	else
> +		dctxlen = zlib_inflate_workspacesize();
> +
> +
> +	buf = kmalloc(qcow2->clu_size + dctxlen, GFP_NOIO);
>   	if (!buf) {
>   		end_qios(read_list, BLK_STS_RESOURCE);
>   		return;
>   	}
>   
> +	if (qcow2->hdr.compression_type == QCOW2_COMPRESSION_TYPE_ZSTD) {
> +		arg = zstd_init_dstream(qcow2->clu_size, buf + qcow2->clu_size, dctxlen);
> +		if (!arg) {
> +			end_qios(read_list, BLK_STS_RESOURCE);
> +			kfree(buf);
> +			return;
> +		}
> +	} else {
> +		arg = buf + qcow2->clu_size;
> +	}
> +
>   	while ((qio = qio_list_pop(read_list)) != NULL) {
>   		qvec = qio->data;
>   		ext = qio->ext;
>   
>   		ret = extract_one_compressed(qcow2, buf, qvec,
> -				    ext->zdata_off, qio->ret);
> +				    ext->zdata_off, qio->ret, arg);
>   		if (ret)
>   			goto err;
>   
> diff --git a/drivers/md/dm-qcow2-target.c b/drivers/md/dm-qcow2-target.c
> index 112ebbc56b42..871958ee9024 100644
> --- a/drivers/md/dm-qcow2-target.c
> +++ b/drivers/md/dm-qcow2-target.c
> @@ -565,22 +565,23 @@ static int qcow2_check_convert_hdr(struct dm_target *ti,
>   //	    !(hdr->incompatible_features & INCOMPATIBLE_FEATURES_DIRTY_BIT))
>   //		return kernel_sets_dirty_bit ? -EUCLEAN : -ENOLCK;
>   	if (hdr->incompatible_features &
> -	    ~(INCOMPATIBLE_FEATURES_EXTL2_BIT|INCOMPATIBLE_FEATURES_DIRTY_BIT))
> +	    ~(INCOMPATIBLE_FEATURES_EXTL2_BIT|INCOMPATIBLE_FEATURES_DIRTY_BIT|
> +	      INCOMPATIBLE_FEATURES_COMPRESSION))
>   		return -EOPNOTSUPP;
>   	ext_l2 = hdr->incompatible_features & INCOMPATIBLE_FEATURES_EXTL2_BIT;
>   
>   	if (hdr->refcount_order > 6 || (ext_l2 && hdr->cluster_bits < 14))
>   		return -EINVAL;
>   
> -	if (hdr->header_length < offsetof(struct QCowHeader, compression_type))
> -		return -EINVAL;
> +	if (hdr->incompatible_features & INCOMPATIBLE_FEATURES_COMPRESSION) {
> +		if (hdr->header_length < sizeof(struct QCowHeader))
> +			return -EINVAL;
>   
> -	if (hdr->header_length < offsetof(struct QCowHeader, padding))
> -		return 0;
> -
> -	hdr->compression_type = (u8)raw_hdr->compression_type;
> -	if (hdr->compression_type != (u8)0)
> -		return -EOPNOTSUPP;
> +		hdr->compression_type = (u8)raw_hdr->compression_type;
> +		if (hdr->compression_type != QCOW2_COMPRESSION_TYPE_ZLIB &&
> +		    hdr->compression_type != QCOW2_COMPRESSION_TYPE_ZSTD)
> +			return -EOPNOTSUPP;
> +	}
>   
>   	return 0;
>   }
> diff --git a/drivers/md/dm-qcow2.h b/drivers/md/dm-qcow2.h
> index 4c4f4a0c0c8c..42f041a82a5b 100644
> --- a/drivers/md/dm-qcow2.h
> +++ b/drivers/md/dm-qcow2.h
> @@ -38,8 +38,9 @@ struct QCowHeader {
>   	uint64_t snapshots_offset;
>   
>   	/* The following fields are only valid for version >= 3 */
> -#define INCOMPATIBLE_FEATURES_DIRTY_BIT	(1 << 0)
> -#define INCOMPATIBLE_FEATURES_EXTL2_BIT	(1 << 4)
> +#define INCOMPATIBLE_FEATURES_DIRTY_BIT		(1 << 0)
> +#define INCOMPATIBLE_FEATURES_COMPRESSION	(1 << 3)
> +#define INCOMPATIBLE_FEATURES_EXTL2_BIT		(1 << 4)
>   	uint64_t incompatible_features;
>   	uint64_t compatible_features;
>   	uint64_t autoclear_features;
> @@ -48,6 +49,8 @@ struct QCowHeader {
>   	uint32_t header_length;
>   
>   	/* Additional fields */
> +#define QCOW2_COMPRESSION_TYPE_ZLIB 0
> +#define QCOW2_COMPRESSION_TYPE_ZSTD 1
>   	uint8_t compression_type;
>   
>   	/* header must be a multiple of 8 */


More information about the Devel mailing list