[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