From patchwork Thu Dec 17 14:40:22 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Wolf X-Patchwork-Id: 41318 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 7900AB70BA for ; Fri, 18 Dec 2009 01:50:20 +1100 (EST) Received: from localhost ([127.0.0.1]:38771 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1NLHgH-0007PI-EY for incoming@patchwork.ozlabs.org; Thu, 17 Dec 2009 09:50:17 -0500 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1NLHXv-0004zd-Hb for qemu-devel@nongnu.org; Thu, 17 Dec 2009 09:41:39 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1NLHXr-0004uV-0O for qemu-devel@nongnu.org; Thu, 17 Dec 2009 09:41:38 -0500 Received: from [199.232.76.173] (port=59881 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1NLHXq-0004u7-A2 for qemu-devel@nongnu.org; Thu, 17 Dec 2009 09:41:34 -0500 Received: from mx1.redhat.com ([209.132.183.28]:13631) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1NLHXp-0004lF-Cq for qemu-devel@nongnu.org; Thu, 17 Dec 2009 09:41:33 -0500 Received: from int-mx01.intmail.prod.int.phx2.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id nBHEfV4x024952 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Thu, 17 Dec 2009 09:41:32 -0500 Received: from localhost.localdomain (dhcp-5-175.str.redhat.com [10.32.5.175]) by int-mx01.intmail.prod.int.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id nBHEfRwM002820; Thu, 17 Dec 2009 09:41:31 -0500 From: Kevin Wolf To: qemu-devel@nongnu.org Date: Thu, 17 Dec 2009 15:40:22 +0100 Message-Id: <1261060822-23219-4-git-send-email-kwolf@redhat.com> In-Reply-To: <1261060822-23219-1-git-send-email-kwolf@redhat.com> References: <1261060822-23219-1-git-send-email-kwolf@redhat.com> X-Scanned-By: MIMEDefang 2.67 on 10.5.11.11 X-detected-operating-system: by monty-python.gnu.org: Genre and OS details not recognized. Cc: Kevin Wolf Subject: [Qemu-devel] [PATCH v3 3/3] qemu-img rebase X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This adds a rebase subcommand to qemu-img which allows to change the backing file of an image. In default mode, both the current and the new backing file need to exist, and after the rebase, the COW image is guaranteed to have the same guest visible content as before. To achieve this, old and new backing file are compared and, if necessary, data is copied from the old backing file into the COW image. With -u an unsafe mode is enabled that doesn't require the backing files to exist. It merely changes the backing file reference in the COW image. This is useful for renaming or moving the backing file. The user is responsible to make sure that the new backing file has no changes compared to the old one, or corruption may occur. Signed-off-by: Kevin Wolf --- qemu-img-cmds.hx | 6 ++ qemu-img.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 0 deletions(-) diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 641bd87..f28ef36 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -43,5 +43,11 @@ DEF("snapshot", img_snapshot, "snapshot [-l | -a snapshot | -c snapshot | -d snapshot] filename") STEXI @item snapshot [-l | -a @var{snapshot} | -c @var{snapshot} | -d @var{snapshot}] @var{filename} +ETEXI + +DEF("rebase", img_rebase, + "rebase [-u] -b backing_file [-F backing_fmt] filename") +STEXI +@item rebase [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename} @end table ETEXI diff --git a/qemu-img.c b/qemu-img.c index 5ad88bf..48b9315 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -81,6 +81,9 @@ static void help(void) " name=value format. Use -o ? for an overview of the options supported by the\n" " used format\n" " '-c' indicates that target image must be compressed (qcow format only)\n" + " '-u' enables unsafe rebasing. It is assumed that old and new backing file\n" + " match exactly. The image doesn't need a working backing file before\n" + " rebasing in this case (useful for renaming the backing file)\n" " '-h' with or without a command shows this help and lists the supported formats\n" "\n" "Parameters to snapshot subcommand:\n" @@ -527,6 +530,37 @@ static int is_allocated_sectors(const uint8_t *buf, int n, int *pnum) return v; } +/* + * Compares two buffers sector by sector. Returns 0 if the first sector of both + * buffers matches, non-zero otherwise. + * + * pnum is set to the number of sectors (including and immediately following + * the first one) that are known to have the same comparison result + */ +static int compare_sectors(const uint8_t *buf1, const uint8_t *buf2, int n, + int *pnum) +{ + int res, i; + + if (n <= 0) { + *pnum = 0; + return 0; + } + + res = !!memcmp(buf1, buf2, 512); + for(i = 1; i < n; i++) { + buf1 += 512; + buf2 += 512; + + if (!!memcmp(buf1, buf2, 512) != res) { + break; + } + } + + *pnum = i; + return res; +} + #define IO_BUF_SIZE (2 * 1024 * 1024) static int img_convert(int argc, char **argv) @@ -1033,6 +1067,197 @@ static int img_snapshot(int argc, char **argv) return 0; } +static int img_rebase(int argc, char **argv) +{ + BlockDriverState *bs, *bs_old_backing, *bs_new_backing; + BlockDriver *old_backing_drv, *new_backing_drv; + char *filename; + const char *out_basefmt, *out_baseimg; + int c, flags, ret; + int unsafe = 0; + + /* Parse commandline parameters */ + out_baseimg = NULL; + out_basefmt = NULL; + + for(;;) { + c = getopt(argc, argv, "uhF:b:"); + if (c == -1) + break; + switch(c) { + case 'h': + help(); + return 0; + case 'F': + out_basefmt = optarg; + break; + case 'b': + out_baseimg = optarg; + break; + case 'u': + unsafe = 1; + break; + } + } + + if ((optind >= argc) || !out_baseimg) + help(); + filename = argv[optind++]; + + /* + * Open the images. + * + * Ignore the old backing file for unsafe rebase in case we want to correct + * the reference to a renamed or moved backing file. + */ + bs = bdrv_new(""); + if (!bs) + error("Not enough memory"); + + flags = BRDV_O_FLAGS | (unsafe ? BDRV_O_NO_BACKING : 0); + if (bdrv_open2(bs, filename, flags, NULL) < 0) { + error("Could not open '%s'", filename); + } + + /* Find the right drivers for the backing files */ + old_backing_drv = NULL; + new_backing_drv = NULL; + + if (!unsafe && bs->backing_format[0] != '\0') { + old_backing_drv = bdrv_find_format(bs->backing_format); + if (old_backing_drv == NULL) { + error("Invalid format name: '%s'", bs->backing_format); + } + } + + if (out_basefmt != NULL) { + new_backing_drv = bdrv_find_format(out_basefmt); + if (new_backing_drv == NULL) { + error("Invalid format name: '%s'", out_basefmt); + } + } + + /* For safe rebasing we need to compare old and new backing file */ + if (unsafe) { + /* Make the compiler happy */ + bs_old_backing = NULL; + bs_new_backing = NULL; + } else { + char backing_name[1024]; + + bs_old_backing = bdrv_new("old_backing"); + bdrv_get_backing_filename(bs, backing_name, sizeof(backing_name)); + if (bdrv_open2(bs_old_backing, backing_name, BRDV_O_FLAGS, + old_backing_drv)) + { + error("Could not open old backing file '%s'", backing_name); + return -1; + } + + bs_new_backing = bdrv_new("new_backing"); + if (bdrv_open2(bs_new_backing, out_baseimg, BRDV_O_FLAGS, + new_backing_drv)) + { + error("Could not open new backing file '%s'", backing_name); + return -1; + } + } + + /* + * Check each unallocated cluster in the COW file. If it is unallocated, + * accesses go to the backing file. We must therefore compare this cluster + * in the old and new backing file, and if they differ we need to copy it + * from the old backing file into the COW file. + * + * If qemu-img crashes during this step, no harm is done. The content of + * the image is the same as the original one at any time. + */ + if (!unsafe) { + uint64_t num_sectors; + uint64_t sector; + int n, n1; + uint8_t buf_old[IO_BUF_SIZE]; + uint8_t buf_new[IO_BUF_SIZE]; + + bdrv_get_geometry(bs, &num_sectors); + + for (sector = 0; sector < num_sectors; sector += n) { + + /* How many sectors can we handle with the next read? */ + if (sector + (IO_BUF_SIZE / 512) <= num_sectors) { + n = (IO_BUF_SIZE / 512); + } else { + n = num_sectors - sector; + } + + /* If the cluster is allocated, we don't need to take action */ + if (bdrv_is_allocated(bs, sector, n, &n1)) { + n = n1; + continue; + } + + /* Read old and new backing file */ + if (bdrv_read(bs_old_backing, sector, buf_old, n) < 0) { + error("error while reading from old backing file"); + } + if (bdrv_read(bs_new_backing, sector, buf_new, n) < 0) { + error("error while reading from new backing file"); + } + + /* If they differ, we need to write to the COW file */ + uint64_t written = 0; + + while (written < n) { + int pnum; + + if (compare_sectors(buf_old + written * 512, + buf_new + written * 512, n, &pnum)) + { + ret = bdrv_write(bs, sector + written, + buf_old + written * 512, pnum); + if (ret < 0) { + error("Error while writing to COW image: %s", + strerror(-ret)); + } + } + + written += pnum; + } + } + } + + /* + * Change the backing file. All clusters that are different from the old + * backing file are overwritten in the COW file now, so the visible content + * doesn't change when we switch the backing file. + */ + ret = bdrv_change_backing_file(bs, out_baseimg, out_basefmt); + if (ret == -ENOSPC) { + error("Could not change the backing file to '%s': No space left in " + "the file header", out_baseimg); + } else if (ret < 0) { + error("Could not change the backing file to '%s': %s", + out_baseimg, strerror(-ret)); + } + + /* + * TODO At this point it is possible to check if any clusters that are + * allocated in the COW file are the same in the backing file. If so, they + * could be dropped from the COW file. Don't do this before switching the + * backing file, in case of a crash this would lead to corruption. + */ + + /* Cleanup */ + if (!unsafe) { + bdrv_delete(bs_old_backing); + bdrv_delete(bs_new_backing); + } + + bdrv_delete(bs); + + return 0; +} + static const img_cmd_t img_cmds[] = { #define DEF(option, callback, arg_string) \ { option, callback },