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.zhadche...@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 */
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to