diff mbox

kvm tools: Add read-only support for QCOW2 images

Message ID 1303247249-11037-1-git-send-email-penberg@kernel.org (mailing list archive)
State New, archived
Headers show

Commit Message

Pekka Enberg April 19, 2011, 9:07 p.m. UTC
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(-)

Comments

Pekka Enberg April 20, 2011, 3:57 p.m. UTC | #1
On Tue, 19 Apr 2011, Prasad Joshi wrote:

> On Tue, Apr 19, 2011 at 10:07 PM, Pekka Enberg <penberg@kernel.org> wrote:
>       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(-)
>
>       diff --git a/tools/kvm/include/kvm/qcow.h b/tools/kvm/include/kvm/qcow.h
>       index 4be2597..afd776d 100644
>       --- a/tools/kvm/include/kvm/qcow.h
>       +++ b/tools/kvm/include/kvm/qcow.h
>       @@ -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;
>       +};
> 
> IMHO, as we start adding other features of QCOW, the two structures qcow2_header_disk and qcow_header might eventually become the same. 

No, the point of 'struct qcow2_header_disk' is to map to the on-disk 
representation. 'struct qcow_header' is the in-memory version of the data.

>       +       disk_image = disk_image__new(fd, h->size, &qcow1_disk_ops);
> 
> 
> qcow1_disk_ops can be changed to qcow_disk_ops.

Sure, there's more qcow1 prefixes that need fixing now as well.
diff mbox

Patch

diff --git a/tools/kvm/include/kvm/qcow.h b/tools/kvm/include/kvm/qcow.h
index 4be2597..afd776d 100644
--- a/tools/kvm/include/kvm/qcow.h
+++ b/tools/kvm/include/kvm/qcow.h
@@ -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 */
diff --git a/tools/kvm/qcow.c b/tools/kvm/qcow.c
index 11f6454..6d847d0 100644
--- a/tools/kvm/qcow.c
+++ b/tools/kvm/qcow.c
@@ -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;
 }