Patchwork [3/5] block: Virtual Bridges VERDE GOW disk image format, legacy GOW version 1 support implementation

login
register
mail settings
Submitter Leonardo E. Reiter
Date March 8, 2012, 10:15 p.m.
Message ID <CA+BHXkKGW-mfKVAw-F5YhsV7EHCObN4Z5TPUJbEaNOQGemTw2g@mail.gmail.com>
Download mbox | patch
Permalink /patch/145665/
State New
Headers show

Comments

Leonardo E. Reiter - March 8, 2012, 10:15 p.m.
commit 368ff891c1d06581aacaab1063fa53838a6b8a99
Author: Leonardo E. Reiter <lreiter@vbridges.com>
Date:   Thu Mar 8 16:01:48 2012 -0600

    Virtual Bridges GOW version 1 disk image format implementation
    Signed-off-by: Leonardo E. Reiter <lreiter@vbridges.com>

+    .bdrv_write     = gow1_write,
+
+    .bdrv_create    = gow1_create,
+    .bdrv_getlength = gow1_getlength,
+    .bdrv_truncate  = gow1_truncate,
+    .create_options = gow1_create_options,
+};
+
+static void bdrv_gow1_init(void)
+{
+    bdrv_register(&bdrv_gow1);
+}
+
+block_init(bdrv_gow1_init);
+
+#endif  /* ! _WIN32 */
+
Stefan Hajnoczi - March 9, 2012, 11:42 a.m.
On Thu, Mar 8, 2012 at 10:15 PM, Leonardo E. Reiter
<lreiter@vbridges.com> wrote:
> +#ifndef _WIN32
> +#include <sys/mman.h>

I recommend writing this in a platform-independent way using bdrv_*()
APIs to access the image file.  This has been a requirement for all
block drivers.  Legacy drivers have been updated too.  The chance of
merging a new image format that uses platform-specific I/O APIs is low
IMO.

> +BlockDriver bdrv_gow1 = {
> +    .format_name    = "gow1",
> +    .instance_size  = sizeof(gow1_state_t),
> +    .bdrv_probe     = gow1_probe,
> +    .bdrv_file_open = gow1_open,
> +    .bdrv_close     = gow1_close,
> +    .bdrv_read      = gow1_read,
> +    .bdrv_write     = gow1_write,

Please implement .bdrv_co_readv()/.bdrv_co_writev() instead.  They
work along the same lines - except they allow parallel requests.  If
you don't want to handle parallel requests you can use a CoMutex to
serialize these functions.

Stefan
Kevin Wolf - March 9, 2012, 11:59 a.m.
Am 09.03.2012 12:42, schrieb Stefan Hajnoczi:
> On Thu, Mar 8, 2012 at 10:15 PM, Leonardo E. Reiter
> <lreiter@vbridges.com> wrote:
>> +#ifndef _WIN32
>> +#include <sys/mman.h>
> 
> I recommend writing this in a platform-independent way using bdrv_*()
> APIs to access the image file.  This has been a requirement for all
> block drivers.  Legacy drivers have been updated too.  The chance of
> merging a new image format that uses platform-specific I/O APIs is low
> IMO.

Let's be clear here: It's not just low, it's zero.

Maybe the qcow2-cache should be generalised so that replacing an mmap in
other image formats becomes easier?

>> +BlockDriver bdrv_gow1 = {
>> +    .format_name    = "gow1",
>> +    .instance_size  = sizeof(gow1_state_t),
>> +    .bdrv_probe     = gow1_probe,
>> +    .bdrv_file_open = gow1_open,
>> +    .bdrv_close     = gow1_close,
>> +    .bdrv_read      = gow1_read,
>> +    .bdrv_write     = gow1_write,
> 
> Please implement .bdrv_co_readv()/.bdrv_co_writev() instead.  They
> work along the same lines - except they allow parallel requests.  If
> you don't want to handle parallel requests you can use a CoMutex to
> serialize these functions.

.bdrv_read/write allow parallel requests as well. The difference is that
bdrv_co_readv/writev is vectored and therefore preferable.

Kevin
Kevin Wolf - March 9, 2012, 12:04 p.m.
Am 08.03.2012 23:15, schrieb Leonardo E. Reiter:
> +BlockDriver bdrv_gow1 = {
> +    .format_name    = "gow1",
> +    .instance_size  = sizeof(gow1_state_t),
> +    .bdrv_probe     = gow1_probe,
> +    .bdrv_file_open = gow1_open,
> +    .bdrv_close     = gow1_close,
> +    .bdrv_read      = gow1_read,
> +    .bdrv_write     = gow1_write,
> +
> +    .bdrv_create    = gow1_create,
> +    .bdrv_getlength = gow1_getlength,
> +    .bdrv_truncate  = gow1_truncate,
> +    .create_options = gow1_create_options,
> +};

I wonder whether a name like verde_gow[123] wouldn't make more sense. I
think most people wouldn't be able to associate something with "gow",
and it doesn't look like a good keyword for searches either.

Also, is gow1 so different that implementing it in the same driver with
gow[23] would be impossible?

Kevin
Leonardo E. Reiter - March 9, 2012, 2:35 p.m.
On Fri, Mar 9, 2012 at 6:04 AM, Kevin Wolf <kwolf@redhat.com> wrote:

> I wonder whether a name like verde_gow[123] wouldn't make more sense. I
> think most people wouldn't be able to associate something with "gow",
> and it doesn't look like a good keyword for searches either.
>
> Also, is gow1 so different that implementing it in the same driver with
> gow[23] would be impossible?
>

Kevin et al,

thank you for the advice and suggestions.  I'll see what I can do about
updating this to meet spec.

- Leo Reiter

>
> Kevin
>

Patch

diff --git a/block/gow1.c b/block/gow1.c
new file mode 100644
index 0000000..efead4b
--- /dev/null
+++ b/block/gow1.c
@@ -0,0 +1,386 @@ 
+/*
+ * Virtual Bridges Grow-on-Write v.1 block driver for QEMU
+ *
+ * Copyright (c) 2006-2008 Leonardo E. Reiter
+ * Copyright (c) 1984-2012 Virtual Bridges, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
copy
+ * of this software and associated documentation files (the "Software"),
to deal
+ * in the Software without restriction, including without limitation the
rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN
+ * THE SOFTWARE.
+ */
+#ifndef _WIN32
+#include <sys/mman.h>
+#include "qemu-common.h"
+#include "block_int.h"
+#include "module.h"
+
+#define BLOCK_GOW1
+#include "gow_int.h"
+
+typedef struct {
+    int fd;
+    gow_header_t *header;
+    size_t len;
+    off64_t base;
+} gow1_state_t;
+
+/* probe for GOW1 image */
+static int gow1_probe(const uint8_t *buf, int buf_size, const char
*filename)
+{
+    int fd;
+    uint8_t buffer[sizeof(uint32_t) * 3];
+    gow_header_t *header = (gow_header_t *) buffer;
+    size_t readsize = sizeof(buffer);
+    ssize_t bytes;
+
+    if (buf == NULL) {
+        return 0;
+    }
+
+    /* make sure header is sound - XXX: just check meta data, not map */
+    fd = open(filename, O_RDONLY);
+    if (fd >= 0) {
+        bytes = read(fd, header, readsize);
+        close(fd);
+        if (bytes != readsize || header->magic != le32_to_cpu(GOW_MAGIC) ||
+                le32_to_cpu(header->size) > GOW_MAXBLOCKS ||
+                le32_to_cpu(header->allocated) >
le32_to_cpu(header->size)) {
+            return 0;
+        } else {
+            return 10000;
+        }
+    } else {
+        return 0;
+    }
+}
+
+/* open GOW1 image */
+static int gow1_open(BlockDriverState *bs, const char *filename, int flags)
+{
+    gow1_state_t *s = bs->opaque;
+    int prot = PROT_READ;
+    int oflags = O_BINARY;
+
+    /* compute open flags */
+    if ((flags & O_ACCMODE) == O_RDWR) {
+        oflags |= O_RDWR;
+    } else {
+        oflags |= O_RDONLY;
+        bs->read_only = 1;
+    }
+
+    /* open the disk image */
+    s->fd = open(filename, oflags, 0644);
+    if (s->fd < 0) {
+        return errno == EROFS ? -EACCES : -errno;
+    }
+
+    /* map in the header and bitmap */
+    s->len = GOW_HEADER_SIZE;
+    if (!bs->read_only) {
+        prot |= PROT_WRITE;
+    }
+    s->header = mmap(NULL, s->len, prot, MAP_SHARED, s->fd, 0);
+    if (s->header == MAP_FAILED) {
+        close(s->fd);
+        return -EIO;
+    }
+    s->base = sizeof(gow_header_t);
+
+    /* calculate total sectors */
+    bs->total_sectors = ((int64_t) le32_to_cpu(s->header->size)
+            * GOW_BLOCKSIZE) >> 9;
+
+    return 0;
+}
+
+/* read all or part of a block, zero filling if block is not allocated */
+static inline int read_block(gow1_state_t *s, uint32_t block, uint32_t
offset,
+        uint8_t *buf, size_t size)
+{
+    memset(buf, 0, size);
+    if (s->header->map[block] != GOW_FREE_BLOCK) {
+        if (pread64(s->fd, buf, size, IMG_OFFSET(block, offset)) < 0) {
+            return -errno;
+        }
+    }
+
+    return 0;
+}
+
+/* write all or part of a block, allocating if needed */
+static inline int write_block(gow1_state_t *s, uint32_t block, uint32_t
offset,
+        const uint8_t *buf, size_t size)
+{
+    uint32_t allocated;
+    uint32_t *wb;
+    int index, wlen = (size / sizeof(uint32_t));
+
+    if (s->header->map[block] == GOW_FREE_BLOCK) {
+
+        /* if it's zero-filled buffer, don't actually allocate */
+        for (index = 0, wb = (uint32_t *) buf; index < wlen && *wb == 0;
+                ++index, ++wb)
+            ;
+        if (index >= wlen) {
+            return 0;
+        }
+
+        /* allocate the block */
+        if (le32_to_cpu(s->header->allocated) <
le32_to_cpu(s->header->size)) {
+            s->header->map[block] = s->header->allocated;
+            allocated = le32_to_cpu(s->header->allocated);
+            s->header->allocated = cpu_to_le32(allocated + 1);
+        } else {
+
+            /* image full */
+            return -ENOSPC;
+        }
+    }
+
+    if (pwrite64(s->fd, buf, size, IMG_OFFSET(block, offset)) < 0) {
+        return -errno;
+    } else {
+        return 0;
+    }
+}
+
+/* synchronous positioned read from GOW1 image */
+static int gow1_read(BlockDriverState *bs, int64_t sector_num, uint8_t
*buf,
+        int nb_sectors)
+{
+    gow1_state_t *s = bs->opaque;
+    uint32_t block, offset, sectors;
+    int64_t sector = sector_num;
+    int left = nb_sectors;
+    uint8_t *ptr = buf;
+    ssize_t bytes;
+    int ret;
+
+    /* loop until all sectors are read */
+    while (left > 0) {
+        block = BLOCK_NUM(sector);
+        offset = BLOCK_OFFSET(sector);
+        sectors = BLOCK_SECTORS(offset);
+        if (sectors > left) {
+            sectors = left;
+        }
+        bytes = sectors << 9;
+
+        ret = read_block(s, block, offset, ptr, bytes);
+        if (ret < 0) {
+            return ret;
+        }
+
+        /* adjust pointers and counts */
+        ptr += bytes;
+        left -= sectors;
+        sector += sectors;
+    }
+
+    return 0;
+}
+
+/* synchronous positioned write to GOW1 image */
+static int gow1_write(BlockDriverState *bs, int64_t sector_num,
+        const uint8_t *buf, int nb_sectors)
+{
+    gow1_state_t *s = bs->opaque;
+    uint32_t block, offset, sectors;
+    int64_t sector = sector_num;
+    int left = nb_sectors;
+    const uint8_t *ptr = buf;
+    ssize_t bytes;
+    int ret;
+
+    /* loop until all sectors are written */
+    while (left > 0) {
+        block = BLOCK_NUM(sector);
+        offset = BLOCK_OFFSET(sector);
+        sectors = BLOCK_SECTORS(offset);
+        if (sectors > left) {
+            sectors = left;
+        }
+        bytes = sectors << 9;
+
+        ret = write_block(s, block, offset, ptr, bytes);
+        if (ret < 0) {
+            return ret;
+        }
+
+        /* adjust pointers and counts */
+        ptr += bytes;
+        left -= sectors;
+        sector += sectors;
+    }
+
+    return 0;
+}
+
+/* close and unmap the GOW1 image */
+static void gow1_close(BlockDriverState *bs)
+{
+    gow1_state_t *s = bs->opaque;
+
+    munmap((void *) s->header, s->len);
+    close(s->fd);
+}
+
+/* create GOW1 image */
+static int gow1_create(const char *filename, QEMUOptionParameter *options)
+{
+    int fd;
+    gow_header_t *header = calloc(1, GOW_HEADER_SIZE);
+    int64_t total_size = 0;
+    const char *backing_file = NULL;
+    int flags = 0;
+    int index;
+    ssize_t written;
+
+    /* Read out options */
+    while (options && options->name) {
+        if (!strcmp(options->name, BLOCK_OPT_SIZE)) {
+            total_size = options->value.n / 512;
+        } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) {
+            backing_file = options->value.s;
+        } else if (!strcmp(options->name, BLOCK_OPT_ENCRYPT)) {
+            flags |= options->value.n ? BLOCK_FLAG_ENCRYPT : 0;
+        }
+        options++;
+    }
+    int64_t blocks = total_size / (GOW_BLOCKSIZE >> 9);
+
+    if (header == NULL) {
+        return -EIO;
+    }
+
+    if (flags || backing_file || blocks < 1 || blocks > GOW_MAXBLOCKS) {
+        free(header);
+        return -ENOTSUP;
+    }
+
+    fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
+    if (fd < 0) {
+        free(header);
+        return -EIO;
+    }
+
+    /* create and write the header */
+    header->magic = cpu_to_le32(GOW_MAGIC);
+    header->size = cpu_to_le32(((uint32_t) blocks));
+    header->allocated = 0;
+    for (index = 0; index < GOW_MAXBLOCKS; index++) {
+        header->map[index] = GOW_FREE_BLOCK;
+    }
+    written = write(fd, header, GOW_HEADER_SIZE);
+    free(header);
+    close(fd);
+    if (written != GOW_HEADER_SIZE) {
+        return -EIO;
+    } else {
+        return 0;
+    }
+}
+
+/* flush GOW1 image */
+static int gow1_flush(BlockDriverState *bs)
+{
+    gow1_state_t *s = bs->opaque;
+
+    return qemu_fdatasync(s->fd);
+}
+
+/* truncate (resize) the GOW1 image */
+static int gow1_truncate(BlockDriverState *bs, int64_t offset)
+{
+    gow1_state_t *s = bs->opaque;
+    int64_t size;
+    uint32_t index;
+
+    /* calculate size in blocks, rounding up to nearest block */
+    size = (offset + (GOW_BLOCKSIZE - 1)) / GOW_BLOCKSIZE;
+    if (size < 1 || size > GOW_MAXBLOCKS) {
+        return -ENOTSUP;
+    } else {
+
+        /* adjust header */
+        s->header->size = cpu_to_le32(((uint32_t) size));
+        if (le32_to_cpu(s->header->allocated) >
le32_to_cpu(s->header->size)) {
+            s->header->allocated = s->header->size;
+        }
+
+        /* clear unallocated blocks, if any */
+        for (index = le32_to_cpu(s->header->allocated);
+                index < le32_to_cpu(s->header->size); ++index) {
+            s->header->map[index] = GOW_FREE_BLOCK;
+        }
+
+        /* XXX: actual image length doesn't change, even if making it
smaller
+         * since the exact location of the blocks in the image file may be
+         * out of order */
+
+        gow1_flush(bs);
+
+        return 0;
+    }
+}
+
+/* get length in bytes of GOW1 image */
+static int64_t gow1_getlength(BlockDriverState *bs)
+{
+    return (int64_t) bs->total_sectors << 9;
+}
+
+static QEMUOptionParameter gow1_create_options[] = {
+    {
+        .name = BLOCK_OPT_SIZE,
+        .type = OPT_SIZE,
+        .help = "Virtual disk size"
+    },
+    {
+        .name = BLOCK_OPT_BACKING_FILE,
+        .type = OPT_STRING,
+        .help = "File name of a base image"
+    },
+    { NULL }
+};
+
+
+BlockDriver bdrv_gow1 = {
+    .format_name    = "gow1",
+    .instance_size  = sizeof(gow1_state_t),
+    .bdrv_probe     = gow1_probe,
+    .bdrv_file_open = gow1_open,
+    .bdrv_close     = gow1_close,
+    .bdrv_read      = gow1_read,