[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