diff mbox

[v3,3/3] qemu-img rebase

Message ID 1261060822-23219-4-git-send-email-kwolf@redhat.com
State New
Headers show

Commit Message

Kevin Wolf Dec. 17, 2009, 2:40 p.m. UTC
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 <kwolf@redhat.com>
---
 qemu-img-cmds.hx |    6 ++
 qemu-img.c       |  225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 231 insertions(+), 0 deletions(-)
diff mbox

Patch

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 },