From dbb41ce2b7f309d394054a6bd1e33afd578798a5 Mon Sep 17 00:00:00 2001 From: Manuel Bentele Date: Fri, 23 Oct 2020 15:18:01 +0200 Subject: Move the source code of all xloop components to the common 'src' directory --- src/kernel/xloop_file_fmt_qcow_main.c | 1283 +++++++++++++++++++++++++++++++++ 1 file changed, 1283 insertions(+) create mode 100644 src/kernel/xloop_file_fmt_qcow_main.c (limited to 'src/kernel/xloop_file_fmt_qcow_main.c') diff --git a/src/kernel/xloop_file_fmt_qcow_main.c b/src/kernel/xloop_file_fmt_qcow_main.c new file mode 100644 index 0000000..38fe1af --- /dev/null +++ b/src/kernel/xloop_file_fmt_qcow_main.c @@ -0,0 +1,1283 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xloop_file_fmt_qcow.c + * + * QCOW file format driver for the xloop device module. + * + * Copyright (C) 2019 Manuel Bentele + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_ZSTD_DECOMPRESS +#include +#endif + +#include "xloop_file_fmt.h" +#include "xloop_file_fmt_qcow_main.h" +#include "xloop_file_fmt_qcow_cache.h" +#include "xloop_file_fmt_qcow_cluster.h" + +#ifdef CONFIG_ZSTD_DECOMPRESS +#define ZSTD_WINDOWLOG_LIMIT_DEFAULT 27 +#define ZSTD_MAXWINDOWSIZE ((U32_C(1) << ZSTD_WINDOWLOG_LIMIT_DEFAULT) + 1) +#endif + +typedef ssize_t (*qcow_file_fmt_decompress_fn)(struct xloop_file_fmt *xlo_fmt, + void *dest, size_t dest_size, const void *src, size_t src_size); + +static int __qcow_file_fmt_header_read(struct xloop_file_fmt *xlo_fmt, + struct file *file, struct xloop_file_fmt_qcow_header *header) +{ + ssize_t len; + loff_t offset; + int ret = 0; + + /* read QCOW header */ + offset = 0; + len = kernel_read(file, header, sizeof(*header), &offset); + if (len < 0) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "could not read QCOW " + "header\n"); + return len; + } + + header->magic = be32_to_cpu(header->magic); + header->version = be32_to_cpu(header->version); + header->backing_file_offset = be64_to_cpu(header->backing_file_offset); + header->backing_file_size = be32_to_cpu(header->backing_file_size); + header->cluster_bits = be32_to_cpu(header->cluster_bits); + header->size = be64_to_cpu(header->size); + header->crypt_method = be32_to_cpu(header->crypt_method); + header->l1_size = be32_to_cpu(header->l1_size); + header->l1_table_offset = be64_to_cpu(header->l1_table_offset); + header->refcount_table_offset = + be64_to_cpu(header->refcount_table_offset); + header->refcount_table_clusters = + be32_to_cpu(header->refcount_table_clusters); + header->nb_snapshots = be32_to_cpu(header->nb_snapshots); + header->snapshots_offset = be64_to_cpu(header->snapshots_offset); + + /* check QCOW file format and header version */ + if (header->magic != QCOW_MAGIC) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "image is not in QCOW " + "format\n"); + return -EINVAL; + } + + if (header->version < 2 || header->version > 3) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "unsupported QCOW " + "version %d\n", header->version); + return -ENOTSUPP; + } + + /* initialize version 3 header fields */ + if (header->version == 2) { + header->incompatible_features = 0; + header->compatible_features = 0; + header->autoclear_features = 0; + header->refcount_order = 4; + header->header_length = 72; + } else { + header->incompatible_features = + be64_to_cpu(header->incompatible_features); + header->compatible_features = + be64_to_cpu(header->compatible_features); + header->autoclear_features = + be64_to_cpu(header->autoclear_features); + header->refcount_order = be32_to_cpu(header->refcount_order); + header->header_length = be32_to_cpu(header->header_length); + + if (header->header_length < 104) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "QCOW header too short\n"); + return -EINVAL; + } + } + + return ret; +} + +static int __qcow_file_fmt_validate_table(struct xloop_file_fmt *xlo_fmt, + u64 offset, u64 entries, size_t entry_len, s64 max_size_bytes, + const char *table_name) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + if (entries > max_size_bytes / entry_len) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "%s too large\n", table_name); + return -EFBIG; + } + + /* Use signed S64_MAX as the maximum even for u64 header fields, + * because values will be passed to qemu functions taking s64. */ + if ((S64_MAX - entries * entry_len < offset) || ( + xloop_file_fmt_qcow_offset_into_cluster(qcow_data, offset) != 0) + ) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "%s offset invalid", + table_name); + return -EINVAL; + } + + return 0; +} + +static inline loff_t __qcow_file_fmt_rq_get_pos(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + return ((loff_t) blk_rq_pos(rq) << 9) + xlo->xlo_offset; +} + +static int __qcow_file_fmt_compression_init(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + int ret = 0; +#ifdef CONFIG_ZSTD_DECOMPRESS + size_t workspace_size; +#endif + + /* create workspace for ZLIB decompression stream */ + qcow_data->zlib_dstrm = kzalloc(sizeof(*qcow_data->zlib_dstrm), GFP_KERNEL); + if (!qcow_data->zlib_dstrm) { + ret = -ENOMEM; + goto out; + } + + qcow_data->zlib_dstrm->workspace = vzalloc(zlib_inflate_workspacesize()); + if (!qcow_data->zlib_dstrm->workspace) { + ret = -ENOMEM; + goto out_free_zlib_dstrm; + } + + /* set up ZLIB decompression stream */ + ret = zlib_inflateInit2(qcow_data->zlib_dstrm, -12); + if (ret != Z_OK) { + ret = -EIO; + goto out_free_zlib_dworkspace; + } + +#ifdef CONFIG_ZSTD_DECOMPRESS + /* create workspace for ZSTD decompression stream */ + workspace_size = ZSTD_DStreamWorkspaceBound(ZSTD_MAXWINDOWSIZE); + qcow_data->zstd_dworkspace = vzalloc(workspace_size); + if (!qcow_data->zstd_dworkspace) { + ret = -ENOMEM; + goto out_free_zlib_dworkspace; + } + + /* set up ZSTD decompression stream */ + qcow_data->zstd_dstrm = ZSTD_initDStream(ZSTD_MAXWINDOWSIZE, + qcow_data->zstd_dworkspace, workspace_size); + if (!qcow_data->zstd_dstrm) { + ret = -EINVAL; + goto out_free_zstd_dworkspace; + } +#endif + + /* create cache for last compressed QCOW cluster */ + qcow_data->cmp_last_coffset = ULLONG_MAX; + qcow_data->cmp_out_buf = vmalloc(qcow_data->cluster_size); + if (!qcow_data->cmp_out_buf) { + ret = -ENOMEM; +#ifdef CONFIG_ZSTD_DECOMPRESS + goto out_free_zstd_dworkspace; +#else + goto out_free_zlib_dworkspace; +#endif + } + + return ret; + +#ifdef CONFIG_ZSTD_DECOMPRESS +out_free_zstd_dworkspace: + vfree(qcow_data->zstd_dworkspace); +#endif +out_free_zlib_dworkspace: + vfree(qcow_data->zlib_dstrm->workspace); +out_free_zlib_dstrm: + kfree(qcow_data->zlib_dstrm); +out: + return ret; +} + +static void __qcow_file_fmt_compression_exit(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + /* ZLIB specific cleanup */ + zlib_inflateEnd(qcow_data->zlib_dstrm); + vfree(qcow_data->zlib_dstrm->workspace); + kfree(qcow_data->zlib_dstrm); + + /* ZSTD specific cleanup */ +#ifdef CONFIG_ZSTD_DECOMPRESS + vfree(qcow_data->zstd_dworkspace); +#endif + + /* last compressed QCOW cluster cleanup */ + vfree(qcow_data->cmp_out_buf); +} + +#ifdef CONFIG_DEBUG_FS +static void __qcow_file_fmt_header_to_buf(struct xloop_file_fmt *xlo_fmt, + const struct xloop_file_fmt_qcow_header *header) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + char *header_buf = qcow_data->dbgfs_file_qcow_header_buf; + ssize_t len = 0; + + len += sprintf(header_buf + len, "magic: %d\n", + header->magic); + len += sprintf(header_buf + len, "version: %d\n", + header->version); + len += sprintf(header_buf + len, "backing_file_offset: %lld\n", + header->backing_file_offset); + len += sprintf(header_buf + len, "backing_file_size: %d\n", + header->backing_file_size); + len += sprintf(header_buf + len, "cluster_bits: %d\n", + header->cluster_bits); + len += sprintf(header_buf + len, "size: %lld\n", + header->size); + len += sprintf(header_buf + len, "crypt_method: %d\n", + header->crypt_method); + len += sprintf(header_buf + len, "l1_size: %d\n", + header->l1_size); + len += sprintf(header_buf + len, "l1_table_offset: %lld\n", + header->l1_table_offset); + len += sprintf(header_buf + len, "refcount_table_offset: %lld\n", + header->refcount_table_offset); + len += sprintf(header_buf + len, "refcount_table_clusters: %d\n", + header->refcount_table_clusters); + len += sprintf(header_buf + len, "nb_snapshots: %d\n", + header->nb_snapshots); + len += sprintf(header_buf + len, "snapshots_offset: %lld\n", + header->snapshots_offset); + + if (header->version == 3) { + len += sprintf(header_buf + len, + "incompatible_features: %lld\n", + header->incompatible_features); + len += sprintf(header_buf + len, + "compatible_features: %lld\n", + header->compatible_features); + len += sprintf(header_buf + len, + "autoclear_features: %lld\n", + header->autoclear_features); + len += sprintf(header_buf + len, + "refcount_order: %d\n", + header->refcount_order); + len += sprintf(header_buf + len, + "header_length: %d\n", + header->header_length); + } + + if (header->header_length > offsetof(struct xloop_file_fmt_qcow_header, + compression_type)) { + len += sprintf(header_buf + len, + "compression_type: %d\n", + header->compression_type); + } + + ASSERT(len < QCOW_HEADER_BUF_LEN); +} + +static ssize_t __qcow_file_fmt_dbgfs_hdr_read(struct file *file, + char __user *buf, size_t size, loff_t *ppos) +{ + struct xloop_file_fmt *xlo_fmt = file->private_data; + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + char *header_buf = qcow_data->dbgfs_file_qcow_header_buf; + + return simple_read_from_buffer(buf, size, ppos, header_buf, + strlen(header_buf)); +} + +static const struct file_operations qcow_file_fmt_dbgfs_hdr_fops = { + .open = simple_open, + .read = __qcow_file_fmt_dbgfs_hdr_read +}; + +static ssize_t __qcow_file_fmt_dbgfs_ofs_read(struct file *file, + char __user *buf, size_t size, loff_t *ppos) +{ + struct xloop_file_fmt *xlo_fmt = file->private_data; + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + unsigned int cur_bytes = 1; + u64 offset = 0; + u64 coffset = 0; + u64 host_offset = 0; + s64 offset_in_cluster = 0; + enum xloop_file_fmt_qcow_subcluster_type type; + ssize_t len = 0; + int ret = 0, csize = 0, nb_csectors = 0; + + /* read the share debugfs offset */ + ret = mutex_lock_interruptible(&qcow_data->dbgfs_qcow_offset_mutex); + if (ret) + return ret; + + offset = qcow_data->dbgfs_qcow_offset; + mutex_unlock(&qcow_data->dbgfs_qcow_offset_mutex); + + /* calculate and print the cluster offset */ + ret = xloop_file_fmt_qcow_get_host_offset(xlo_fmt, + offset, &cur_bytes, &host_offset, &type); + if (ret < 0) + return -EINVAL; + + offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster(qcow_data, + offset); + + len = sprintf(qcow_data->dbgfs_file_qcow_cluster_buf, + "cluster type: %s\n" + "cluster offset host: %lld\n" + "cluster offset guest: %lld\n" + "cluster offset in-cluster: %lld\n", + xloop_file_fmt_qcow_get_subcluster_name(type), + host_offset, offset, offset_in_cluster); + + if (type == QCOW_SUBCLUSTER_COMPRESSED) { + coffset = host_offset & qcow_data->cluster_offset_mask; + nb_csectors = ((host_offset >> qcow_data->csize_shift) & + qcow_data->csize_mask) + 1; + csize = nb_csectors * QCOW_COMPRESSED_SECTOR_SIZE - + (coffset & ~QCOW_COMPRESSED_SECTOR_MASK); + + len += sprintf(qcow_data->dbgfs_file_qcow_cluster_buf + len, + "cluster compressed offset: %lld\n" + "cluster compressed sectors: %d\n" + "cluster compressed size: %d\n", + coffset, nb_csectors, csize); + } + + ASSERT(len < QCOW_CLUSTER_BUF_LEN); + + return simple_read_from_buffer(buf, size, ppos, + qcow_data->dbgfs_file_qcow_cluster_buf, len); +} + +static ssize_t __qcow_file_fmt_dbgfs_ofs_write(struct file *file, + const char __user *buf, size_t size, loff_t *ppos) +{ + struct xloop_file_fmt *xlo_fmt = file->private_data; + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + ssize_t len = 0; + int ret = 0; + + if (*ppos > QCOW_OFFSET_BUF_LEN || size > QCOW_OFFSET_BUF_LEN) + return -EINVAL; + + len = simple_write_to_buffer(qcow_data->dbgfs_file_qcow_offset_buf, + QCOW_OFFSET_BUF_LEN, ppos, buf, size); + if (len < 0) + return len; + + qcow_data->dbgfs_file_qcow_offset_buf[len] = '\0'; + + ret = mutex_lock_interruptible(&qcow_data->dbgfs_qcow_offset_mutex); + if (ret) + return ret; + + ret = kstrtou64(qcow_data->dbgfs_file_qcow_offset_buf, 10, + &qcow_data->dbgfs_qcow_offset); + if (ret < 0) + goto out; + + ret = len; +out: + mutex_unlock(&qcow_data->dbgfs_qcow_offset_mutex); + return ret; +} + +static const struct file_operations qcow_file_fmt_dbgfs_ofs_fops = { + .open = simple_open, + .read = __qcow_file_fmt_dbgfs_ofs_read, + .write = __qcow_file_fmt_dbgfs_ofs_write +}; + +static int __qcow_file_fmt_dbgfs_init(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + int ret = 0; + + qcow_data->dbgfs_dir = debugfs_create_dir("QCOW", xlo->xlo_dbgfs_dir); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_dir)) { + ret = -ENODEV; + goto out; + } + + qcow_data->dbgfs_file_qcow_header = debugfs_create_file("header", + S_IRUGO, qcow_data->dbgfs_dir, xlo_fmt, + &qcow_file_fmt_dbgfs_hdr_fops); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_file_qcow_header)) { + ret = -ENODEV; + goto out_free_dbgfs_dir; + } + + qcow_data->dbgfs_file_qcow_offset = debugfs_create_file("offset", + S_IRUGO | S_IWUSR, qcow_data->dbgfs_dir, xlo_fmt, + &qcow_file_fmt_dbgfs_ofs_fops); + if (IS_ERR_OR_NULL(qcow_data->dbgfs_file_qcow_offset)) { + qcow_data->dbgfs_file_qcow_offset = NULL; + ret = -ENODEV; + goto out_free_dbgfs_hdr; + } + + qcow_data->dbgfs_qcow_offset = 0; + mutex_init(&qcow_data->dbgfs_qcow_offset_mutex); + + return ret; + +out_free_dbgfs_hdr: + debugfs_remove(qcow_data->dbgfs_file_qcow_header); + qcow_data->dbgfs_file_qcow_header = NULL; +out_free_dbgfs_dir: + debugfs_remove(qcow_data->dbgfs_dir); + qcow_data->dbgfs_dir = NULL; +out: + return ret; +} + +static void __qcow_file_fmt_dbgfs_exit(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + if (qcow_data->dbgfs_file_qcow_offset) + debugfs_remove(qcow_data->dbgfs_file_qcow_offset); + + mutex_destroy(&qcow_data->dbgfs_qcow_offset_mutex); + + if (qcow_data->dbgfs_file_qcow_header) + debugfs_remove(qcow_data->dbgfs_file_qcow_header); + + if (qcow_data->dbgfs_dir) + debugfs_remove(qcow_data->dbgfs_dir); +} +#endif + +static int __qcow_file_fmt_validate_compression_type( + struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + + switch (qcow_data->compression_type) { + case QCOW_COMPRESSION_TYPE_ZLIB: +#ifdef CONFIG_ZSTD_DECOMPRESS + case QCOW_COMPRESSION_TYPE_ZSTD: +#endif + break; + default: + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "unknown compression type: %u", + qcow_data->compression_type); + return -ENOTSUPP; + } + + /* + * if the compression type differs from QCOW_COMPRESSION_TYPE_ZLIB + * the incompatible feature flag must be set + */ + if (qcow_data->compression_type == QCOW_COMPRESSION_TYPE_ZLIB) { + if (qcow_data->incompatible_features & QCOW_INCOMPAT_COMPRESSION) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "compression type " + "incompatible feature bit must not be set\n"); + return -EINVAL; + } + } else { + if (!(qcow_data->incompatible_features & QCOW_INCOMPAT_COMPRESSION)) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "compression type " + "incompatible feature bit must be set\n"); + return -EINVAL; + } + } + + return 0; +} + +static int qcow_file_fmt_init(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data; + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + struct xloop_file_fmt_qcow_header header; + u64 l1_vm_state_index; + u64 l2_cache_size; + u64 l2_cache_entry_size; + u64 virtual_disk_size; + u64 max_l2_entries; + u64 max_l2_cache; + u64 l2_cache_max_setting; + ssize_t len; + unsigned int i; + int ret = 0; + + /* allocate memory for saving QCOW file format data */ + qcow_data = kzalloc(sizeof(*qcow_data), GFP_KERNEL); + if (!qcow_data) + return -ENOMEM; + + xlo_fmt->private_data = qcow_data; + + /* read the QCOW file header */ + ret = __qcow_file_fmt_header_read(xlo_fmt, xlo->xlo_backing_file, &header); + if (ret) + goto free_qcow_data; + + /* save information of the header fields in human readable format in + * a file buffer to access it with debugfs */ +#ifdef CONFIG_DEBUG_FS + __qcow_file_fmt_header_to_buf(xlo_fmt, &header); +#endif + + qcow_data->qcow_version = header.version; + + /* Initialise cluster size */ + if (header.cluster_bits < QCOW_MIN_CLUSTER_BITS + || header.cluster_bits > QCOW_MAX_CLUSTER_BITS) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "unsupported cluster size: " + "2^%d\n", header.cluster_bits); + ret = -EINVAL; + goto free_qcow_data; + } + + qcow_data->cluster_bits = header.cluster_bits; + qcow_data->cluster_size = 1 << qcow_data->cluster_bits; + + if (header.header_length > qcow_data->cluster_size) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "QCOW header exceeds cluster " + "size\n"); + ret = -EINVAL; + goto free_qcow_data; + } + + if (header.backing_file_offset > qcow_data->cluster_size) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "invalid backing file " + "offset\n"); + ret = -EINVAL; + goto free_qcow_data; + } + + if (header.backing_file_offset) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "backing file support not " + "available\n"); + ret = -ENOTSUPP; + goto free_qcow_data; + } + + /* handle feature bits */ + qcow_data->incompatible_features = header.incompatible_features; + qcow_data->compatible_features = header.compatible_features; + qcow_data->autoclear_features = header.autoclear_features; + + /* + * Handle compression type + * Older qcow2 images don't contain the compression type header. + * Distinguish them by the header length and use + * the only valid (default) compression type in that case + */ + if (header.header_length > offsetof(struct xloop_file_fmt_qcow_header, + compression_type)) { + qcow_data->compression_type = header.compression_type; + } else { + qcow_data->compression_type = QCOW_COMPRESSION_TYPE_ZLIB; + } + + ret = __qcow_file_fmt_validate_compression_type(xlo_fmt); + if (ret) { + goto free_qcow_data; + } + + /* check for incompatible features */ + if (qcow_data->incompatible_features & QCOW_INCOMPAT_DIRTY) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "image contains inconsistent " + "refcounts\n"); + ret = -EACCES; + goto free_qcow_data; + } + + if (qcow_data->incompatible_features & QCOW_INCOMPAT_CORRUPT) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "image is corrupt; cannot be " + "opened read/write\n"); + ret = -EACCES; + goto free_qcow_data; + } + + if (qcow_data->incompatible_features & QCOW_INCOMPAT_DATA_FILE) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "data-file is required for " + "this image\n"); + ret = -EINVAL; + goto free_qcow_data; + } + + qcow_data->subclusters_per_cluster = + xloop_file_fmt_qcow_has_subclusters(qcow_data) ? + QCOW_EXTL2_SUBCLUSTERS_PER_CLUSTER : 1; + qcow_data->subcluster_size = + qcow_data->cluster_size / qcow_data->subclusters_per_cluster; + /* + * check if subcluster_size is non-zero to avoid unknown results of + * __builtin_ctz + */ + ASSERT(qcow_data->subcluster_size != 0); + qcow_data->subcluster_bits = __builtin_ctz(qcow_data->subcluster_size); + + if (qcow_data->subcluster_size < (1 << QCOW_MIN_CLUSTER_BITS)) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "unsupported subcluster " + "size: %d\n", qcow_data->subcluster_size); + ret = -EINVAL; + goto free_qcow_data; + } + + /* Check support for various header values */ + if (header.refcount_order > 6) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "reference count entry width " + "too large; may not exceed 64 bits\n"); + ret = -EINVAL; + goto free_qcow_data; + } + qcow_data->refcount_order = header.refcount_order; + qcow_data->refcount_bits = 1 << qcow_data->refcount_order; + qcow_data->refcount_max = U64_C(1) << (qcow_data->refcount_bits - 1); + qcow_data->refcount_max += qcow_data->refcount_max - 1; + + qcow_data->crypt_method_header = header.crypt_method; + if (qcow_data->crypt_method_header) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "encryption support not " + "available\n"); + ret = -ENOTSUPP; + goto free_qcow_data; + } + + /* + * check if xloop_file_fmt_qcow_l2_entry_size(qcow_data) is non-zero to + * avoid unknown results of __builtin_ctz + */ + ASSERT(xloop_file_fmt_qcow_l2_entry_size(qcow_data) != 0); + qcow_data->l2_bits = qcow_data->cluster_bits - + __builtin_ctz(xloop_file_fmt_qcow_l2_entry_size(qcow_data)); + qcow_data->l2_size = 1 << qcow_data->l2_bits; + /* 2^(qcow_data->refcount_order - 3) is the refcount width in bytes */ + qcow_data->refcount_block_bits = qcow_data->cluster_bits - + (qcow_data->refcount_order - 3); + qcow_data->refcount_block_size = 1 << qcow_data->refcount_block_bits; + qcow_data->size = header.size; + qcow_data->csize_shift = (62 - (qcow_data->cluster_bits - 8)); + qcow_data->csize_mask = (1 << (qcow_data->cluster_bits - 8)) - 1; + qcow_data->cluster_offset_mask = (1LL << qcow_data->csize_shift) - 1; + + qcow_data->refcount_table_offset = header.refcount_table_offset; + qcow_data->refcount_table_size = header.refcount_table_clusters << + (qcow_data->cluster_bits - 3); + + if (header.refcount_table_clusters == 0) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "image does not contain a " + "reference count table\n"); + ret = -EINVAL; + goto free_qcow_data; + } + + ret = __qcow_file_fmt_validate_table(xlo_fmt, + qcow_data->refcount_table_offset, + header.refcount_table_clusters, qcow_data->cluster_size, + QCOW_MAX_REFTABLE_SIZE, "Reference count table"); + if (ret < 0) { + goto free_qcow_data; + } + + /* The total size in bytes of the snapshot table is checked in + * qcow2_read_snapshots() because the size of each snapshot is + * variable and we don't know it yet. + * Here we only check the offset and number of snapshots. */ + ret = __qcow_file_fmt_validate_table(xlo_fmt, header.snapshots_offset, + header.nb_snapshots, + sizeof(struct xloop_file_fmt_qcow_snapshot_header), + sizeof(struct xloop_file_fmt_qcow_snapshot_header) * + QCOW_MAX_SNAPSHOTS, "Snapshot table"); + if (ret < 0) { + goto free_qcow_data; + } + + /* read the level 1 table */ + ret = __qcow_file_fmt_validate_table(xlo_fmt, header.l1_table_offset, + header.l1_size, QCOW_L1E_SIZE, QCOW_MAX_L1_SIZE, + "Active L1 table"); + if (ret < 0) { + goto free_qcow_data; + } + qcow_data->l1_size = header.l1_size; + qcow_data->l1_table_offset = header.l1_table_offset; + + l1_vm_state_index = xloop_file_fmt_qcow_size_to_l1(qcow_data, + header.size); + if (l1_vm_state_index > INT_MAX) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "image is too big\n"); + ret = -EFBIG; + goto free_qcow_data; + } + qcow_data->l1_vm_state_index = l1_vm_state_index; + + /* the L1 table must contain at least enough entries to put header.size + * bytes */ + if (qcow_data->l1_size < qcow_data->l1_vm_state_index) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "L1 table is too small\n"); + ret = -EINVAL; + goto free_qcow_data; + } + + if (qcow_data->l1_size > 0) { + qcow_data->l1_table = vzalloc(round_up(qcow_data->l1_size * + QCOW_L1E_SIZE, 512)); + if (qcow_data->l1_table == NULL) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "could not allocate " + "L1 table\n"); + ret = -ENOMEM; + goto free_qcow_data; + } + len = kernel_read(xlo->xlo_backing_file, qcow_data->l1_table, + qcow_data->l1_size * QCOW_L1E_SIZE, + &qcow_data->l1_table_offset); + if (len < 0) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "could not read " + "L1 table\n"); + ret = len; + goto free_l1_table; + } + for (i = 0; i < qcow_data->l1_size; i++) { + qcow_data->l1_table[i] = + be64_to_cpu(qcow_data->l1_table[i]); + } + } + + /* Internal snapshots */ + qcow_data->snapshots_offset = header.snapshots_offset; + qcow_data->nb_snapshots = header.nb_snapshots; + + if (qcow_data->nb_snapshots > 0) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "snapshots support not " + "available\n"); + ret = -ENOTSUPP; + goto free_l1_table; + } + + /* create cache for L2 */ + virtual_disk_size = qcow_data->size; + max_l2_entries = DIV_ROUND_UP(virtual_disk_size, qcow_data->cluster_size); + max_l2_cache = round_up( + max_l2_entries * xloop_file_fmt_qcow_l2_entry_size(qcow_data), + qcow_data->cluster_size); + + /* define the maximum L2 cache size */ + l2_cache_max_setting = QCOW_DEFAULT_L2_CACHE_MAX_SIZE; + + /* limit the L2 cache size to maximum l2_cache_max_setting */ + l2_cache_size = min(max_l2_cache, l2_cache_max_setting); + + /* determine the size of a cache entry */ + l2_cache_entry_size = min(qcow_data->cluster_size, (int)PAGE_SIZE); + + /* calculate the number of cache tables */ + l2_cache_size /= l2_cache_entry_size; + if (l2_cache_size < QCOW_MIN_L2_CACHE_SIZE) { + l2_cache_size = QCOW_MIN_L2_CACHE_SIZE; + } + + if (l2_cache_size > INT_MAX) { + dev_err(xloop_file_fmt_to_dev(xlo_fmt), "L2 cache size too big\n"); + ret = -EINVAL; + goto free_l1_table; + } + + qcow_data->l2_slice_size = + l2_cache_entry_size / xloop_file_fmt_qcow_l2_entry_size(qcow_data); + + qcow_data->l2_table_cache = xloop_file_fmt_qcow_cache_create(xlo_fmt, + l2_cache_size, l2_cache_entry_size); + if (!qcow_data->l2_table_cache) { + ret = -ENOMEM; + goto free_l1_table; + } + + /* initialize compression support */ + ret = __qcow_file_fmt_compression_init(xlo_fmt); + if (ret < 0) + goto free_l2_cache; + + /* initialize debugfs entries */ +#ifdef CONFIG_DEBUG_FS + ret = __qcow_file_fmt_dbgfs_init(xlo_fmt); + if (ret < 0) + goto free_l2_cache; +#endif + + return ret; + +free_l2_cache: + xloop_file_fmt_qcow_cache_destroy(xlo_fmt); +free_l1_table: + vfree(qcow_data->l1_table); +free_qcow_data: + kfree(qcow_data); + xlo_fmt->private_data = NULL; + return ret; +} + +static void qcow_file_fmt_exit(struct xloop_file_fmt *xlo_fmt) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + +#ifdef CONFIG_DEBUG_FS + __qcow_file_fmt_dbgfs_exit(xlo_fmt); +#endif + + __qcow_file_fmt_compression_exit(xlo_fmt); + + if (qcow_data->l1_table) { + vfree(qcow_data->l1_table); + } + + if (qcow_data->l2_table_cache) { + xloop_file_fmt_qcow_cache_destroy(xlo_fmt); + } + + if (qcow_data) { + kfree(qcow_data); + xlo_fmt->private_data = NULL; + } +} + +/* + * __qcow_file_fmt_zlib_decompress() + * + * Decompress some data (not more than @src_size bytes) to produce exactly + * @dest_size bytes using zlib compression method + * + * @xlo_fmt - QCOW file format + * @dest - destination buffer, @dest_size bytes + * @src - source buffer, @src_size bytes + * + * Returns: 0 on success + * -EIO on fail + */ +static ssize_t __qcow_file_fmt_zlib_decompress(struct xloop_file_fmt *xlo_fmt, + void *dest, + size_t dest_size, + const void *src, + size_t src_size) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + u8 zerostuff = 0; + ssize_t ret = 0; + + ret = zlib_inflateReset(qcow_data->zlib_dstrm); + if (ret != Z_OK) { + ret = -EINVAL; + goto out; + } + + qcow_data->zlib_dstrm->avail_in = src_size; + qcow_data->zlib_dstrm->next_in = (void *)src; + qcow_data->zlib_dstrm->avail_out = dest_size; + qcow_data->zlib_dstrm->next_out = dest; + + ret = zlib_inflate(qcow_data->zlib_dstrm, Z_SYNC_FLUSH); + /* + * Work around a bug in zlib, which sometimes wants to taste an extra + * byte when being used in the (undocumented) raw deflate mode. + * (From USAGI). + */ + if (ret == Z_OK && !qcow_data->zlib_dstrm->avail_in && + qcow_data->zlib_dstrm->avail_out) { + qcow_data->zlib_dstrm->next_in = &zerostuff; + qcow_data->zlib_dstrm->avail_in = 1; + ret = zlib_inflate(qcow_data->zlib_dstrm, Z_FINISH); + } + if (ret != Z_STREAM_END) { + ret = -EIO; + goto out; + } + +out: + return ret; +} + +#ifdef CONFIG_ZSTD_DECOMPRESS +/* + * __qcow_file_fmt_zstd_decompress() + * + * Decompress some data (not more than @src_size bytes) to produce exactly + * @dest_size bytes using zstd compression method + * + * @xlo_fmt - QCOW file format + * @dest - destination buffer, @dest_size bytes + * @src - source buffer, @src_size bytes + * + * Returns: 0 on success + * -EIO on any error + */ +static ssize_t __qcow_file_fmt_zstd_decompress(struct xloop_file_fmt *xlo_fmt, + void *dest, + size_t dest_size, + const void *src, + size_t src_size) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + size_t zstd_ret = 0; + ssize_t ret = 0; + + ZSTD_outBuffer output = { + .dst = dest, + .size = dest_size, + .pos = 0 + }; + + ZSTD_inBuffer input = { + .src = src, + .size = src_size, + .pos = 0 + }; + + zstd_ret = ZSTD_resetDStream(qcow_data->zstd_dstrm); + + if (ZSTD_isError(zstd_ret)) { + ret = -EIO; + goto out; + } + + /* + * The compressed stream from the input buffer may consist of more + * than one zstd frame. So we iterate until we get a fully + * uncompressed cluster. + * From zstd docs related to ZSTD_decompressStream: + * "return : 0 when a frame is completely decoded and fully flushed" + * We suppose that this means: each time ZSTD_decompressStream reads + * only ONE full frame and returns 0 if and only if that frame + * is completely decoded and flushed. Only after returning 0, + * ZSTD_decompressStream reads another ONE full frame. + */ + while (output.pos < output.size) { + size_t last_in_pos = input.pos; + size_t last_out_pos = output.pos; + zstd_ret = ZSTD_decompressStream(qcow_data->zstd_dstrm, &output, &input); + + if (ZSTD_isError(zstd_ret)) { + ret = -EIO; + break; + } + + /* + * The ZSTD manual is vague about what to do if it reads + * the buffer partially, and we don't want to get stuck + * in an infinite loop where ZSTD_decompressStream + * returns > 0 waiting for another input chunk. So, we add + * a check which ensures that the loop makes some progress + * on each step. + */ + if (last_in_pos >= input.pos && + last_out_pos >= output.pos) { + ret = -EIO; + break; + } + } + /* + * Make sure that we have the frame fully flushed here + * if not, we somehow managed to get uncompressed cluster + * greater then the cluster size, possibly because of its + * damage. + */ + if (zstd_ret > 0) { + ret = -EIO; + } + +out: + ASSERT(ret == 0 || ret == -EIO); + return ret; +} +#endif + +/* + * __qcow_file_fmt_buffer_decompress() + * + * Decompress @src_size bytes of data using the compression + * method defined by the image compression type + * + * @xlo_fmt - QCOW file format + * @dest - destination buffer, @dest_size bytes + * @src - source buffer, @src_size bytes + * + * Returns: compressed size on success + * a negative error code on failure + */ +static ssize_t __qcow_file_fmt_buffer_decompress(struct xloop_file_fmt *xlo_fmt, + void *dest, + size_t dest_size, + const void *src, + size_t src_size) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + qcow_file_fmt_decompress_fn decompress_fn; + + switch (qcow_data->compression_type) { + case QCOW_COMPRESSION_TYPE_ZLIB: + decompress_fn = __qcow_file_fmt_zlib_decompress; + break; + +#ifdef CONFIG_ZSTD_DECOMPRESS + case QCOW_COMPRESSION_TYPE_ZSTD: + decompress_fn = __qcow_file_fmt_zstd_decompress; + break; +#endif + default: + return -EINVAL; + } + + return decompress_fn(xlo_fmt, dest, dest_size, src, src_size); +} + + +static int __qcow_file_fmt_read_compressed(struct xloop_file_fmt *xlo_fmt, + struct bio_vec *bvec, + u64 file_cluster_offset, + u64 offset, + u64 bytes, + u64 bytes_done) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + int ret = 0, csize, nb_csectors; + u64 coffset; + u8 *in_buf = NULL; + ssize_t len; + void *data; + unsigned long irq_flags; + int offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster( + qcow_data, offset); + + coffset = file_cluster_offset & qcow_data->cluster_offset_mask; + nb_csectors = ((file_cluster_offset >> qcow_data->csize_shift) & + qcow_data->csize_mask) + 1; + csize = nb_csectors * QCOW_COMPRESSED_SECTOR_SIZE - + (coffset & ~QCOW_COMPRESSED_SECTOR_MASK); + + + if (qcow_data->cmp_last_coffset != coffset) { + in_buf = vmalloc(csize); + if (!in_buf) { + qcow_data->cmp_last_coffset = ULLONG_MAX; + return -ENOMEM; + } + qcow_data->cmp_last_coffset = coffset; + len = kernel_read(xlo->xlo_backing_file, in_buf, csize, &coffset); + if (len < 0) { + qcow_data->cmp_last_coffset = ULLONG_MAX; + ret = len; + goto out_free_in_buf; + } + + if (__qcow_file_fmt_buffer_decompress(xlo_fmt, qcow_data->cmp_out_buf, + qcow_data->cluster_size, in_buf, csize) < 0) { + qcow_data->cmp_last_coffset = ULLONG_MAX; + ret = -EIO; + goto out_free_in_buf; + } + } + + ASSERT(bytes <= bvec->bv_len); + data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; + memcpy(data, qcow_data->cmp_out_buf + offset_in_cluster, bytes); + flush_dcache_page(bvec->bv_page); + bvec_kunmap_irq(data, &irq_flags); + +out_free_in_buf: + vfree(in_buf); + + return ret; +} + +static int __qcow_file_fmt_read_bvec(struct xloop_file_fmt *xlo_fmt, + struct bio_vec *bvec, + loff_t *ppos) +{ + struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data; + struct xloop_device *xlo = xloop_file_fmt_get_xlo(xlo_fmt); + int offset_in_cluster; + int ret; + unsigned int cur_bytes; /* number of bytes in current iteration */ + u64 bytes; + u64 host_offset = 0; + u64 bytes_done = 0; + enum xloop_file_fmt_qcow_subcluster_type type; + void *data; + unsigned long irq_flags; + ssize_t len; + loff_t pos_read; + + bytes = bvec->bv_len; + + while (bytes != 0) { + + /* prepare next request */ + cur_bytes = bytes; + + ret = xloop_file_fmt_qcow_get_host_offset(xlo_fmt, *ppos, + &cur_bytes, &host_offset, &type); + if (ret < 0) { + goto fail; + } + + offset_in_cluster = xloop_file_fmt_qcow_offset_into_cluster( + qcow_data, *ppos); + + switch (type) { + case QCOW_SUBCLUSTER_ZERO_PLAIN: + case QCOW_SUBCLUSTER_ZERO_ALLOC: + case QCOW_SUBCLUSTER_UNALLOCATED_PLAIN: + case QCOW_SUBCLUSTER_UNALLOCATED_ALLOC: + data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; + memset(data, 0, cur_bytes); + flush_dcache_page(bvec->bv_page); + bvec_kunmap_irq(data, &irq_flags); + break; + + case QCOW_SUBCLUSTER_COMPRESSED: + ret = __qcow_file_fmt_read_compressed(xlo_fmt, bvec, + host_offset, *ppos, cur_bytes, bytes_done); + if (ret < 0) { + goto fail; + } + + break; + + case QCOW_SUBCLUSTER_NORMAL: + pos_read = host_offset; + + data = bvec_kmap_irq(bvec, &irq_flags) + bytes_done; + len = kernel_read(xlo->xlo_backing_file, data, cur_bytes, + &pos_read); + flush_dcache_page(bvec->bv_page); + bvec_kunmap_irq(data, &irq_flags); + + if (len < 0) + return len; + + ASSERT(len == cur_bytes); + break; + + default: + ret = -EIO; + goto fail; + } + + bytes -= cur_bytes; + *ppos += cur_bytes; + bytes_done += cur_bytes; + } + + ret = 0; + +fail: + return ret; +} + +static int qcow_file_fmt_read(struct xloop_file_fmt *xlo_fmt, + struct request *rq) +{ + struct bio_vec bvec; + struct req_iterator iter; + loff_t pos; + int ret = 0; + + pos = __qcow_file_fmt_rq_get_pos(xlo_fmt, rq); + + rq_for_each_segment(bvec, rq, iter) { + ret = __qcow_file_fmt_read_bvec(xlo_fmt, &bvec, &pos); + if (ret) + return ret; + + cond_resched(); + } + + return ret; +} + +static loff_t qcow_file_fmt_sector_size(struct xloop_file_fmt *xlo_fmt, + struct file *file, loff_t offset, loff_t sizelimit) +{ + struct xloop_file_fmt_qcow_header header; + loff_t xloopsize; + int ret; + + /* temporary read the QCOW file header of other QCOW image file */ + ret = __qcow_file_fmt_header_read(xlo_fmt, file, &header); + if (ret) + return 0; + + /* compute xloopsize in bytes */ + xloopsize = header.size; + if (offset > 0) + xloopsize -= offset; + /* offset is beyond i_size, weird but possible */ + if (xloopsize < 0) + return 0; + + if (sizelimit > 0 && sizelimit < xloopsize) + xloopsize = sizelimit; + /* + * Unfortunately, if we want to do I/O on the device, + * the number of 512-byte sectors has to fit into a sector_t. + */ + return xloopsize >> 9; +} + +static struct xloop_file_fmt_ops qcow_file_fmt_ops = { + .init = qcow_file_fmt_init, + .exit = qcow_file_fmt_exit, + .read = qcow_file_fmt_read, + .write = NULL, + .read_aio = NULL, + .write_aio = NULL, + .write_zeros = NULL, + .discard = NULL, + .flush = NULL, + .sector_size = qcow_file_fmt_sector_size, +}; + +static struct xloop_file_fmt_driver qcow_file_fmt_driver = { + .name = "QCOW", + .file_fmt_type = XLO_FILE_FMT_QCOW, + .ops = &qcow_file_fmt_ops, + .owner = THIS_MODULE, +}; + +static int __init xloop_file_fmt_qcow_init(void) +{ + pr_info("init xloop device QCOW file format driver\n"); + return xloop_file_fmt_register_driver(&qcow_file_fmt_driver); +} + +static void __exit xloop_file_fmt_qcow_exit(void) +{ + pr_info("exit xloop device QCOW file format driver\n"); + xloop_file_fmt_unregister_driver(&qcow_file_fmt_driver); +} + +module_init(xloop_file_fmt_qcow_init); +module_exit(xloop_file_fmt_qcow_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Manuel Bentele "); +MODULE_DESCRIPTION("xloop device QCOW file format driver"); +MODULE_SOFTDEP("pre: xloop"); +MODULE_VERSION(__stringify(VERSION)); -- cgit v1.2.3-55-g7522