qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH] Add new block driver for the VDI format


From: Stefan Weil
Subject: [Qemu-devel] [PATCH] Add new block driver for the VDI format
Date: Fri, 3 Jul 2009 21:29:46 +0200

This is a new block driver written from scratch
to support the VDI format in QEMU.

VDI is the native format used by Innotek / SUN VirtualBox.

Signed-off-by: Stefan Weil <address@hidden>
---
 Makefile    |    4 +-
 block/vdi.c |  598 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 600 insertions(+), 2 deletions(-)
 create mode 100644 block/vdi.c

diff --git a/Makefile b/Makefile
index b6bb41a..56815c7 100644
--- a/Makefile
+++ b/Makefile
@@ -67,8 +67,8 @@ recurse-all: $(SUBDIR_RULES)
 #######################################################################
 # block-obj-y is code used by both qemu system emulation and qemu-img
 
-block-obj-y = cutils.o cache-utils.o qemu-malloc.o qemu-option.o module.o
-block-obj-y += block/cow.o block/qcow.o aes.o block/vmdk.o block/cloop.o
+block-obj-y = cutils.o cache-utils.o qemu-malloc.o qemu-option.o aes.o module.o
+block-obj-y += block/cow.o block/qcow.o block/vdi.o block/vmdk.o block/cloop.o
 block-obj-y += block/dmg.o block/bochs.o block/vpc.o block/vvfat.o
 block-obj-y += block/qcow2.o block/qcow2-refcount.o block/qcow2-cluster.o
 block-obj-y += block/qcow2-snapshot.o
diff --git a/block/vdi.c b/block/vdi.c
new file mode 100644
index 0000000..78e223c
--- /dev/null
+++ b/block/vdi.c
@@ -0,0 +1,598 @@
+/*
+ * Block driver for the Virtual Disk Image (VDI) format
+ *
+ * Copyright (c) 2009 Stefan Weil
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) version 3 or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Reference:
+ * http://forums.virtualbox.org/viewtopic.php?t=8046
+ *
+ * This driver supports create / read / write operations on VDI images.
+ *
+ * Some features like snapshots are still missing (see TODO in code).
+ * Deallocation of zero-filled clusters is missing, too
+ * (might be added to common block layer).
+ * Asynchronous read / write support could be added, too.
+ */
+
+#include "qemu-common.h"
+#include "block_int.h"
+#include "module.h"
+
+#if defined(HAVE_UUID_H)
+#include <uuid/uuid.h>
+#endif
+
+/* Enable debug messages. */
+//~ #define CONFIG_VDI_DEBUG
+
+/* Support experimental write operations on VDI images. */
+#define CONFIG_VDI_WRITE
+
+/* Support snapshot images. */
+//~ #define CONFIG_VDI_SNAPSHOT
+
+/* Enable (currently) unsupported features. */
+//~ #define CONFIG_VDI_UNSUPPORTED
+
+/* Support non-standard cluster (block) size. */
+//~ #define CONFIG_VDI_CLUSTER_SIZE
+
+#define KiB     1024
+#define MiB     (KiB * KiB)
+
+#if defined(CONFIG_VDI_DEBUG)
+#define logout(fmt, ...) \
+                fprintf(stderr, "vdi\t%-24s" fmt, __func__, ##__VA_ARGS__)
+#else
+#define logout(fmt, ...) ((void)0)
+#endif
+
+#define SECTOR_SIZE 512
+
+/* Image signature. */
+#define VDI_SIGNATURE 0xbeda107f
+
+/* Image version. */
+#define VDI_VERSION_1_1 0x00010001
+
+/* Image type. */
+#define VDI_TYPE_DYNAMIC 1
+#define VDI_TYPE_FIXED  2
+
+/* Innotek / SUN images use these strings in header.text:
+ * "<<< innotek VirtualBox Disk Image >>>\n"
+ * "<<< Sun xVM VirtualBox Disk Image >>>\n"
+ * "<<< Sun VirtualBox Disk Image >>>\n"
+ * The value does not matter, so QEMU created images use a different text.
+ */
+#define VDI_TEXT "<<< QEMU VM Virtual Disk Image >>>\n"
+
+#if !defined(HAVE_UUID_H)
+typedef unsigned char uuid_t[16];
+#endif
+
+typedef struct {
+    char text[0x40];
+    uint32_t signature;
+    uint32_t version;
+    uint32_t header_size;
+    uint32_t image_type;
+    uint32_t image_flags;
+    char description[256];
+    uint32_t offset_blockmap;
+    uint32_t offset_data;
+    uint32_t cylinders;         /* disk geometry, unused here */
+    uint32_t heads;             /* disk geometry, unused here */
+    uint32_t sectors;           /* disk geometry, unused here */
+    uint32_t sector_size;
+    uint32_t unused1;
+    uint64_t disk_size;
+    uint32_t block_size;
+    uint32_t block_extra;       /* unused here */
+    uint32_t blocks_in_image;
+    uint32_t blocks_allocated;
+    uuid_t uuid_image;
+    uuid_t uuid_last_snap;
+    uuid_t uuid_link;
+    uuid_t uuid_parent;
+    uint64_t unused2[7];
+} VdiHeader;
+
+typedef struct BDRVVdiState {
+    BlockDriverState *hd;
+    uint32_t *blockmap;
+    /* Size of cluster (bytes). */
+    uint32_t cluster_size;
+    /* Size of cluster (sectors). */
+    uint32_t cluster_sectors;
+    VdiHeader header;
+} BDRVVdiState;
+
+static void vdi_header_to_cpu(VdiHeader *header)
+{
+    le32_to_cpus(&header->signature);
+    le32_to_cpus(&header->version);
+    le32_to_cpus(&header->header_size);
+    le32_to_cpus(&header->image_type);
+    le32_to_cpus(&header->image_flags);
+    le32_to_cpus(&header->offset_blockmap);
+    le32_to_cpus(&header->offset_data);
+    le32_to_cpus(&header->cylinders);
+    le32_to_cpus(&header->heads);
+    le32_to_cpus(&header->sectors);
+    le32_to_cpus(&header->sector_size);
+    le64_to_cpus(&header->disk_size);
+    le32_to_cpus(&header->block_size);
+    le32_to_cpus(&header->block_extra);
+    le32_to_cpus(&header->blocks_in_image);
+    le32_to_cpus(&header->blocks_allocated);
+}
+
+static void vdi_header_to_le(VdiHeader *header)
+{
+    cpu_to_le32s(&header->signature);
+    cpu_to_le32s(&header->version);
+    cpu_to_le32s(&header->header_size);
+    cpu_to_le32s(&header->image_type);
+    cpu_to_le32s(&header->image_flags);
+    cpu_to_le32s(&header->offset_blockmap);
+    cpu_to_le32s(&header->offset_data);
+    cpu_to_le32s(&header->cylinders);
+    cpu_to_le32s(&header->heads);
+    cpu_to_le32s(&header->sectors);
+    cpu_to_le32s(&header->sector_size);
+    cpu_to_le64s(&header->disk_size);
+    cpu_to_le32s(&header->block_size);
+    cpu_to_le32s(&header->block_extra);
+    cpu_to_le32s(&header->blocks_in_image);
+    cpu_to_le32s(&header->blocks_allocated);
+}
+
+static void vdi_header_print(VdiHeader *header)
+{
+    logout("text        %s", header->text);
+    logout("signature   0x%04x\n", header->signature);
+    logout("header size 0x%04x\n", header->header_size);
+    logout("image type  0x%04x\n", header->image_type);
+    logout("image flags 0x%04x\n", header->image_flags);
+    logout("description %s\n", header->description);
+    logout("offset bmap 0x%04x\n", header->offset_blockmap);
+    logout("offset data 0x%04x\n", header->offset_data);
+    logout("cylinders   0x%04x\n", header->cylinders);
+    logout("heads       0x%04x\n", header->heads);
+    logout("sectors     0x%04x\n", header->sectors);
+    logout("sector size 0x%04x\n", header->sector_size);
+    logout("image size  0x%" PRIx64 " B (%" PRIu64 " MiB)\n",
+           header->disk_size, header->disk_size / MiB);
+    logout("block size  0x%04x\n", header->block_size);
+    logout("block extra 0x%04x\n", header->block_extra);
+    logout("blocks tot. 0x%04x\n", header->blocks_in_image);
+    logout("blocks all. 0x%04x\n", header->blocks_allocated);
+}
+
+static int vdi_check(BlockDriverState *bs)
+{
+    /* TODO: missing code. */
+    logout("\n");
+    return -ENOTSUP;
+}
+
+static int vdi_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
+{
+    /* TODO: unchecked code. */
+    BDRVVdiState *s = (BDRVVdiState *)bs->opaque;
+    logout("\n");
+    bdi->cluster_size = s->cluster_size;
+    bdi->vm_state_offset = -1;
+    return -ENOTSUP;
+}
+
+static int vdi_make_empty(BlockDriverState *bs)
+{
+    /* TODO: missing code. */
+    logout("\n");
+    return -ENOTSUP;
+}
+
+static int vdi_probe(const uint8_t *buf, int buf_size, const char *filename)
+{
+    const VdiHeader *header = (const VdiHeader *)buf;
+    int result = 0;
+
+    if (buf_size < sizeof(*header)) {
+        /* Header too small, no VDI. */
+    } else if (le32_to_cpu(header->signature) == VDI_SIGNATURE) {
+        result = 100;
+    }
+
+    if (result == 0) {
+        logout("no vdi image\n");
+    } else {
+        logout("%s", header->text);
+    }
+
+    return result;
+}
+
+#if defined(CONFIG_VDI_SNAPSHOT)
+static int vdi_snapshot_create(const char *filename, const char *backing_file)
+{
+    /* TODO: missing code. */
+    logout("\n");
+    return -1;
+}
+#endif
+
+static int vdi_open(BlockDriverState *bs, const char *filename, int flags)
+{
+    BDRVVdiState *s = bs->opaque;
+    VdiHeader header;
+    size_t blockmap_size;
+    int ret;
+
+    logout("\n");
+
+    /* Performance is terrible right now with cache=writethrough due mainly
+     * to reference count updates.  If the user does not explicitly specify
+     * a caching type, force to writeback caching.
+     * TODO: This was copied from qcow2.c, maybe it is true for vdi, too.
+     */
+    if ((flags & BDRV_O_CACHE_DEF)) {
+        flags |= BDRV_O_CACHE_WB;
+        flags &= ~BDRV_O_CACHE_DEF;
+    }
+
+    ret = bdrv_file_open(&s->hd, filename, flags);
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (bdrv_pread(s->hd, 0, &header, sizeof(header)) != sizeof(header)) {
+        goto fail;
+    }
+
+    vdi_header_to_cpu(&header);
+    vdi_header_print(&header);
+
+    if (header.version != VDI_VERSION_1_1) {
+        logout("unsupported version %u.%u\n",
+               header.version >> 16, header.version & 0xffff);
+        goto fail;
+    } else if (header.offset_blockmap % SECTOR_SIZE != 0) {
+        /* We only support blockmaps which start on a sector boundary. */
+        logout("unsupported blockmap offset 0x%x B\n", header.offset_blockmap);
+        goto fail;
+    } else if (header.offset_data % SECTOR_SIZE != 0) {
+        /* We only support data blocks which start on a sector boundary. */
+        logout("unsupported data offset 0x%x B\n", header.offset_data);
+        goto fail;
+    } else if (header.sector_size != SECTOR_SIZE) {
+        logout("unsupported sector size %u B\n", header.sector_size);
+        goto fail;
+    } else if (header.block_size != 1 * MiB) {
+        logout("unsupported block size %u B\n", header.block_size);
+        goto fail;
+    } else if (header.disk_size !=
+               (uint64_t)header.blocks_in_image * header.block_size) {
+        logout("unexpected block number %u B\n", header.blocks_in_image);
+        goto fail;
+    }
+
+    bs->total_sectors = header.disk_size / SECTOR_SIZE;
+
+    blockmap_size = header.blocks_in_image * sizeof(uint32_t);
+    s->blockmap = qemu_malloc(blockmap_size);
+    if (bdrv_pread(s->hd, header.offset_blockmap, s->blockmap, blockmap_size) 
!= blockmap_size) {
+        goto fail_free_blockmap;
+    }
+
+    /* Blocks (VDI documentation) correspond to clusters (QEMU). */
+    s->cluster_size = header.block_size;
+    s->cluster_sectors = (header.block_size / SECTOR_SIZE);
+    s->header = header;
+    logout("cluster size %u KiB\n", s->cluster_size / KiB);
+
+    return 0;
+
+ fail_free_blockmap:
+    qemu_free(s->blockmap);
+
+ fail:
+    bdrv_delete(s->hd);
+    return -1;
+}
+
+static int vdi_is_allocated(BlockDriverState *bs, int64_t sector_num,
+                             int nb_sectors, int *pnum)
+{
+    /* TODO: Check for too large sector_num (in bdrv_is_allocated or here). */
+    BDRVVdiState *s = (BDRVVdiState *)bs->opaque;
+    size_t blockmap_index = sector_num / s->cluster_sectors;
+    size_t sector_in_cluster = sector_num % s->cluster_sectors;
+    int n_sectors = s->cluster_sectors - sector_in_cluster;
+    uint32_t cluster_index = s->blockmap[blockmap_index];
+    logout("%p, %" PRId64 ", %d, %p\n", bs, sector_num, nb_sectors, pnum);
+    if (n_sectors > nb_sectors) {
+        n_sectors = nb_sectors;
+    }
+    *pnum = n_sectors;
+    return cluster_index != UINT32_MAX;
+}
+
+static int vdi_read(BlockDriverState *bs, int64_t sector_num,
+                    uint8_t *buf, int nb_sectors)
+{
+    BDRVVdiState *s = (BDRVVdiState *)bs->opaque;
+    logout("%p, %" PRId64 ", %p, %d\n", bs, sector_num, buf, nb_sectors);
+    if (sector_num < 0) {
+        logout("unsupported sector %" PRId64 "\n", sector_num);
+        return -1;
+    }
+    while (nb_sectors > 0 && sector_num < bs->total_sectors) {
+        size_t n_bytes;
+        uint32_t blockmap_entry;
+        size_t block_index = sector_num / s->cluster_sectors;
+        size_t sector_in_cluster = sector_num % s->cluster_sectors;
+        size_t n_sectors = s->cluster_sectors - sector_in_cluster;
+        if (n_sectors > nb_sectors) {
+            n_sectors = nb_sectors;
+        }
+        n_bytes = n_sectors * SECTOR_SIZE;
+        blockmap_entry = s->blockmap[block_index];
+        if (blockmap_entry == UINT32_MAX) {
+            /* Cluster not allocated, return zeros. */
+            memset(buf, 0, n_bytes);
+        } else {
+            uint64_t offset = (uint64_t)s->header.offset_data +
+                (uint64_t)blockmap_entry * s->cluster_size +
+                sector_in_cluster * SECTOR_SIZE;
+            if (bdrv_pread(s->hd, offset, buf, n_bytes) != n_bytes) {
+                logout("read error\n");
+                return -1;
+            }
+        }
+        buf += n_bytes;
+        sector_num += n_sectors;
+        nb_sectors -= n_sectors;
+    }
+    return 0;
+}
+
+#if defined(CONFIG_VDI_WRITE)
+static int vdi_write(BlockDriverState *bs, int64_t sector_num,
+                     const uint8_t *buf, int nb_sectors)
+{
+    BDRVVdiState *s = (BDRVVdiState *)bs->opaque;
+    logout("%p, %" PRId64 ", %p, %d\n", bs, sector_num, buf, nb_sectors);
+    if (sector_num < 0) {
+        logout("unsupported sector %" PRId64 "\n", sector_num);
+        return -1;
+    }
+    while (nb_sectors > 0 && sector_num < bs->total_sectors) {
+        size_t n_bytes;
+        uint32_t blockmap_entry;
+        uint64_t offset;
+        size_t block_index = sector_num / s->cluster_sectors;
+        size_t sector_in_cluster = sector_num % s->cluster_sectors;
+        size_t n_sectors = s->cluster_sectors - sector_in_cluster;
+        if (n_sectors > nb_sectors) {
+            n_sectors = nb_sectors;
+        }
+        n_bytes = n_sectors * SECTOR_SIZE;
+        blockmap_entry = s->blockmap[block_index];
+        if (blockmap_entry == UINT32_MAX) {
+            /* Allocate new cluster and write to it. */
+            uint8_t *block;
+            blockmap_entry =
+            s->blockmap[block_index] = s->header.blocks_allocated;
+            s->header.blocks_allocated++;
+            offset = (uint64_t)s->header.offset_data +
+                (uint64_t)blockmap_entry * s->cluster_size;
+            block = qemu_mallocz(s->cluster_size);
+            memcpy(block + sector_in_cluster * SECTOR_SIZE, buf, n_bytes);
+            n_bytes = s->cluster_size;
+            if (bdrv_pwrite(s->hd, offset, block, n_bytes) != n_bytes) {
+                qemu_free(block);
+                logout("write error\n");
+                return -1;
+            }
+            qemu_free(block);
+            /* Write modified sector from block map. */
+            blockmap_entry &= ~(SECTOR_SIZE / sizeof(uint32_t) - 1);
+            offset = (s->header.offset_blockmap +
+                      blockmap_entry * sizeof(uint32_t));
+            if (bdrv_pwrite(s->hd, offset,
+                            &s->blockmap[blockmap_entry],
+                            SECTOR_SIZE) != SECTOR_SIZE) {
+                logout("write error\n");
+                return -1;
+            }
+        } else {
+            /* Write to existing block. */
+            offset = (uint64_t)s->header.offset_data +
+                (uint64_t)blockmap_entry * s->cluster_size +
+                sector_in_cluster * SECTOR_SIZE;
+            if (bdrv_pwrite(s->hd, offset, buf, n_bytes) != n_bytes) {
+                logout("write error\n");
+                return -1;
+            }
+        }
+        buf += n_bytes;
+        sector_num += n_sectors;
+        nb_sectors -= n_sectors;
+    }
+    return 0;
+}
+#endif
+
+static int vdi_create(const char *filename, QEMUOptionParameter *options)
+{
+    int fd;
+    uint64_t bytes = 0;
+    uint32_t clusters;
+    //~ int flags = 0;
+    size_t cluster_size = 1 * MiB;
+    VdiHeader header;
+    size_t i;
+    size_t blockmap_size;
+    uint32_t *blockmap;
+
+    logout("\n");
+
+    /* Read out options. */
+    while (options && options->name) {
+        if (!strcmp(options->name, BLOCK_OPT_SIZE)) {
+            bytes = options->value.n;
+#if defined(CONFIG_VDI_CLUSTER_SIZE)
+        } else if (!strcmp(options->name, BLOCK_OPT_CLUSTER_SIZE)) {
+            if (options->value.n) {
+                /* TODO: Additional checks (SECTOR_SIZE * 2^n, ...). */
+                cluster_size = options->value.n;
+            }
+#endif
+        }
+        options++;
+    }
+
+    fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE,
+              0644);
+    if (fd < 0) {
+        return -1;
+    }
+
+    clusters = bytes / cluster_size;
+    blockmap_size = clusters * sizeof(uint32_t);
+    blockmap_size = ((blockmap_size + SECTOR_SIZE - 1) & ~(SECTOR_SIZE -1));
+
+    memset(&header, 0, sizeof(header));
+    strcpy(header.text, VDI_TEXT);
+    header.signature = VDI_SIGNATURE;
+    header.version = VDI_VERSION_1_1;
+    header.header_size = 0x180;
+    header.image_type = VDI_TYPE_DYNAMIC;
+    header.offset_blockmap = 0x200;
+    header.offset_data = 0x200 + blockmap_size;
+    header.sector_size = SECTOR_SIZE;
+    header.disk_size = bytes;
+    header.block_size = cluster_size;
+    header.blocks_in_image = clusters;
+#if defined(HAVE_UUID_H)
+    uuid_generate(header.uuid_image);
+    uuid_generate(header.uuid_last_snap);
+#if 0
+    uuid_generate(header.uuid_link);
+    uuid_generate(header.uuid_parent);
+#endif
+#endif
+    vdi_header_print(&header);
+    vdi_header_to_le(&header);
+    write(fd, &header, sizeof(header));
+
+    blockmap = (uint32_t *)qemu_mallocz(blockmap_size);
+    for (i = 0; i < clusters; i++) {
+        blockmap[i] = UINT32_MAX;
+    }
+    write(fd, blockmap, blockmap_size);
+    qemu_free(blockmap);
+
+    close(fd);
+
+    return 0;
+}
+
+static void vdi_close(BlockDriverState *bs)
+{
+    BDRVVdiState *s = bs->opaque;
+    logout("\n");
+    bdrv_delete(s->hd);
+}
+
+static void vdi_flush(BlockDriverState *bs)
+{
+    BDRVVdiState *s = bs->opaque;
+    logout("\n");
+    bdrv_flush(s->hd);
+}
+
+
+static QEMUOptionParameter vdi_create_options[] = {
+    {
+        .name = BLOCK_OPT_SIZE,
+        .type = OPT_SIZE,
+        .help = "Virtual disk size"
+    },
+#if defined(CONFIG_VDI_CLUSTER_SIZE)
+    {
+        .name = BLOCK_OPT_CLUSTER_SIZE,
+        .type = OPT_SIZE,
+        .help = "vdi cluster size"
+    },
+#endif
+    { NULL }
+};
+
+static BlockDriver bdrv_vdi = {
+    .format_name        = "vdi",
+    .instance_size      = sizeof(BDRVVdiState),
+    .bdrv_probe         = vdi_probe,
+    .bdrv_open          = vdi_open,
+    .bdrv_close         = vdi_close,
+    .bdrv_create        = vdi_create,
+    .bdrv_flush         = vdi_flush,
+#if defined(CONFIG_VDI_UNSUPPORTED)
+    .bdrv_getlength     = vdi_getlength,
+#endif
+    .bdrv_is_allocated  = vdi_is_allocated,
+#if defined(CONFIG_VDI_UNSUPPORTED)
+    .bdrv_set_key       = vdi_set_key,
+#endif
+    .bdrv_make_empty    = vdi_make_empty,
+
+#if defined(CONFIG_VDI_UNSUPPORTED)
+    .bdrv_aio_readv     = vdi_aio_readv,
+    .bdrv_aio_writev    = vdi_aio_writev,
+    .bdrv_write_compressed = vdi_write_compressed,
+#endif
+
+    .bdrv_read          = vdi_read,
+#if defined(CONFIG_VDI_WRITE)
+    .bdrv_write         = vdi_write,
+#endif
+
+#if defined(CONFIG_VDI_SNAPSHOT)
+    .bdrv_snapshot_create   = vdi_snapshot_create,
+    .bdrv_snapshot_goto     = vdi_snapshot_goto,
+    .bdrv_snapshot_delete   = vdi_snapshot_delete,
+    .bdrv_snapshot_list     = vdi_snapshot_list,
+#endif
+    .bdrv_get_info      = vdi_get_info,
+
+#if defined(CONFIG_VDI_UNSUPPORTED)
+    .bdrv_put_buffer    = vdi_put_buffer,
+    .bdrv_get_buffer    = vdi_get_buffer,
+#endif
+
+    .create_options     = vdi_create_options,
+    .bdrv_check         = vdi_check,
+};
+
+static void bdrv_vdi_init(void)
+{
+    logout("\n");
+    bdrv_register(&bdrv_vdi);
+}
+
+block_init(bdrv_vdi_init);
-- 
1.5.6.5





reply via email to

[Prev in Thread] Current Thread [Next in Thread]