commit 368ff891c1d06581aacaab1063fa53838a6b8a99 Author: Leonardo E. Reiter <lrei...@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 <lrei...@vbridges.com> 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, + .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 */ +