From patchwork Tue Apr 19 21:07:29 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pekka Enberg X-Patchwork-Id: 720171 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id p3JL816P013199 for ; Tue, 19 Apr 2011 21:08:02 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755655Ab1DSVHu (ORCPT ); Tue, 19 Apr 2011 17:07:50 -0400 Received: from filtteri2.pp.htv.fi ([213.243.153.185]:43967 "EHLO filtteri2.pp.htv.fi" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755595Ab1DSVHd (ORCPT ); Tue, 19 Apr 2011 17:07:33 -0400 Received: from localhost (localhost [127.0.0.1]) by filtteri2.pp.htv.fi (Postfix) with ESMTP id A32D519B51A; Wed, 20 Apr 2011 00:07:31 +0300 (EEST) X-Virus-Scanned: Debian amavisd-new at pp.htv.fi Received: from smtp4.welho.com ([213.243.153.38]) by localhost (filtteri2.pp.htv.fi [213.243.153.185]) (amavisd-new, port 10024) with ESMTP id rOgXEidzr9n9; Wed, 20 Apr 2011 00:07:31 +0300 (EEST) Received: from localhost.localdomain (cs181148025.pp.htv.fi [82.181.148.25]) by smtp4.welho.com (Postfix) with ESMTP id DC9965BC005; Wed, 20 Apr 2011 00:07:30 +0300 (EEST) From: Pekka Enberg To: kvm@vger.kernel.org Cc: Pekka Enberg , Asias He , Cyrill Gorcunov , Prasad Joshi , Sasha Levin , Ingo Molnar Subject: [PATCH] kvm tools: Add read-only support for QCOW2 images Date: Wed, 20 Apr 2011 00:07:29 +0300 Message-Id: <1303247249-11037-1-git-send-email-penberg@kernel.org> X-Mailer: git-send-email 1.7.0.4 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Tue, 19 Apr 2011 21:08:03 +0000 (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 Cc: Cyrill Gorcunov Cc: Prasad Joshi Cc: Sasha Levin Cc: Ingo Molnar Signed-off-by: Pekka Enberg --- 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 #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; }