summaryrefslogtreecommitdiffstats
path: root/kernel/xloop_file_fmt_qcow_main.h
diff options
context:
space:
mode:
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