qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH v2 3/3] qemu-img rebase


From: Kevin Wolf
Subject: [Qemu-devel] [PATCH v2 3/3] qemu-img rebase
Date: Fri, 11 Dec 2009 09:49:26 +0100

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 <address@hidden>
---

v2:
- Added missing braces in copied code

 qemu-img-cmds.hx |    6 ++
 qemu-img.c       |  192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 198 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
address@hidden 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..1459e65 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,164 @@ 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;
+    char *filename;
+    const char *out_basefmt, *out_baseimg;
+    int c, flags;
+    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);
+    }
+
+    /* For safe rebasing we need to compare old and new backing file */
+    if (!unsafe) {
+        char backing_name[1024];
+
+        // FIXME use the specified format
+        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, NULL)) {
+            error("Could not open old backing file '%s'", backing_name);
+            return -1;
+        }
+
+        // FIXME use the specified format
+        bs_new_backing = bdrv_new("new_backing");
+        if (bdrv_open2(bs_new_backing, out_baseimg, BRDV_O_FLAGS, NULL)) {
+            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))
+                {
+                    bdrv_write(bs, sector + written,
+                        buf_old + written * 512, pnum);
+                }
+
+                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.
+     */
+    bdrv_change_backing_file(bs, out_baseimg, out_basefmt);
+
+    /*
+     * 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 },
-- 
1.6.2.5





reply via email to

[Prev in Thread] Current Thread [Next in Thread]