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

Andrey Zhadchenko andrey.zhadchenko at virtuozzo.com
Wed Jul 10 12:55:20 MSK 2024


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

 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 */
-- 
2.39.3



More information about the Devel mailing list