diff options
-rw-r--r-- | qemu-img-cmds.hx | 6 | ||||
-rw-r--r-- | qemu-img.c | 225 |
2 files changed, 231 insertions, 0 deletions
diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 641bd8775b..f28ef36328 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 5ad88bf763..48b9315aa8 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 }, |