The commit is pushed to "branch-rh9-5.14.0-427.22.1.vz9.62.x-ovz" and will appear at https://src.openvz.org/scm/ovz/vzkernel.git after rh9-5.14.0-427.22.1.vz9.62.2 ------> commit e4815b333708538bef01ae4ee15e65952d392137 Author: Andrey Zhadchenko <andrey.zhadche...@virtuozzo.com> Date: Wed Jul 10 12:55:20 2024 +0300
dm-qcow2: add zstd decompression 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.zhadche...@virtuozzo.com> Reviewed-by: Alexander Atanasov <alexander.atana...@virtuozzo.com> khorenko@: high level explanation what is going on here: * we would like to use dm-qcow2 driver for VMs as well * VM images are compressed using ZSTD nowadays * Container images are created by us and we just did not use ZSTD, used ZLIB => we did not have ZSTD compressed images support, so adding it Q: Why only decompression is here? Where is the compression part? A: Compression is done only on backup creation or image conversion, so the appropriate compression type support is there. There is no kernel support for compressed writes. Feature: dm-qcow2: ZSTD decompression --- 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->header_length < offsetof(struct QCowHeader, padding)) - return 0; + if (hdr->incompatible_features & INCOMPATIBLE_FEATURES_COMPRESSION) { + if (hdr->header_length < sizeof(struct QCowHeader)) + return -EINVAL; - 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 */ _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel