From patchwork Thu Apr 14 17:21:47 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Pekka Enberg X-Patchwork-Id: 707431 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 p3EHM3En018972 for ; Thu, 14 Apr 2011 17:22:03 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753397Ab1DNRWA (ORCPT ); Thu, 14 Apr 2011 13:22:00 -0400 Received: from filtteri1.pp.htv.fi ([213.243.153.184]:44127 "EHLO filtteri1.pp.htv.fi" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751192Ab1DNRV7 (ORCPT ); Thu, 14 Apr 2011 13:21:59 -0400 Received: from localhost (localhost [127.0.0.1]) by filtteri1.pp.htv.fi (Postfix) with ESMTP id 4FAB98BC3C; Thu, 14 Apr 2011 20:21:58 +0300 (EEST) X-Virus-Scanned: Debian amavisd-new at pp.htv.fi Received: from smtp5.welho.com ([213.243.153.39]) by localhost (filtteri1.pp.htv.fi [213.243.153.184]) (amavisd-new, port 10024) with ESMTP id 69Hvt3q-wFsw; Thu, 14 Apr 2011 20:21:58 +0300 (EEST) Received: from localhost.localdomain (cs181148025.pp.htv.fi [82.181.148.25]) by smtp5.welho.com (Postfix) with ESMTP id 9AD695BC002; Thu, 14 Apr 2011 20:21:57 +0300 (EEST) From: Pekka Enberg To: kvm@vger.kernel.org Cc: Prasad Joshi , Asias He , Cyrill Gorcunov , Ingo Molnar , Kevin Wolf , Sasha Levin , Stefan Hajnoczi , Pekka Enberg Subject: [PATCH] kvm tools: Add QCOW version 1 read-only support Date: Thu, 14 Apr 2011 20:21:47 +0300 Message-Id: <1302801707-12630-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]); Thu, 14 Apr 2011 17:22:03 +0000 (UTC) From: Prasad Joshi The patch only implements the basic read-only support for QCOW version 1 images. Many of the QCOW features are not implemented: - write - image creation - snapshot - copy-on-write - encryption The code is based on the following QCOW 1 image format specification: http://people.gnome.org/~markmc/qcow-image-format-version-1.html Cc: Asias He Cc: Cyrill Gorcunov Cc: Ingo Molnar Cc: Kevin Wolf Cc: Sasha Levin Cc: Stefan Hajnoczi Signed-off-by: Prasad Joshi Signed-off-by: Pekka Enberg --- FYI, I dropped commit 910c791 ("kvm tools: Add QCOW version 1 read/write support") from the GIT repository and re-implemented read-only QCOW1 support from scratch. tools/kvm/Makefile | 1 + tools/kvm/disk-image.c | 5 + tools/kvm/include/kvm/qcow.h | 41 +++++++ tools/kvm/include/linux/byteorder.h | 7 + tools/kvm/include/linux/types.h | 19 +++ tools/kvm/qcow.c | 227 +++++++++++++++++++++++++++++++++++ 6 files changed, 300 insertions(+), 0 deletions(-) create mode 100644 tools/kvm/include/kvm/qcow.h create mode 100644 tools/kvm/include/linux/byteorder.h create mode 100644 tools/kvm/qcow.c diff --git a/tools/kvm/Makefile b/tools/kvm/Makefile index 6895113..28dd369 100644 --- a/tools/kvm/Makefile +++ b/tools/kvm/Makefile @@ -34,6 +34,7 @@ OBJS += util/strbuf.o OBJS += kvm-help.o OBJS += kvm-cmd.o OBJS += kvm-run.o +OBJS += qcow.o DEPS := $(patsubst %.o,%.d,$(OBJS)) diff --git a/tools/kvm/disk-image.c b/tools/kvm/disk-image.c index 5d0f342..fff71b4 100644 --- a/tools/kvm/disk-image.c +++ b/tools/kvm/disk-image.c @@ -1,6 +1,7 @@ #include "kvm/disk-image.h" #include "kvm/read-write.h" +#include "kvm/qcow.h" #include "kvm/util.h" #include @@ -131,6 +132,10 @@ struct disk_image *disk_image__open(const char *filename, bool readonly) if (fd < 0) return NULL; + self = qcow_probe(fd); + if (self) + return self; + self = raw_image__probe(fd, readonly); if (self) return self; diff --git a/tools/kvm/include/kvm/qcow.h b/tools/kvm/include/kvm/qcow.h new file mode 100644 index 0000000..4be2597 --- /dev/null +++ b/tools/kvm/include/kvm/qcow.h @@ -0,0 +1,41 @@ +#ifndef KVM__QCOW_H +#define KVM__QCOW_H + +#include + +#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) +#define QCOW1_VERSION 1 + +#define QCOW_OFLAG_COMPRESSED (1LL << 63) + +struct qcow_table { + u32 table_size; + u64 *l1_table; +}; + +struct qcow { + void *header; + struct qcow_table table; + int fd; +}; + +struct qcow1_header { + u32 magic; + u32 version; + + u64 backing_file_offset; + u32 backing_file_size; + u32 mtime; + + u64 size; /* in bytes */ + + u8 cluster_bits; + u8 l2_bits; + u32 crypt_method; + + u64 l1_table_offset; +}; + +struct disk_image *qcow_probe(int fd); + +#endif /* KVM__QCOW_H */ diff --git a/tools/kvm/include/linux/byteorder.h b/tools/kvm/include/linux/byteorder.h new file mode 100644 index 0000000..c490de8 --- /dev/null +++ b/tools/kvm/include/linux/byteorder.h @@ -0,0 +1,7 @@ +#ifndef __BYTE_ORDER_H__ +#define __BYTE_ORDER_H__ + +#include +#include + +#endif diff --git a/tools/kvm/include/linux/types.h b/tools/kvm/include/linux/types.h index 8b608e7..efd8519 100644 --- a/tools/kvm/include/linux/types.h +++ b/tools/kvm/include/linux/types.h @@ -27,4 +27,23 @@ typedef __s16 s16; typedef __u8 u8; typedef __s8 s8; +#ifdef __CHECKER__ +#define __bitwise__ __attribute__((bitwise)) +#else +#define __bitwise__ +#endif +#ifdef __CHECK_ENDIAN__ +#define __bitwise __bitwise__ +#else +#define __bitwise +#endif + + +typedef __u16 __bitwise __le16; +typedef __u16 __bitwise __be16; +typedef __u32 __bitwise __le32; +typedef __u32 __bitwise __be32; +typedef __u64 __bitwise __le64; +typedef __u64 __bitwise __be64; + #endif /* LINUX_TYPES_H */ diff --git a/tools/kvm/qcow.c b/tools/kvm/qcow.c new file mode 100644 index 0000000..c4e3e48 --- /dev/null +++ b/tools/kvm/qcow.c @@ -0,0 +1,227 @@ +#include "kvm/qcow.h" + +#include "kvm/disk-image.h" +#include "kvm/read-write.h" +#include "kvm/util.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static inline uint64_t sect_to_l1_offset(struct qcow *q, uint64_t offset) +{ + struct qcow1_header *header = q->header; + + return offset >> (header->l2_bits + header->cluster_bits); +} + +static inline uint64_t sect_to_l2_offset(struct qcow *q, uint64_t offset) +{ + struct qcow1_header *header = q->header; + + return (offset >> (header->cluster_bits)) & ((1 << header->l2_bits)-1); +} + +static inline uint64_t sect_to_cluster_offset(struct qcow *q, uint64_t offset) +{ + struct qcow1_header *header = q->header; + + return offset & ((1 << header->cluster_bits)-1); +} + +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; + uint64_t l2_table_offset; + uint64_t l2_table_size; + uint64_t clust_offset; + uint64_t clust_start; + uint64_t *l2_table; + uint64_t l1_idx; + uint64_t l2_idx; + uint64_t offset; + + offset = sector << SECTOR_SHIFT; + if (offset >= header->size) + goto out_error; + + l1_idx = sect_to_l1_offset(self->priv, offset); + + if (l1_idx >= q->table.table_size) + goto out_error; + + l2_table_offset = be64_to_cpu(q->table.l1_table[l1_idx]); + if (!l2_table_offset) + goto zero_sector; + + l2_table_size = 1 << header->l2_bits; + + l2_table = calloc(l2_table_size, sizeof(uint64_t)); + if (!l2_table) + goto out_error; + + if (pread_in_full(q->fd, l2_table, sizeof(uint64_t) * l2_table_size, l2_table_offset) < 0) + goto out_error_free_l2; + + l2_idx = sect_to_l2_offset(self->priv, offset); + + if (l2_idx >= l2_table_size) + goto out_error_free_l2; + + clust_start = be64_to_cpu(l2_table[l2_idx]); + + if (!clust_start) + goto zero_sector; + + clust_offset = sect_to_cluster_offset(self->priv, offset); + + if (pread_in_full(q->fd, dst, dst_len, clust_start + clust_offset) < 0) + goto out_error_free_l2; + + free(l2_table); + + return 0; + +zero_sector: + memset(dst, 0, dst_len); + + return 0; + +out_error_free_l2: + free(l2_table); +out_error: + return -1; +} + +static int qcow1_write_sector(struct disk_image *self, uint64_t sector, void *src, uint32_t src_len) +{ + return -1; +} + +static void qcow1_disk_close(struct disk_image *self) +{ + struct qcow *q; + + if (!self) + return; + + q = self->priv; + + free(q->header); + free(q); +} + +struct disk_image_operations qcow1_disk_ops = { + .read_sector = qcow1_read_sector, + .write_sector = qcow1_write_sector, + .close = qcow1_disk_close +}; + +static int qcow_read_l1_table(struct qcow *q) +{ + struct qcow1_header *header = q->header; + + q->table.table_size = header->size / ((1 << header->l2_bits) * (1 << header->cluster_bits)); + + q->table.l1_table = calloc(q->table.table_size, sizeof(uint64_t)); + if (!q->table.l1_table) + return -1; + + if (pread_in_full(q->fd, q->table.l1_table, sizeof(uint64_t) * q->table.table_size, header->l1_table_offset) < 0) + return -1; + + return 0; +} + +static void *qcow1_read_header(int fd) +{ + struct qcow1_header *header; + + header = malloc(sizeof(struct qcow1_header)); + if (!header) + return NULL; + + if (pread_in_full(fd, header, sizeof(struct qcow1_header), 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); + + return header; +} + +static struct disk_image *qcow1_probe(int fd) +{ + struct qcow *q; + struct qcow1_header *h; + struct disk_image *disk_image; + + q = calloc(1, sizeof(struct qcow)); + if (!q) + goto error; + + q->fd = fd; + + h = q->header = qcow1_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->header); + free(q); + + return NULL; +} + +static int qcow_check_image(int fd) +{ + struct qcow1_header header; + + if (pread_in_full(fd, &header, sizeof(struct qcow1_header), 0) < 0) + return -1; + + be32_to_cpus(&header.magic); + be32_to_cpus(&header.version); + + if (header.magic != QCOW_MAGIC) + return -1; + + if (header.version != QCOW1_VERSION) + return -1; + + return 0; +} + +struct disk_image *qcow_probe(int fd) +{ + if (qcow_check_image(fd) < 0) + return NULL; + + return qcow1_probe(fd); +}