From patchwork Thu Mar 1 02:56:50 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Robert Wang X-Patchwork-Id: 143890 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id D626BB6EF1 for ; Thu, 1 Mar 2012 13:57:44 +1100 (EST) Received: from localhost ([::1]:36280 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S2wD8-0003l4-Qg for incoming@patchwork.ozlabs.org; Wed, 29 Feb 2012 21:57:42 -0500 Received: from eggs.gnu.org ([208.118.235.92]:59320) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S2wCz-0003kN-35 for qemu-devel@nongnu.org; Wed, 29 Feb 2012 21:57:35 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1S2wCv-0000nr-6a for qemu-devel@nongnu.org; Wed, 29 Feb 2012 21:57:32 -0500 Received: from e28smtp02.in.ibm.com ([122.248.162.2]:36611) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S2wCt-0000mC-Up for qemu-devel@nongnu.org; Wed, 29 Feb 2012 21:57:29 -0500 Received: from /spool/local by e28smtp02.in.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Thu, 1 Mar 2012 08:27:21 +0530 Received: from d28relay05.in.ibm.com (9.184.220.62) by e28smtp02.in.ibm.com (192.168.1.132) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Thu, 1 Mar 2012 08:27:16 +0530 Received: from d28av04.in.ibm.com (d28av04.in.ibm.com [9.184.220.66]) by d28relay05.in.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id q212vFgr712808 for ; Thu, 1 Mar 2012 08:27:16 +0530 Received: from d28av04.in.ibm.com (loopback [127.0.0.1]) by d28av04.in.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id q212vFuk008966 for ; Thu, 1 Mar 2012 13:57:15 +1100 Received: from wdongxu-T410.cn.ibm.com (wdongxu-t410.cn.ibm.com [9.115.118.147] (may be forged)) by d28av04.in.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id q212vEDn008934; Thu, 1 Mar 2012 13:57:14 +1100 From: Dong Xu Wang To: qemu-devel@nongnu.org Date: Thu, 1 Mar 2012 10:56:50 +0800 Message-Id: <1330570610-8523-1-git-send-email-wdongxu@linux.vnet.ibm.com> X-Mailer: git-send-email 1.7.5.4 x-cbid: 12030102-5816-0000-0000-000001881500 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 122.248.162.2 Cc: Kevin Wolf , Marcelo Tosatti , Dong Xu Wang , Stefan Hajnoczi Subject: [Qemu-devel] [PATCH 1/2 v7] block: add-cow file format X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Dong Xu Wang Provide a new file format: add-cow. The usage can be found in add-cow.txt of this patch. CC: Marcelo Tosatti CC: Kevin Wolf CC: Stefan Hajnoczi Signed-off-by: Dong Xu Wang --- Makefile.objs | 1 + block.c | 2 +- block.h | 1 + block/add-cow-cache.c | 171 ++++++++++++++++++++ block/add-cow.c | 402 ++++++++++++++++++++++++++++++++++++++++++++++++ block_int.h | 1 + docs/specs/add-cow.txt | 68 ++++++++ 7 files changed, 645 insertions(+), 1 deletions(-) create mode 100644 block/add-cow-cache.c create mode 100644 block/add-cow.c create mode 100644 docs/specs/add-cow.txt diff --git a/Makefile.objs b/Makefile.objs index 808de6a..fa9dde0 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -34,6 +34,7 @@ block-nested-y += raw.o cow.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o vv block-nested-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.o block-nested-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o block-nested-y += qed-check.o +block-nested-y += add-cow.o add-cow-cache.o block-nested-y += parallels.o nbd.o blkdebug.o sheepdog.o blkverify.o block-nested-y += stream.o block-nested-$(CONFIG_WIN32) += raw-win32.o diff --git a/block.c b/block.c index 52ffe14..581c092 100644 --- a/block.c +++ b/block.c @@ -194,7 +194,7 @@ static void bdrv_io_limits_intercept(BlockDriverState *bs, } /* check if the path starts with ":" */ -static int path_has_protocol(const char *path) +int path_has_protocol(const char *path) { #ifdef _WIN32 if (is_windows_drive(path) || diff --git a/block.h b/block.h index 48d0bf3..3d96444 100644 --- a/block.h +++ b/block.h @@ -310,6 +310,7 @@ char *bdrv_snapshot_dump(char *buf, int buf_size, QEMUSnapshotInfo *sn); char *get_human_readable_size(char *buf, int buf_size, int64_t size); int path_is_absolute(const char *path); +int path_has_protocol(const char *path); void path_combine(char *dest, int dest_size, const char *base_path, const char *filename); diff --git a/block/add-cow-cache.c b/block/add-cow-cache.c new file mode 100644 index 0000000..6be02ff --- /dev/null +++ b/block/add-cow-cache.c @@ -0,0 +1,171 @@ +/* + * Cache For QEMU ADD-COW Disk Format + * + * Copyright IBM, Corp. 2012 + * + * Authors: + * Dong Xu Wang + * This work is licensed under the terms of the GNU LGPL, version 2 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "block_int.h" +#include "qemu-common.h" +#include "add-cow.h" + +AddCowCache *add_cow_cache_create(BlockDriverState *bs, int num_tables) +{ + BDRVAddCowState *s = bs->opaque; + AddCowCache *c; + int i; + + c = g_malloc0(sizeof(*c)); + c->size = num_tables; + c->entries = g_malloc0(sizeof(*c->entries) * num_tables); + + for (i = 0; i < c->size; i++) { + c->entries[i].table = qemu_blockalign(bs, s->cluster_size); + c->entries[i].offset = -1; + } + + return c; +} + +int add_cow_cache_destroy(BlockDriverState *bs, AddCowCache *c) +{ + int i; + + for (i = 0; i < c->size; i++) { + qemu_vfree(c->entries[i].table); + } + + g_free(c->entries); + g_free(c); + + return 0; +} + +static int add_cow_cache_find_entry_to_replace(AddCowCache *c) +{ + int i; + int min_count = INT_MAX; + int min_index = -1; + + + for (i = 0; i < c->size; i++) { + if (c->entries[i].cache_hits < min_count) { + min_index = i; + min_count = c->entries[i].cache_hits; + } + + c->entries[i].cache_hits /= 2; + } + + if (min_index == -1) { + abort(); + } + return min_index; +} + +static int add_cow_cache_entry_flush(BlockDriverState *bs, + AddCowCache *c, int i) +{ + BDRVAddCowState *s = bs->opaque; + int ret = 0; + + if (!c->entries[i].dirty || (-1 == c->entries[i].offset)) { + return 0; + } + ret = bdrv_flush(bs->file); + if (ret < 0) { + return ret; + } + + ret = bdrv_pwrite(bs->file, + sizeof(AddCowHeader) + c->entries[i].offset, + c->entries[i].table, + s->cluster_size); + if (ret < 0) { + return ret; + } + + c->entries[i].dirty = false; + + return 0; +} + +void add_cow_cache_entry_mark_dirty(AddCowCache *c, void *table) +{ + int i; + + for (i = 0; i < c->size; i++) { + if (c->entries[i].table == table) { + goto found; + } + } + abort(); + +found: + c->entries[i].dirty = true; +} + +int add_cow_cache_flush(BlockDriverState *bs, AddCowCache *c) +{ + int result = 0; + int ret; + int i; + + for (i = 0; i < c->size; i++) { + ret = add_cow_cache_entry_flush(bs, c, i); + if (ret < 0 && result != -ENOSPC) { + result = ret; + } + } + + if (result == 0) { + ret = bdrv_flush(bs->file); + if (ret < 0) { + result = ret; + } + } + + return result; +} + +int add_cow_cache_get(BlockDriverState *bs, AddCowCache *c, + uint64_t offset, void **table) +{ + BDRVAddCowState *s = bs->opaque; + int i; + int ret; + + for (i = 0; i < c->size; i++) { + if (c->entries[i].offset == offset) { + goto found; + } + } + + i = add_cow_cache_find_entry_to_replace(c); + if (i < 0) { + return i; + } + + ret = add_cow_cache_entry_flush(bs, c, i); + if (ret < 0) { + return ret; + } + + ret = bdrv_pread(bs->file, sizeof(AddCowHeader) + offset, + c->entries[i].table, s->cluster_size); + if (ret < 0) { + return ret; + } + c->entries[i].cache_hits = 32; + c->entries[i].offset = offset; + +found: + c->entries[i].cache_hits++; + *table = c->entries[i].table; + return 0; +} diff --git a/block/add-cow.c b/block/add-cow.c new file mode 100644 index 0000000..6897a52 --- /dev/null +++ b/block/add-cow.c @@ -0,0 +1,402 @@ +/* + * QEMU ADD-COW Disk Format + * + * Copyright IBM, Corp. 2012 + * + * Authors: + * Dong Xu Wang + * This work is licensed under the terms of the GNU LGPL, version 2 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu-common.h" +#include "block_int.h" +#include "module.h" +#include "add-cow.h" + +static int add_cow_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + const AddCowHeader *header = (const void *)buf; + + if (be64_to_cpu(header->magic) == ADD_COW_MAGIC && + be32_to_cpu(header->version) == ADD_COW_VERSION) { + return 100; + } else { + return 0; + } +} + +static int add_cow_open(BlockDriverState *bs, int flags) +{ + AddCowHeader header; + char image_filename[ADD_COW_FILE_LEN]; + BlockDriver *image_drv = NULL; + int ret; + BDRVAddCowState *s = bs->opaque; + + ret = bdrv_pread(bs->file, 0, &header, sizeof(header)); + if (ret != sizeof(header)) { + goto fail; + } + + if (be64_to_cpu(header.magic) != ADD_COW_MAGIC) { + ret = -EINVAL; + goto fail; + } + if (be32_to_cpu(header.version) != ADD_COW_VERSION) { + char version[64]; + snprintf(version, sizeof(version), "ADD-COW version %d", header.version); + qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE, + bs->device_name, "add-cow", version); + ret = -ENOTSUP; + goto fail; + } + + QEMU_BUILD_BUG_ON(sizeof(bs->backing_file) != sizeof(header.backing_file)); + strncpy(bs->backing_file, header.backing_file, + sizeof(bs->backing_file)); + + if (header.image_file[0] == '\0') { + ret = -ENOENT; + goto fail; + } + s->image_hd = bdrv_new(""); + if (path_has_protocol(header.image_file)) { + strncpy(image_filename, header.image_file, sizeof(image_filename)); + } else { + path_combine(image_filename, sizeof(image_filename), + bs->filename, header.image_file); + } + + image_drv = bdrv_find_format("raw"); + ret = bdrv_open(s->image_hd, image_filename, flags, image_drv); + if (ret < 0) { + bdrv_delete(s->image_hd); + goto fail; + } + bs->total_sectors = s->image_hd->total_sectors; + s->cluster_size = ADD_COW_CLUSTER_SIZE; + s->bitmap_cache = add_cow_cache_create(bs, ADD_COW_CACHE_SIZE); + qemu_co_mutex_init(&s->lock); + return 0; + fail: + return ret; +} + +static inline bool is_bit_set(BlockDriverState *bs, int64_t bitnum) +{ + BDRVAddCowState *s = bs->opaque; + uint64_t offset = bitnum >> 3; + uint8_t *bitmap; + int ret = add_cow_cache_get(bs, s->bitmap_cache, + offset & ~(ADD_COW_CLUSTER_SIZE - 1), (void **)&bitmap); + if (ret < 0) { + abort(); + } + + return *(bitmap + (offset & (ADD_COW_CLUSTER_SIZE - 1))) & (1 << (bitnum % 8)); +} + +static coroutine_fn int add_cow_is_allocated(BlockDriverState *bs, + int64_t sector_num, int nb_sectors, int *num_same) +{ + int changed; + + if (nb_sectors == 0) { + *num_same = nb_sectors; + return 0; + } + + changed = is_bit_set(bs, sector_num); + for (*num_same = 1; *num_same < nb_sectors; (*num_same)++) { + if (is_bit_set(bs, sector_num + *num_same) != changed) { + break; + } + } + + return changed; +} + +static int add_cow_update_bitmap(BlockDriverState *bs, int64_t sector_num, + int nb_sectors) +{ + BDRVAddCowState *s = bs->opaque; + uint8_t *bitmap; + + int i, ret = 0; + for (i = 0; i < nb_sectors; i++) { + int ret = add_cow_cache_get(bs, s->bitmap_cache, + (sector_num + i) / 8 & ~(ADD_COW_CLUSTER_SIZE - 1), (void **)&bitmap); + if (ret < 0) { + abort(); + } + *(bitmap + ((sector_num + i) / 8 & (ADD_COW_CLUSTER_SIZE - 1))) |= + (1 << ((sector_num + i) % 8)); + add_cow_cache_entry_mark_dirty(s->bitmap_cache, bitmap); + + } + ret = add_cow_cache_flush(bs, s->bitmap_cache); + if (ret < 0) { + abort(); + } + return ret; +} + +static void add_cow_close(BlockDriverState *bs) +{ + BDRVAddCowState *s = bs->opaque; + add_cow_cache_destroy(bs, s->bitmap_cache); + bdrv_delete(s->image_hd); +} +static int add_cow_create(const char *filename, QEMUOptionParameter *options) +{ + AddCowHeader header; + int64_t image_sectors = 0; + const char *backing_filename = NULL; + const char *image_filename = NULL; + int ret; + BlockDriverState *bs, *image_bs = NULL, *backing_bs = NULL; + + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + image_sectors = options->value.n / BDRV_SECTOR_SIZE; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { + backing_filename = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_IMAGE_FILE)) { + image_filename = options->value.s; + } + options++; + } + + if (!backing_filename || !image_filename) { + error_report("Both backing_file and image_file should be given."); + return -EINVAL; + } + + ret = bdrv_file_open(&image_bs, image_filename, BDRV_O_RDWR + | BDRV_O_CACHE_WB); + if (ret < 0) { + return ret; + } + image_sectors = image_bs->total_sectors; + bdrv_delete(image_bs); + + ret = bdrv_file_open(&backing_bs, backing_filename, BDRV_O_RDWR + | BDRV_O_CACHE_WB); + if (ret < 0) { + return ret; + } + bdrv_delete(backing_bs); + + ret = bdrv_create_file(filename, NULL); + if (ret < 0) { + return ret; + } + + ret = bdrv_file_open(&bs, filename, BDRV_O_RDWR); + if (ret < 0) { + return ret; + } + + memset(&header, 0, sizeof(header)); + header.magic = cpu_to_be64(ADD_COW_MAGIC); + header.version = cpu_to_be32(ADD_COW_VERSION); + strncpy(header.backing_file, backing_filename, sizeof(header.backing_file)); + strncpy(header.image_file, image_filename, sizeof(header.image_file)); + + ret = bdrv_pwrite(bs, 0, &header, sizeof(header)); + if (ret < 0) { + bdrv_delete(bs); + return ret; + } + + BlockDriver *drv = bdrv_find_format("add-cow"); + assert(drv != NULL); + ret = bdrv_open(bs, filename, BDRV_O_RDWR | BDRV_O_NO_FLUSH, drv); + if (ret < 0) { + bdrv_delete(bs); + return ret; + } + + ret = bdrv_truncate(bs, image_sectors * BDRV_SECTOR_SIZE); + bdrv_delete(bs); + return ret; +} + +static int add_cow_backing_read(BlockDriverState *bs, QEMUIOVector *qiov, + int64_t sector_num, int nb_sectors) +{ + int n1; + if ((sector_num + nb_sectors) <= bs->total_sectors) { + return nb_sectors; + } + if (sector_num >= bs->total_sectors) { + n1 = 0; + } else { + n1 = bs->total_sectors - sector_num; + } + + qemu_iovec_memset_skip(qiov, 0, BDRV_SECTOR_SIZE * (nb_sectors - n1), + BDRV_SECTOR_SIZE * n1); + return n1; +} + +static coroutine_fn int add_cow_co_readv(BlockDriverState *bs, int64_t sector_num, + int remaining_sectors, QEMUIOVector *qiov) +{ + BDRVAddCowState *s = bs->opaque; + int cur_nr_sectors; + uint64_t bytes_done = 0; + QEMUIOVector hd_qiov; + int n, n1, ret = 0; + + qemu_iovec_init(&hd_qiov, qiov->niov); + qemu_co_mutex_lock(&s->lock); + while (remaining_sectors != 0) { + cur_nr_sectors = remaining_sectors; + if (add_cow_is_allocated(bs, sector_num, cur_nr_sectors, &n)) { + cur_nr_sectors = n; + qemu_iovec_reset(&hd_qiov); + qemu_iovec_copy(&hd_qiov, qiov, bytes_done, + cur_nr_sectors * BDRV_SECTOR_SIZE); + ret = bdrv_co_readv(s->image_hd, sector_num, n, &hd_qiov); + if (ret < 0) { + goto fail; + } + } else { + cur_nr_sectors = n; + if (bs->backing_hd) { + n1 = add_cow_backing_read(bs->backing_hd, &hd_qiov, + sector_num, cur_nr_sectors); + if (n1 > 0) { + qemu_iovec_reset(&hd_qiov); + qemu_iovec_copy(&hd_qiov, qiov, bytes_done, + cur_nr_sectors * BDRV_SECTOR_SIZE); + ret = bdrv_co_readv(bs->backing_hd, sector_num, + n, &hd_qiov); + if (ret < 0) { + goto fail; + } + } + } else { + qemu_iovec_reset(&hd_qiov); + } + } + remaining_sectors -= cur_nr_sectors; + sector_num += cur_nr_sectors; + bytes_done += cur_nr_sectors * BDRV_SECTOR_SIZE; + } +fail: + qemu_co_mutex_unlock(&s->lock); + qemu_iovec_destroy(&hd_qiov); + return ret; +} + +static coroutine_fn int add_cow_co_writev(BlockDriverState *bs, int64_t sector_num, + int remaining_sectors, QEMUIOVector *qiov) +{ + BDRVAddCowState *s = bs->opaque; + int ret = 0; + QEMUIOVector hd_qiov; + qemu_iovec_init(&hd_qiov, qiov->niov); + qemu_co_mutex_lock(&s->lock); + qemu_iovec_reset(&hd_qiov); + qemu_iovec_copy(&hd_qiov, qiov, 0, remaining_sectors * BDRV_SECTOR_SIZE); + ret = bdrv_co_writev(s->image_hd, + sector_num, + remaining_sectors, &hd_qiov); + if (ret < 0) { + goto fail; + } + + ret = add_cow_update_bitmap(bs, sector_num, remaining_sectors); + if (ret < 0) { + goto fail; + } +fail: + qemu_co_mutex_unlock(&s->lock); + qemu_iovec_destroy(&hd_qiov); + return ret; +} + +static int bdrv_add_cow_truncate(BlockDriverState *bs, int64_t offset) +{ + int ret = 0; + int64_t image_sectors = offset / BDRV_SECTOR_SIZE; + BDRVAddCowState *s = bs->opaque; + int64_t old_image_sector = s->image_hd->total_sectors; + + ret = bdrv_truncate(bs->file, sizeof(AddCowHeader) + ((image_sectors + 7) >> 3)); + if (ret < 0) { + bdrv_truncate(s->image_hd, old_image_sector * BDRV_SECTOR_SIZE); + return ret; + } + return ret; +} + +static coroutine_fn int add_cow_co_flush(BlockDriverState *bs) +{ + BDRVAddCowState *s = bs->opaque; + int ret = bdrv_co_flush(s->image_hd); + if (ret < 0) { + return ret; + } + + qemu_co_mutex_lock(&s->lock); + ret = add_cow_cache_flush(bs, s->bitmap_cache); + if (ret < 0) { + qemu_co_mutex_unlock(&s->lock); + return ret; + } + qemu_co_mutex_unlock(&s->lock); + return bdrv_co_flush(bs->file); +} + +static QEMUOptionParameter add_cow_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" + }, + { + .name = BLOCK_OPT_IMAGE_FILE, + .type = OPT_STRING, + .help = "File name of a image file" + }, + { + .name = BLOCK_OPT_BACKING_FMT, + .type = OPT_STRING, + .help = "Image format of the base image" + }, + { NULL } +}; + +static BlockDriver bdrv_add_cow = { + .format_name = "add-cow", + .instance_size = sizeof(BDRVAddCowState), + .bdrv_probe = add_cow_probe, + .bdrv_open = add_cow_open, + .bdrv_close = add_cow_close, + .bdrv_create = add_cow_create, + .bdrv_co_is_allocated = add_cow_is_allocated, + + .bdrv_co_readv = add_cow_co_readv, + .bdrv_co_writev = add_cow_co_writev, + .bdrv_truncate = bdrv_add_cow_truncate, + + .create_options = add_cow_create_options, + .bdrv_co_flush_to_disk = add_cow_co_flush, +}; + +static void bdrv_add_cow_init(void) +{ + bdrv_register(&bdrv_add_cow); +} + +block_init(bdrv_add_cow_init); diff --git a/block_int.h b/block_int.h index b460c36..8126f27 100644 --- a/block_int.h +++ b/block_int.h @@ -50,6 +50,7 @@ #define BLOCK_OPT_TABLE_SIZE "table_size" #define BLOCK_OPT_PREALLOC "preallocation" #define BLOCK_OPT_SUBFMT "subformat" +#define BLOCK_OPT_IMAGE_FILE "image_file" typedef struct BdrvTrackedRequest BdrvTrackedRequest; diff --git a/docs/specs/add-cow.txt b/docs/specs/add-cow.txt new file mode 100644 index 0000000..db992a4 --- /dev/null +++ b/docs/specs/add-cow.txt @@ -0,0 +1,68 @@ +== General == + +Raw file format does not support backing_file and copy on write feature. +The add-cow image format makes it possible to use backing files with raw +image by keeping a separate .add-cow metadata file. Once all sectors +have been written to in the raw image it is safe to discard the .add-cow +and backing files and instead use the raw image directly. + +When using add-cow, procedures may like this: +(ubuntu.img is a disk image which has been installed OS.) + 1) Create a raw image with the same size of ubuntu.img + qemu-img create -f raw test.raw 8G + 2) Create a add-cow image which will store dirty bitmap + qemu-img create -f add-cow test.add-cow -o backing_file=ubuntu.img,image_file=test.raw + 3) Run qemu with add-cow image + qemu -drive if=virtio,file=test.add-cow + +=Specification= + +The file format looks like this: + + +---------------+--------------------------+ + | Header | Data | + +---------------+--------------------------+ + +All numbers in add-cow are stored in Big Endian byte order. + +== Header == + +The Header is included in the first bytes: + + Byte 0 - 7: magic + add-cow magic string ("ADD_COW\xff") + + 8 - 11: version + Version number (only valid value is 1 now) + + 12 - 1035: backing_file + backing_file file name related to add-cow file. All + unused bytes are padded with zeros. Must not be longer + than 1023 bytes. + + 1036 - 2059: image_file + image_file is a raw file. All unused bytes are padded + with zeros. Must not be longer than 1023 bytes. + + 2060 - 2559: The Reserved field is used to make sure Data field starts + at the multiple of 512, not used currently. All bytes are + filled with 0. + +== Data == + +The Data field starts at the 2560th byte, stores a bitmap related to backing_file +and image_file. The bitmap will track whether the sector in backing_file is dirty +or not. + + +Each bit in the bitmap indicates one sector's status. So the size of bitmap is +calculated according to virtual size of backing_file. In each byte, bit 0 to 7 +will track the 1st to 7th sector in sequence, bit orders in one byte look like: + +----+----+----+----+----+----+----+----+ + | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | + +----+----+----+----+----+----+----+----+ + +If the bit is 0, indicates the sector has not been allocated in image_file, data +should be loaded from backing_file while reading; if the bit is 1, indicates the +related sector has been dirty, should be loaded from image_file while reading. +Writing to a sector causes the corresponding bit to be set to 1.