@@ -4,9 +4,17 @@
#include <linux/types.h>
#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb)
+
#define QCOW1_VERSION 1
+#define QCOW2_VERSION 2
+
+#define QCOW1_OFLAG_COMPRESSED (1LL << 63)
+
+#define QCOW1_OFLAG_MASK QCOW1_OFLAG_COMPRESSED
-#define QCOW_OFLAG_COMPRESSED (1LL << 63)
+#define QCOW2_OFLAG_COPIED (1LL << 63)
+#define QCOW2_OFLAG_COMPRESSED (1LL << 62)
+#define QCOW2_OFLAG_MASK (QCOW2_OFLAG_COPIED|QCOW2_OFLAG_COMPRESSED)
struct qcow_table {
u32 table_size;
@@ -19,7 +27,16 @@ struct qcow {
int fd;
};
-struct qcow1_header {
+struct qcow_header {
+ u64 size; /* in bytes */
+ u64 l1_table_offset;
+ u32 l1_size;
+ u8 cluster_bits;
+ u8 l2_bits;
+ uint64_t oflag_mask;
+};
+
+struct qcow1_header_disk {
u32 magic;
u32 version;
@@ -36,6 +53,27 @@ struct qcow1_header {
u64 l1_table_offset;
};
+struct qcow2_header_disk {
+ u32 magic;
+ u32 version;
+
+ u64 backing_file_offset;
+ u32 backing_file_size;
+
+ u32 cluster_bits;
+ u64 size; /* in bytes */
+ u32 crypt_method;
+
+ u32 l1_size;
+ u64 l1_table_offset;
+
+ u64 refcount_table_offset;
+ u32 refcount_table_clusters;
+
+ u32 nb_snapshots;
+ u64 snapshots_offset;
+};
+
struct disk_image *qcow_probe(int fd);
#endif /* KVM__QCOW_H */
@@ -17,28 +17,28 @@
static inline u64 get_l1_index(struct qcow *q, u64 offset)
{
- struct qcow1_header *header = q->header;
+ struct qcow_header *header = q->header;
return offset >> (header->l2_bits + header->cluster_bits);
}
static inline u64 get_l2_index(struct qcow *q, u64 offset)
{
- struct qcow1_header *header = q->header;
+ struct qcow_header *header = q->header;
return (offset >> (header->cluster_bits)) & ((1 << header->l2_bits)-1);
}
static inline u64 get_cluster_offset(struct qcow *q, u64 offset)
{
- struct qcow1_header *header = q->header;
+ struct qcow_header *header = q->header;
return offset & ((1 << header->cluster_bits)-1);
}
static ssize_t qcow1_read_cluster(struct qcow *q, u64 offset, void *dst, u32 dst_len)
{
- struct qcow1_header *header = q->header;
+ struct qcow_header *header = q->header;
struct qcow_table *table = &q->table;
u64 *l2_table = NULL;
u64 l2_table_offset;
@@ -64,7 +64,7 @@ static ssize_t qcow1_read_cluster(struct qcow *q, u64 offset, void *dst, u32 dst
if (length > dst_len)
length = dst_len;
- l2_table_offset = table->l1_table[l1_idx];
+ l2_table_offset = table->l1_table[l1_idx] & ~header->oflag_mask;
if (!l2_table_offset)
goto zero_cluster;
@@ -81,7 +81,7 @@ static ssize_t qcow1_read_cluster(struct qcow *q, u64 offset, void *dst, u32 dst
if (l2_idx >= l2_table_size)
goto out_error;
- clust_start = be64_to_cpu(l2_table[l2_idx]);
+ clust_start = be64_to_cpu(l2_table[l2_idx]) & ~header->oflag_mask;
if (!clust_start)
goto zero_cluster;
@@ -105,7 +105,7 @@ static int qcow1_read_sector(struct disk_image *self, uint64_t sector,
void *dst, uint32_t dst_len)
{
struct qcow *q = self->priv;
- struct qcow1_header *header = q->header;
+ struct qcow_header *header = q->header;
char *buf = dst;
u64 offset;
u32 nr_read;
@@ -157,12 +157,11 @@ struct disk_image_operations qcow1_disk_ops = {
static int qcow_read_l1_table(struct qcow *q)
{
- struct qcow1_header *header = q->header;
+ struct qcow_header *header = q->header;
struct qcow_table *table = &q->table;
u64 i;
- table->table_size = header->size / ((1 << header->l2_bits) *
- (1 << header->cluster_bits));
+ table->table_size = header->l1_size;
table->l1_table = calloc(table->table_size, sizeof(u64));
if (!table->l1_table)
@@ -178,25 +177,128 @@ static int qcow_read_l1_table(struct qcow *q)
return 0;
}
+static void *qcow2_read_header(int fd)
+{
+ struct qcow2_header_disk f_header;
+ struct qcow_header *header;
+
+ header = malloc(sizeof(struct qcow_header));
+ if (!header)
+ return NULL;
+
+ if (pread_in_full(fd, &f_header, sizeof(struct qcow2_header_disk), 0) < 0)
+ return NULL;
+
+ be32_to_cpus(&f_header.magic);
+ be32_to_cpus(&f_header.version);
+ be64_to_cpus(&f_header.backing_file_offset);
+ be32_to_cpus(&f_header.backing_file_size);
+ be32_to_cpus(&f_header.cluster_bits);
+ be64_to_cpus(&f_header.size);
+ be32_to_cpus(&f_header.crypt_method);
+ be32_to_cpus(&f_header.l1_size);
+ be64_to_cpus(&f_header.l1_table_offset);
+ be64_to_cpus(&f_header.refcount_table_offset);
+ be32_to_cpus(&f_header.refcount_table_clusters);
+ be32_to_cpus(&f_header.nb_snapshots);
+ be64_to_cpus(&f_header.snapshots_offset);
+
+ *header = (struct qcow_header) {
+ .size = f_header.size,
+ .l1_table_offset = f_header.l1_table_offset,
+ .l1_size = f_header.l1_size,
+ .cluster_bits = f_header.cluster_bits,
+ .l2_bits = f_header.cluster_bits - 3,
+ .oflag_mask = QCOW2_OFLAG_MASK,
+ };
+
+ return header;
+}
+
+static struct disk_image *qcow2_probe(int fd)
+{
+ struct qcow *q;
+ struct qcow_header *h;
+ struct disk_image *disk_image;
+
+ q = calloc(1, sizeof(struct qcow));
+ if (!q)
+ goto error;
+
+ q->fd = fd;
+
+ h = q->header = qcow2_read_header(fd);
+ if (!h)
+ goto error;
+
+ if (qcow_read_l1_table(q) < 0)
+ goto error;
+
+ disk_image = disk_image__new(fd, h->size, &qcow1_disk_ops);
+ if (!disk_image)
+ goto error;
+ disk_image->priv = q;
+
+ return disk_image;
+error:
+ if (!q)
+ return NULL;
+
+ free(q->table.l1_table);
+ free(q->header);
+ free(q);
+
+ return NULL;
+}
+
+static bool qcow2_check_image(int fd)
+{
+ struct qcow2_header_disk f_header;
+
+ if (pread_in_full(fd, &f_header, sizeof(struct qcow2_header_disk), 0) < 0)
+ return false;
+
+ be32_to_cpus(&f_header.magic);
+ be32_to_cpus(&f_header.version);
+
+ if (f_header.magic != QCOW_MAGIC)
+ return false;
+
+ if (f_header.version != QCOW2_VERSION)
+ return false;
+
+ return true;
+}
+
static void *qcow1_read_header(int fd)
{
- struct qcow1_header *header;
+ struct qcow1_header_disk f_header;
+ struct qcow_header *header;
- header = malloc(sizeof(struct qcow1_header));
+ header = malloc(sizeof(struct qcow_header));
if (!header)
return NULL;
- if (pread_in_full(fd, header, sizeof(struct qcow1_header), 0) < 0)
+ if (pread_in_full(fd, &f_header, sizeof(struct qcow1_header_disk), 0) < 0)
return NULL;
- be32_to_cpus(&header->magic);
- be32_to_cpus(&header->version);
- be64_to_cpus(&header->backing_file_offset);
- be32_to_cpus(&header->backing_file_size);
- be32_to_cpus(&header->mtime);
- be64_to_cpus(&header->size);
- be32_to_cpus(&header->crypt_method);
- be64_to_cpus(&header->l1_table_offset);
+ be32_to_cpus(&f_header.magic);
+ be32_to_cpus(&f_header.version);
+ be64_to_cpus(&f_header.backing_file_offset);
+ be32_to_cpus(&f_header.backing_file_size);
+ be32_to_cpus(&f_header.mtime);
+ be64_to_cpus(&f_header.size);
+ be32_to_cpus(&f_header.crypt_method);
+ be64_to_cpus(&f_header.l1_table_offset);
+
+ *header = (struct qcow_header) {
+ .size = f_header.size,
+ .l1_table_offset = f_header.l1_table_offset,
+ .l1_size = f_header.size / ((1 << f_header.l2_bits) * (1 << f_header.cluster_bits)),
+ .cluster_bits = f_header.cluster_bits,
+ .l2_bits = f_header.l2_bits,
+ .oflag_mask = QCOW1_OFLAG_MASK,
+ };
return header;
}
@@ -204,7 +306,7 @@ static void *qcow1_read_header(int fd)
static struct disk_image *qcow1_probe(int fd)
{
struct qcow *q;
- struct qcow1_header *h;
+ struct qcow_header *h;
struct disk_image *disk_image;
q = calloc(1, sizeof(struct qcow));
@@ -237,29 +339,32 @@ error:
return NULL;
}
-static int qcow_check_image(int fd)
+static bool qcow1_check_image(int fd)
{
- struct qcow1_header header;
+ struct qcow1_header_disk f_header;
- if (pread_in_full(fd, &header, sizeof(struct qcow1_header), 0) < 0)
- return -1;
+ if (pread_in_full(fd, &f_header, sizeof(struct qcow1_header_disk), 0) < 0)
+ return false;
- be32_to_cpus(&header.magic);
- be32_to_cpus(&header.version);
+ be32_to_cpus(&f_header.magic);
+ be32_to_cpus(&f_header.version);
- if (header.magic != QCOW_MAGIC)
- return -1;
+ if (f_header.magic != QCOW_MAGIC)
+ return false;
- if (header.version != QCOW1_VERSION)
- return -1;
+ if (f_header.version != QCOW1_VERSION)
+ return false;
- return 0;
+ return true;
}
struct disk_image *qcow_probe(int fd)
{
- if (qcow_check_image(fd) < 0)
- return NULL;
+ if (qcow1_check_image(fd))
+ return qcow1_probe(fd);
+
+ if (qcow2_check_image(fd))
+ return qcow2_probe(fd);
- return qcow1_probe(fd);
+ return NULL;
}
This patch extends the QCOW1 format to also support QCOW2 images as specified by the following document: http://people.gnome.org/~markmc/qcow-image-format.html Cc: Asias He <asias.hejun@gmail.com> Cc: Cyrill Gorcunov <gorcunov@gmail.com> Cc: Prasad Joshi <prasadjoshi124@gmail.com> Cc: Sasha Levin <levinsasha928@gmail.com> Cc: Ingo Molnar <mingo@elte.hu> Signed-off-by: Pekka Enberg <penberg@kernel.org> --- tools/kvm/include/kvm/qcow.h | 42 ++++++++++- tools/kvm/qcow.c | 177 +++++++++++++++++++++++++++++++++--------- 2 files changed, 181 insertions(+), 38 deletions(-)