summaryrefslogtreecommitdiffstats
path: root/kernel/xloop_file_fmt_qcow_main.h
diff options
context:
space:
mode:
authorManuel Bentele2020-10-07 11:01:56 +0200
committerManuel Bentele2020-10-07 11:01:56 +0200
commite19fe3beba17c3b85c4d776deaa7834565d51490 (patch)
tree88eea1ad0e5dc5b1a2254a3f459fa9b461b80662 /kernel/xloop_file_fmt_qcow_main.h
parentAdded RPM package creation and moved to relative CMake installation paths (diff)
downloadxloop-e19fe3beba17c3b85c4d776deaa7834565d51490.tar.gz
xloop-e19fe3beba17c3b85c4d776deaa7834565d51490.tar.xz
xloop-e19fe3beba17c3b85c4d776deaa7834565d51490.zip
Updated QCOW file format driver with upstream patches
Ported recent upstream patches from QEMU's qcow2 file format to xloop's QCOW file format driver. The following changes had been made to the QCOW file format driver: - support to read extended L2 has been added - added decompression interface to support various compression types - implemented the ZSTD compression type for QCOW images
Diffstat (limited to 'kernel/xloop_file_fmt_qcow_main.h')
-rw-r--r--kernel/xloop_file_fmt_qcow_main.h269
1 files changed, 248 insertions, 21 deletions
diff --git a/kernel/xloop_file_fmt_qcow_main.h b/kernel/xloop_file_fmt_qcow_main.h
index e6031be..023c679 100644
--- a/kernel/xloop_file_fmt_qcow_main.h
+++ b/kernel/xloop_file_fmt_qcow_main.h
@@ -19,6 +19,9 @@
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/zlib.h>
+#ifdef CONFIG_ZSTD_DECOMPRESS
+#include <linux/zstd.h>
+#endif
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
@@ -80,6 +83,33 @@ do { \
/* The cluster reads as all zeros */
#define QCOW_OFLAG_ZERO (1ULL << 0)
+#define QCOW_EXTL2_SUBCLUSTERS_PER_CLUSTER 32
+
+/* The subcluster X [0..31] is allocated */
+#define QCOW_OFLAG_SUB_ALLOC(X) (1ULL << (X))
+/* The subcluster X [0..31] reads as zeroes */
+#define QCOW_OFLAG_SUB_ZERO(X) (QCOW_OFLAG_SUB_ALLOC(X) << 32)
+/* Subclusters [X, Y) (0 <= X <= Y <= 32) are allocated */
+#define QCOW_OFLAG_SUB_ALLOC_RANGE(X, Y) \
+ (QCOW_OFLAG_SUB_ALLOC(Y) - QCOW_OFLAG_SUB_ALLOC(X))
+/* Subclusters [X, Y) (0 <= X <= Y <= 32) read as zeroes */
+#define QCOW_OFLAG_SUB_ZERO_RANGE(X, Y) \
+ (QCOW_OFLAG_SUB_ALLOC_RANGE(X, Y) << 32)
+/* L2 entry bitmap with all allocation bits set */
+#define QCOW_L2_BITMAP_ALL_ALLOC (QCOW_OFLAG_SUB_ALLOC_RANGE(0, 32))
+/* L2 entry bitmap with all "read as zeroes" bits set */
+#define QCOW_L2_BITMAP_ALL_ZEROES (QCOW_OFLAG_SUB_ZERO_RANGE(0, 32))
+
+/* Size of normal and extended L2 entries */
+#define QCOW_L2E_SIZE_NORMAL (sizeof(u64))
+#define QCOW_L2E_SIZE_EXTENDED (sizeof(u64) * 2)
+
+/* Size of L1 table entries */
+#define QCOW_L1E_SIZE (sizeof(u64))
+
+/* Size of reftable entries */
+#define QCOW_REFTABLE_ENTRY_SIZE (sizeof(u64))
+
#define QCOW_MIN_CLUSTER_BITS 9
#define QCOW_MAX_CLUSTER_BITS 21
@@ -104,7 +134,7 @@ do { \
/* Buffer size for debugfs file buffer to receive and display offset and
* cluster offset information */
#define QCOW_OFFSET_BUF_LEN 32
-#define QCOW_CLUSTER_BUF_LEN 128
+#define QCOW_CLUSTER_BUF_LEN 256
struct xloop_file_fmt_qcow_header {
u32 magic;
@@ -128,6 +158,12 @@ struct xloop_file_fmt_qcow_header {
u32 refcount_order;
u32 header_length;
+
+ /* Additional fields */
+ u8 compression_type;
+
+ /* header must be a multiple of 8 */
+ u8 padding[7];
} __attribute__((packed));
struct xloop_file_fmt_qcow_snapshot_header {
@@ -144,11 +180,11 @@ struct xloop_file_fmt_qcow_snapshot_header {
u64 vm_clock_nsec;
u32 vm_state_size;
- /* for extension */
- u32 extra_data_size;
- /* extra data follows */
- /* id_str follows */
- /* name follows */
+
+ /* Size of all extra data, including QCowSnapshotExtraData if available */
+ u32 extra_data_size;
+ /* Data beyond QCowSnapshotExtraData, if any */
+ void *unknown_extra_data;
} __attribute__((packed));
enum {
@@ -162,13 +198,19 @@ enum {
QCOW_INCOMPAT_DIRTY_BITNR = 0,
QCOW_INCOMPAT_CORRUPT_BITNR = 1,
QCOW_INCOMPAT_DATA_FILE_BITNR = 2,
+ QCOW_INCOMPAT_COMPRESSION_BITNR = 3,
+ QCOW_INCOMPAT_EXTL2_BITNR = 4,
QCOW_INCOMPAT_DIRTY = 1 << QCOW_INCOMPAT_DIRTY_BITNR,
QCOW_INCOMPAT_CORRUPT = 1 << QCOW_INCOMPAT_CORRUPT_BITNR,
QCOW_INCOMPAT_DATA_FILE = 1 << QCOW_INCOMPAT_DATA_FILE_BITNR,
+ QCOW_INCOMPAT_COMPRESSION = 1 << QCOW_INCOMPAT_COMPRESSION_BITNR,
+ QCOW_INCOMPAT_EXTL2 = 1 << QCOW_INCOMPAT_EXTL2_BITNR,
QCOW_INCOMPAT_MASK = QCOW_INCOMPAT_DIRTY
| QCOW_INCOMPAT_CORRUPT
- | QCOW_INCOMPAT_DATA_FILE,
+ | QCOW_INCOMPAT_DATA_FILE
+ | QCOW_INCOMPAT_COMPRESSION
+ | QCOW_INCOMPAT_EXTL2,
};
/* compatible feature bits */
@@ -190,12 +232,19 @@ enum {
QCOW_AUTOCLEAR_DATA_FILE_RAW,
};
+enum xloop_file_fmt_qcow_compression_type {
+ QCOW_COMPRESSION_TYPE_ZLIB,
+ QCOW_COMPRESSION_TYPE_ZSTD,
+};
+
struct xloop_file_fmt_qcow_data {
u64 size;
int cluster_bits;
int cluster_size;
- int cluster_sectors;
int l2_slice_size;
+ int subcluster_bits;
+ int subcluster_size;
+ int subclusters_per_cluster;
int l2_bits;
int l2_size;
int l1_size;
@@ -237,10 +286,27 @@ struct xloop_file_fmt_qcow_data {
u64 compatible_features;
u64 autoclear_features;
- struct z_stream_s *strm;
+ /* ZLIB specific data */
+ z_streamp zlib_dstrm;
+
+ /* ZSTD specific data */
+#ifdef CONFIG_ZSTD_DECOMPRESS
+ void *zstd_dworkspace;
+ ZSTD_DStream *zstd_dstrm;
+#endif
+
+ /* used to cache last compressed QCOW cluster */
u8 *cmp_out_buf;
u64 cmp_last_coffset;
+ /*
+ * Compression type used for the image. Default: 0 - ZLIB
+ * The image compression type is set on image creation.
+ * For now, the only way to change the compression type
+ * is to convert the image with the desired compression type set.
+ */
+ enum xloop_file_fmt_qcow_compression_type compression_type;
+
/* debugfs entries */
#ifdef CONFIG_DEBUG_FS
struct dentry *dbgfs_dir;
@@ -265,6 +331,34 @@ struct xloop_file_fmt_qcow_cow_region {
unsigned nb_bytes;
};
+/*
+ * In images with standard L2 entries all clusters are treated as if
+ * they had one subcluster so xloop_file_fmt_qcow_cluster_type and
+ * xloop_file_fmt_qcow_subcluster_type can be mapped to each other and
+ * have the exact same meaning (QCOW_SUBCLUSTER_UNALLOCATED_ALLOC cannot
+ * happen in these images).
+ *
+ * In images with extended L2 entries xloop_file_fmt_qcow_cluster_type
+ * refers to the complete cluster and xloop_file_fmt_qcow_subcluster_type
+ * to each of the individual subclusters, so there are several possible
+ * combinations:
+ *
+ * |--------------+---------------------------|
+ * | Cluster type | Possible subcluster types |
+ * |--------------+---------------------------|
+ * | UNALLOCATED | UNALLOCATED_PLAIN |
+ * | | ZERO_PLAIN |
+ * |--------------+---------------------------|
+ * | NORMAL | UNALLOCATED_ALLOC |
+ * | | ZERO_ALLOC |
+ * | | NORMAL |
+ * |--------------+---------------------------|
+ * | COMPRESSED | COMPRESSED |
+ * |--------------+---------------------------|
+ *
+ * QCOW_SUBCLUSTER_INVALID means that the L2 entry is incorrect and
+ * the image should be marked corrupt.
+ */
enum xloop_file_fmt_qcow_cluster_type {
QCOW_CLUSTER_UNALLOCATED,
QCOW_CLUSTER_ZERO_PLAIN,
@@ -273,6 +367,16 @@ enum xloop_file_fmt_qcow_cluster_type {
QCOW_CLUSTER_COMPRESSED,
};
+enum xloop_file_fmt_qcow_subcluster_type {
+ QCOW_SUBCLUSTER_UNALLOCATED_PLAIN,
+ QCOW_SUBCLUSTER_UNALLOCATED_ALLOC,
+ QCOW_SUBCLUSTER_ZERO_PLAIN,
+ QCOW_SUBCLUSTER_ZERO_ALLOC,
+ QCOW_SUBCLUSTER_NORMAL,
+ QCOW_SUBCLUSTER_COMPRESSED,
+ QCOW_SUBCLUSTER_INVALID,
+};
+
enum xloop_file_fmt_qcow_metadata_overlap {
QCOW_OL_MAIN_HEADER_BITNR = 0,
QCOW_OL_ACTIVE_L1_BITNR = 1,
@@ -314,25 +418,51 @@ enum xloop_file_fmt_qcow_metadata_overlap {
#define QCOW_OL_ALL \
(QCOW_OL_CACHED | QCOW_OL_INACTIVE_L2)
-#define L1E_OFFSET_MASK 0x00fffffffffffe00ULL
-#define L2E_OFFSET_MASK 0x00fffffffffffe00ULL
-#define L2E_COMPRESSED_OFFSET_SIZE_MASK 0x3fffffffffffffffULL
+#define QCOW_L1E_OFFSET_MASK 0x00fffffffffffe00ULL
+#define QCOW_L2E_OFFSET_MASK 0x00fffffffffffe00ULL
+#define QCOW_L2E_COMPRESSED_OFFSET_SIZE_MASK 0x3fffffffffffffffULL
-#define REFT_OFFSET_MASK 0xfffffffffffffe00ULL
+static inline bool xloop_file_fmt_qcow_has_subclusters(
+ struct xloop_file_fmt_qcow_data *qcow_data)
+{
+ return qcow_data->incompatible_features & QCOW_INCOMPAT_EXTL2;
+}
+
+static inline size_t xloop_file_fmt_qcow_l2_entry_size(
+ struct xloop_file_fmt_qcow_data *qcow_data)
+{
+ return xloop_file_fmt_qcow_has_subclusters(qcow_data) ?
+ QCOW_L2E_SIZE_EXTENDED : QCOW_L2E_SIZE_NORMAL;
+}
+
+static inline u64 xloop_file_fmt_qcow_get_l2_entry(
+ struct xloop_file_fmt_qcow_data *qcow_data, u64 *l2_slice, int idx)
+{
+ idx *= xloop_file_fmt_qcow_l2_entry_size(qcow_data) / sizeof(u64);
+ return be64_to_cpu(l2_slice[idx]);
+}
-#define INV_OFFSET (-1ULL)
+static inline u64 xloop_file_fmt_qcow_get_l2_bitmap(
+ struct xloop_file_fmt_qcow_data *qcow_data, u64 *l2_slice, int idx)
+{
+ if (xloop_file_fmt_qcow_has_subclusters(qcow_data)) {
+ idx *= xloop_file_fmt_qcow_l2_entry_size(qcow_data) / sizeof(u64);
+ return be64_to_cpu(l2_slice[idx + 1]);
+ } else {
+ return 0; /* For convenience only; this value has no meaning. */
+ }
+}
static inline bool xloop_file_fmt_qcow_has_data_file(
- struct xloop_file_fmt *xlo_fmt)
+ struct xloop_file_fmt_qcow_data *qcow_data)
{
/* At the moment, there is no support for copy on write! */
return false;
}
static inline bool xloop_file_fmt_qcow_data_file_is_raw(
- struct xloop_file_fmt *xlo_fmt)
+ struct xloop_file_fmt_qcow_data *qcow_data)
{
- struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data;
return !!(qcow_data->autoclear_features &
QCOW_AUTOCLEAR_DATA_FILE_RAW);
}
@@ -349,6 +479,12 @@ static inline s64 xloop_file_fmt_qcow_offset_into_cluster(
return offset & (qcow_data->cluster_size - 1);
}
+static inline s64 xloop_file_fmt_qcow_offset_into_subcluster(
+ struct xloop_file_fmt_qcow_data *qcow_data, s64 offset)
+{
+ return offset & (qcow_data->subcluster_size - 1);
+}
+
static inline s64 xloop_file_fmt_qcow_size_to_clusters(
struct xloop_file_fmt_qcow_data *qcow_data, u64 size)
{
@@ -382,6 +518,13 @@ static inline int xloop_file_fmt_qcow_offset_to_l2_slice_index(
(qcow_data->l2_slice_size - 1);
}
+static inline int xloop_file_fmt_qcow_offset_to_sc_index(
+ struct xloop_file_fmt_qcow_data *qcow_data, s64 offset)
+{
+ return (offset >> qcow_data->subcluster_bits) &
+ (qcow_data->subclusters_per_cluster - 1);
+}
+
static inline s64 xloop_file_fmt_qcow_vm_state_offset(
struct xloop_file_fmt_qcow_data *qcow_data)
{
@@ -390,22 +533,25 @@ static inline s64 xloop_file_fmt_qcow_vm_state_offset(
}
static inline enum xloop_file_fmt_qcow_cluster_type
-xloop_file_fmt_qcow_get_cluster_type(struct xloop_file_fmt *xlo_fmt, u64 l2_entry)
+xloop_file_fmt_qcow_get_cluster_type(struct xloop_file_fmt *xlo_fmt,
+ u64 l2_entry)
{
+ struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data;
+
if (l2_entry & QCOW_OFLAG_COMPRESSED) {
return QCOW_CLUSTER_COMPRESSED;
} else if (l2_entry & QCOW_OFLAG_ZERO) {
- if (l2_entry & L2E_OFFSET_MASK) {
+ if (l2_entry & QCOW_L2E_OFFSET_MASK) {
return QCOW_CLUSTER_ZERO_ALLOC;
}
return QCOW_CLUSTER_ZERO_PLAIN;
- } else if (!(l2_entry & L2E_OFFSET_MASK)) {
+ } else if (!(l2_entry & QCOW_L2E_OFFSET_MASK)) {
/* Offset 0 generally means unallocated, but it is ambiguous
* with external data files because 0 is a valid offset there.
* However, all clusters in external data files always have
* refcount 1, so we can rely on QCOW_OFLAG_COPIED to
* disambiguate. */
- if (xloop_file_fmt_qcow_has_data_file(xlo_fmt) &&
+ if (xloop_file_fmt_qcow_has_data_file(qcow_data) &&
(l2_entry & QCOW_OFLAG_COPIED)) {
return QCOW_CLUSTER_NORMAL;
} else {
@@ -416,4 +562,85 @@ xloop_file_fmt_qcow_get_cluster_type(struct xloop_file_fmt *xlo_fmt, u64 l2_entr
}
}
+/*
+ * In an image without subsclusters @l2_bitmap is ignored and
+ * @sc_index must be 0.
+ * Return QCOW_SUBCLUSTER_INVALID if an invalid l2 entry is detected
+ * (this checks the whole entry and bitmap, not only the bits related
+ * to subcluster @sc_index).
+ */
+static inline enum xloop_file_fmt_qcow_subcluster_type
+xloop_file_fmt_qcow_get_subcluster_type(struct xloop_file_fmt *xlo_fmt,
+ u64 l2_entry, u64 l2_bitmap, unsigned int sc_index)
+{
+ struct xloop_file_fmt_qcow_data *qcow_data = xlo_fmt->private_data;
+ enum xloop_file_fmt_qcow_cluster_type type =
+ xloop_file_fmt_qcow_get_cluster_type(xlo_fmt, l2_entry);
+ ASSERT(sc_index < qcow_data->subclusters_per_cluster);
+
+ if (xloop_file_fmt_qcow_has_subclusters(qcow_data)) {
+ switch (type) {
+ case QCOW_CLUSTER_COMPRESSED:
+ return QCOW_SUBCLUSTER_COMPRESSED;
+ case QCOW_CLUSTER_NORMAL:
+ if ((l2_bitmap >> 32) & l2_bitmap) {
+ return QCOW_SUBCLUSTER_INVALID;
+ } else if (l2_bitmap & QCOW_OFLAG_SUB_ZERO(sc_index)) {
+ return QCOW_SUBCLUSTER_ZERO_ALLOC;
+ } else if (l2_bitmap & QCOW_OFLAG_SUB_ALLOC(sc_index)) {
+ return QCOW_SUBCLUSTER_NORMAL;
+ } else {
+ return QCOW_SUBCLUSTER_UNALLOCATED_ALLOC;
+ }
+ case QCOW_CLUSTER_UNALLOCATED:
+ if (l2_bitmap & QCOW_L2_BITMAP_ALL_ALLOC) {
+ return QCOW_SUBCLUSTER_INVALID;
+ } else if (l2_bitmap & QCOW_OFLAG_SUB_ZERO(sc_index)) {
+ return QCOW_SUBCLUSTER_ZERO_PLAIN;
+ } else {
+ return QCOW_SUBCLUSTER_UNALLOCATED_PLAIN;
+ }
+ default:
+ /* not reachable */
+ ASSERT(false);
+ return QCOW_SUBCLUSTER_INVALID;
+ }
+ } else {
+ switch (type) {
+ case QCOW_CLUSTER_COMPRESSED:
+ return QCOW_SUBCLUSTER_COMPRESSED;
+ case QCOW_CLUSTER_ZERO_PLAIN:
+ return QCOW_SUBCLUSTER_ZERO_PLAIN;
+ case QCOW_CLUSTER_ZERO_ALLOC:
+ return QCOW_SUBCLUSTER_ZERO_ALLOC;
+ case QCOW_CLUSTER_NORMAL:
+ return QCOW_SUBCLUSTER_NORMAL;
+ case QCOW_CLUSTER_UNALLOCATED:
+ return QCOW_SUBCLUSTER_UNALLOCATED_PLAIN;
+ default:
+ /* not reachable */
+ ASSERT(false);
+ return QCOW_SUBCLUSTER_INVALID;
+ }
+ }
+}
+
+#ifdef CONFIG_DEBUG_FS
+static inline const char *xloop_file_fmt_qcow_get_subcluster_name(
+ const enum xloop_file_fmt_qcow_subcluster_type type)
+{
+ static const char *subcluster_names[] = {
+ "QCOW2_SUBCLUSTER_UNALLOCATED_PLAIN",
+ "QCOW2_SUBCLUSTER_UNALLOCATED_ALLOC",
+ "QCOW2_SUBCLUSTER_ZERO_PLAIN",
+ "QCOW2_SUBCLUSTER_ZERO_ALLOC",
+ "QCOW2_SUBCLUSTER_NORMAL",
+ "QCOW2_SUBCLUSTER_COMPRESSED",
+ "QCOW2_SUBCLUSTER_INVALID"
+ };
+
+ return subcluster_names[type];
+}
+#endif
+
#endif