ddf19c
From 53baacb72e8561391841363b2acbd85a783cbc66 Mon Sep 17 00:00:00 2001
ddf19c
From: Eric Blake <eblake@redhat.com>
ddf19c
Date: Tue, 2 Jun 2020 02:34:15 +0100
ddf19c
Subject: [PATCH 10/26] qemu-img: Add bitmap sub-command
ddf19c
ddf19c
RH-Author: Eric Blake <eblake@redhat.com>
ddf19c
Message-id: <20200602023420.2133649-8-eblake@redhat.com>
ddf19c
Patchwork-id: 97074
ddf19c
O-Subject: [RHEL-AV-8.2.1 qemu-kvm PATCH 07/12] qemu-img: Add bitmap sub-command
ddf19c
Bugzilla: 1779893 1779904
ddf19c
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
ddf19c
RH-Acked-by: Max Reitz <mreitz@redhat.com>
ddf19c
RH-Acked-by: Kevin Wolf <kwolf@redhat.com>
ddf19c
ddf19c
Include actions for --add, --remove, --clear, --enable, --disable, and
ddf19c
--merge (note that --clear is a bit of fluff, because the same can be
ddf19c
accomplished by removing a bitmap and then adding a new one in its
ddf19c
place, but it matches what QMP commands exist).  Listing is omitted,
ddf19c
because it does not require a bitmap name and because it was already
ddf19c
possible with 'qemu-img info'.  A single command line can play one or
ddf19c
more bitmap commands in sequence on the same bitmap name (although all
ddf19c
added bitmaps share the same granularity, and and all merged bitmaps
ddf19c
come from the same source file).  Merge defaults to other bitmaps in
ddf19c
the primary image, but can also be told to merge bitmaps from a
ddf19c
distinct image.
ddf19c
ddf19c
While this supports --image-opts for the file being modified, I did
ddf19c
not think it worth the extra complexity to support that for the source
ddf19c
file in a cross-file merges.  Likewise, I chose to have --merge only
ddf19c
take a single source rather than following the QMP support for
ddf19c
multiple merges in one go (although you can still use more than one
ddf19c
--merge in the command line); in part because qemu-img is offline and
ddf19c
therefore atomicity is not an issue.
ddf19c
ddf19c
Upcoming patches will add iotest coverage of these commands while
ddf19c
also testing other features.
ddf19c
ddf19c
Signed-off-by: Eric Blake <eblake@redhat.com>
ddf19c
Reviewed-by: Max Reitz <mreitz@redhat.com>
ddf19c
Message-Id: <20200513011648.166876-7-eblake@redhat.com>
ddf19c
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
ddf19c
(cherry picked from commit 3b51ab4bf0f49a01cc2db7b954e0669e081719b5)
ddf19c
ddf19c
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
ddf19c
ddf19c
Conflicts:
ddf19c
	docs/tools/qemu-img.rst - lives in qemu-img.texi instead; plus
ddf19c
	 fix a typo in the text for --merge rather than waiting for
ddf19c
	 a one-line upstream followup patch
ddf19c
	qemu-img-cmds.hx - context, use texi instead of rst
ddf19c
	qemu-img.c - context
ddf19c
Signed-off-by: Eric Blake <eblake@redhat.com>
ddf19c
ddf19c
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
ddf19c
---
ddf19c
 qemu-img-cmds.hx |   6 ++
ddf19c
 qemu-img.c       | 248 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
ddf19c
 qemu-img.texi    |  27 ++++++
ddf19c
 3 files changed, 281 insertions(+)
ddf19c
ddf19c
diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx
ddf19c
index 1c93e6d..1a6a8e9 100644
ddf19c
--- a/qemu-img-cmds.hx
ddf19c
+++ b/qemu-img-cmds.hx
ddf19c
@@ -25,6 +25,12 @@ STEXI
ddf19c
 @item bench [-c @var{count}] [-d @var{depth}] [-f @var{fmt}] [--flush-interval=@var{flush_interval}] [-n] [--no-drain] [-o @var{offset}] [--pattern=@var{pattern}] [-q] [-s @var{buffer_size}] [-S @var{step_size}] [-t @var{cache}] [-w] [-U] @var{filename}
ddf19c
 ETEXI
ddf19c
 
ddf19c
+DEF("bitmap", img_bitmap,
ddf19c
+    "bitmap (--merge SOURCE | --add | --remove | --clear | --enable | --disable)... [-b source_file [-F source_fmt]] [-g granularity] [--object objectdef] [--image-opts | -f fmt] filename bitmap")
ddf19c
+STEXI
ddf19c
+.. option:: bitmap (--merge @var{source} | --add | --remove | --clear | --enable | --disable)... [-b @var{source_file} [-F @var{source_fmt}]] [-g @var{granularity}] [--object @var{objectdef}] [--image-opts | -f @var{fmt}] @var{filename} @var{bitmap}
ddf19c
+ETEXI
ddf19c
+
ddf19c
 DEF("check", img_check,
ddf19c
     "check [--object objectdef] [--image-opts] [-q] [-f fmt] [--output=ofmt] [-r [leaks | all]] [-T src_cache] [-U] filename")
ddf19c
 STEXI
ddf19c
diff --git a/qemu-img.c b/qemu-img.c
ddf19c
index e69529b..11a4537 100644
ddf19c
--- a/qemu-img.c
ddf19c
+++ b/qemu-img.c
ddf19c
@@ -28,6 +28,7 @@
ddf19c
 #include "qemu-common.h"
ddf19c
 #include "qemu-version.h"
ddf19c
 #include "qapi/error.h"
ddf19c
+#include "qapi/qapi-commands-block-core.h"
ddf19c
 #include "qapi/qapi-visit-block-core.h"
ddf19c
 #include "qapi/qobject-output-visitor.h"
ddf19c
 #include "qapi/qmp/qjson.h"
ddf19c
@@ -70,6 +71,12 @@ enum {
ddf19c
     OPTION_PREALLOCATION = 265,
ddf19c
     OPTION_SHRINK = 266,
ddf19c
     OPTION_SALVAGE = 267,
ddf19c
+    OPTION_ADD = 269,
ddf19c
+    OPTION_REMOVE = 270,
ddf19c
+    OPTION_CLEAR = 271,
ddf19c
+    OPTION_ENABLE = 272,
ddf19c
+    OPTION_DISABLE = 273,
ddf19c
+    OPTION_MERGE = 274,
ddf19c
 };
ddf19c
 
ddf19c
 typedef enum OutputFormat {
ddf19c
@@ -168,6 +175,14 @@ static void QEMU_NORETURN help(void)
ddf19c
            "  '-n' skips the target volume creation (useful if the volume is created\n"
ddf19c
            "       prior to running qemu-img)\n"
ddf19c
            "\n"
ddf19c
+           "Parameters to bitmap subcommand:\n"
ddf19c
+           "  'bitmap' is the name of the bitmap to manipulate, through one or more\n"
ddf19c
+           "       actions from '--add', '--remove', '--clear', '--enable', '--disable',\n"
ddf19c
+           "       or '--merge source'\n"
ddf19c
+           "  '-g granularity' sets the granularity for '--add' actions\n"
ddf19c
+           "  '-b source' and '-F src_fmt' tell '--merge' actions to find the source\n"
ddf19c
+           "       bitmaps from an alternative file\n"
ddf19c
+           "\n"
ddf19c
            "Parameters to check subcommand:\n"
ddf19c
            "  '-r' tries to repair any inconsistencies that are found during the check.\n"
ddf19c
            "       '-r leaks' repairs only cluster leaks, whereas '-r all' fixes all\n"
ddf19c
@@ -4402,6 +4417,239 @@ out:
ddf19c
     return 0;
ddf19c
 }
ddf19c
 
ddf19c
+enum ImgBitmapAct {
ddf19c
+    BITMAP_ADD,
ddf19c
+    BITMAP_REMOVE,
ddf19c
+    BITMAP_CLEAR,
ddf19c
+    BITMAP_ENABLE,
ddf19c
+    BITMAP_DISABLE,
ddf19c
+    BITMAP_MERGE,
ddf19c
+};
ddf19c
+typedef struct ImgBitmapAction {
ddf19c
+    enum ImgBitmapAct act;
ddf19c
+    const char *src; /* only used for merge */
ddf19c
+    QSIMPLEQ_ENTRY(ImgBitmapAction) next;
ddf19c
+} ImgBitmapAction;
ddf19c
+
ddf19c
+static int img_bitmap(int argc, char **argv)
ddf19c
+{
ddf19c
+    Error *err = NULL;
ddf19c
+    int c, ret = 1;
ddf19c
+    QemuOpts *opts = NULL;
ddf19c
+    const char *fmt = NULL, *src_fmt = NULL, *src_filename = NULL;
ddf19c
+    const char *filename, *bitmap;
ddf19c
+    BlockBackend *blk = NULL, *src = NULL;
ddf19c
+    BlockDriverState *bs = NULL, *src_bs = NULL;
ddf19c
+    bool image_opts = false;
ddf19c
+    int64_t granularity = 0;
ddf19c
+    bool add = false, merge = false;
ddf19c
+    QSIMPLEQ_HEAD(, ImgBitmapAction) actions;
ddf19c
+    ImgBitmapAction *act, *act_next;
ddf19c
+    const char *op;
ddf19c
+
ddf19c
+    QSIMPLEQ_INIT(&actions);
ddf19c
+
ddf19c
+    for (;;) {
ddf19c
+        static const struct option long_options[] = {
ddf19c
+            {"help", no_argument, 0, 'h'},
ddf19c
+            {"object", required_argument, 0, OPTION_OBJECT},
ddf19c
+            {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS},
ddf19c
+            {"add", no_argument, 0, OPTION_ADD},
ddf19c
+            {"remove", no_argument, 0, OPTION_REMOVE},
ddf19c
+            {"clear", no_argument, 0, OPTION_CLEAR},
ddf19c
+            {"enable", no_argument, 0, OPTION_ENABLE},
ddf19c
+            {"disable", no_argument, 0, OPTION_DISABLE},
ddf19c
+            {"merge", required_argument, 0, OPTION_MERGE},
ddf19c
+            {"granularity", required_argument, 0, 'g'},
ddf19c
+            {"source-file", required_argument, 0, 'b'},
ddf19c
+            {"source-format", required_argument, 0, 'F'},
ddf19c
+            {0, 0, 0, 0}
ddf19c
+        };
ddf19c
+        c = getopt_long(argc, argv, ":b:f:F:g:h", long_options, NULL);
ddf19c
+        if (c == -1) {
ddf19c
+            break;
ddf19c
+        }
ddf19c
+
ddf19c
+        switch (c) {
ddf19c
+        case ':':
ddf19c
+            missing_argument(argv[optind - 1]);
ddf19c
+            break;
ddf19c
+        case '?':
ddf19c
+            unrecognized_option(argv[optind - 1]);
ddf19c
+            break;
ddf19c
+        case 'h':
ddf19c
+            help();
ddf19c
+            break;
ddf19c
+        case 'b':
ddf19c
+            src_filename = optarg;
ddf19c
+            break;
ddf19c
+        case 'f':
ddf19c
+            fmt = optarg;
ddf19c
+            break;
ddf19c
+        case 'F':
ddf19c
+            src_fmt = optarg;
ddf19c
+            break;
ddf19c
+        case 'g':
ddf19c
+            granularity = cvtnum("granularity", optarg);
ddf19c
+            if (granularity < 0) {
ddf19c
+                return 1;
ddf19c
+            }
ddf19c
+            break;
ddf19c
+        case OPTION_ADD:
ddf19c
+            act = g_new0(ImgBitmapAction, 1);
ddf19c
+            act->act = BITMAP_ADD;
ddf19c
+            QSIMPLEQ_INSERT_TAIL(&actions, act, next);
ddf19c
+            add = true;
ddf19c
+            break;
ddf19c
+        case OPTION_REMOVE:
ddf19c
+            act = g_new0(ImgBitmapAction, 1);
ddf19c
+            act->act = BITMAP_REMOVE;
ddf19c
+            QSIMPLEQ_INSERT_TAIL(&actions, act, next);
ddf19c
+            break;
ddf19c
+        case OPTION_CLEAR:
ddf19c
+            act = g_new0(ImgBitmapAction, 1);
ddf19c
+            act->act = BITMAP_CLEAR;
ddf19c
+            QSIMPLEQ_INSERT_TAIL(&actions, act, next);
ddf19c
+            break;
ddf19c
+        case OPTION_ENABLE:
ddf19c
+            act = g_new0(ImgBitmapAction, 1);
ddf19c
+            act->act = BITMAP_ENABLE;
ddf19c
+            QSIMPLEQ_INSERT_TAIL(&actions, act, next);
ddf19c
+            break;
ddf19c
+        case OPTION_DISABLE:
ddf19c
+            act = g_new0(ImgBitmapAction, 1);
ddf19c
+            act->act = BITMAP_DISABLE;
ddf19c
+            QSIMPLEQ_INSERT_TAIL(&actions, act, next);
ddf19c
+            break;
ddf19c
+        case OPTION_MERGE:
ddf19c
+            act = g_new0(ImgBitmapAction, 1);
ddf19c
+            act->act = BITMAP_MERGE;
ddf19c
+            act->src = optarg;
ddf19c
+            QSIMPLEQ_INSERT_TAIL(&actions, act, next);
ddf19c
+            merge = true;
ddf19c
+            break;
ddf19c
+        case OPTION_OBJECT:
ddf19c
+            opts = qemu_opts_parse_noisily(&qemu_object_opts, optarg, true);
ddf19c
+            if (!opts) {
ddf19c
+                goto out;
ddf19c
+            }
ddf19c
+            break;
ddf19c
+        case OPTION_IMAGE_OPTS:
ddf19c
+            image_opts = true;
ddf19c
+            break;
ddf19c
+        }
ddf19c
+    }
ddf19c
+
ddf19c
+    if (qemu_opts_foreach(&qemu_object_opts,
ddf19c
+                          user_creatable_add_opts_foreach,
ddf19c
+                          qemu_img_object_print_help, &error_fatal)) {
ddf19c
+        goto out;
ddf19c
+    }
ddf19c
+
ddf19c
+    if (QSIMPLEQ_EMPTY(&actions)) {
ddf19c
+        error_report("Need at least one of --add, --remove, --clear, "
ddf19c
+                     "--enable, --disable, or --merge");
ddf19c
+        goto out;
ddf19c
+    }
ddf19c
+
ddf19c
+    if (granularity && !add) {
ddf19c
+        error_report("granularity only supported with --add");
ddf19c
+        goto out;
ddf19c
+    }
ddf19c
+    if (src_fmt && !src_filename) {
ddf19c
+        error_report("-F only supported with -b");
ddf19c
+        goto out;
ddf19c
+    }
ddf19c
+    if (src_filename && !merge) {
ddf19c
+        error_report("Merge bitmap source file only supported with "
ddf19c
+                     "--merge");
ddf19c
+        goto out;
ddf19c
+    }
ddf19c
+
ddf19c
+    if (optind != argc - 2) {
ddf19c
+        error_report("Expecting filename and bitmap name");
ddf19c
+        goto out;
ddf19c
+    }
ddf19c
+
ddf19c
+    filename = argv[optind];
ddf19c
+    bitmap = argv[optind + 1];
ddf19c
+
ddf19c
+    blk = img_open(image_opts, filename, fmt, BDRV_O_RDWR, false, false,
ddf19c
+                   false);
ddf19c
+    if (!blk) {
ddf19c
+        goto out;
ddf19c
+    }
ddf19c
+    bs = blk_bs(blk);
ddf19c
+    if (src_filename) {
ddf19c
+        src = img_open(false, src_filename, src_fmt, 0, false, false, false);
ddf19c
+        if (!src) {
ddf19c
+            goto out;
ddf19c
+        }
ddf19c
+        src_bs = blk_bs(src);
ddf19c
+    } else {
ddf19c
+        src_bs = bs;
ddf19c
+    }
ddf19c
+
ddf19c
+    QSIMPLEQ_FOREACH_SAFE(act, &actions, next, act_next) {
ddf19c
+        switch (act->act) {
ddf19c
+        case BITMAP_ADD:
ddf19c
+            qmp_block_dirty_bitmap_add(bs->node_name, bitmap,
ddf19c
+                                       !!granularity, granularity, true, true,
ddf19c
+                                       false, false, &err;;
ddf19c
+            op = "add";
ddf19c
+            break;
ddf19c
+        case BITMAP_REMOVE:
ddf19c
+            qmp_block_dirty_bitmap_remove(bs->node_name, bitmap, &err;;
ddf19c
+            op = "remove";
ddf19c
+            break;
ddf19c
+        case BITMAP_CLEAR:
ddf19c
+            qmp_block_dirty_bitmap_clear(bs->node_name, bitmap, &err;;
ddf19c
+            op = "clear";
ddf19c
+            break;
ddf19c
+        case BITMAP_ENABLE:
ddf19c
+            qmp_block_dirty_bitmap_enable(bs->node_name, bitmap, &err;;
ddf19c
+            op = "enable";
ddf19c
+            break;
ddf19c
+        case BITMAP_DISABLE:
ddf19c
+            qmp_block_dirty_bitmap_disable(bs->node_name, bitmap, &err;;
ddf19c
+            op = "disable";
ddf19c
+            break;
ddf19c
+        case BITMAP_MERGE: {
ddf19c
+            BlockDirtyBitmapMergeSource *merge_src;
ddf19c
+            BlockDirtyBitmapMergeSourceList *list;
ddf19c
+
ddf19c
+            merge_src = g_new0(BlockDirtyBitmapMergeSource, 1);
ddf19c
+            merge_src->type = QTYPE_QDICT;
ddf19c
+            merge_src->u.external.node = g_strdup(src_bs->node_name);
ddf19c
+            merge_src->u.external.name = g_strdup(act->src);
ddf19c
+            list = g_new0(BlockDirtyBitmapMergeSourceList, 1);
ddf19c
+            list->value = merge_src;
ddf19c
+            qmp_block_dirty_bitmap_merge(bs->node_name, bitmap, list, &err;;
ddf19c
+            qapi_free_BlockDirtyBitmapMergeSourceList(list);
ddf19c
+            op = "merge";
ddf19c
+            break;
ddf19c
+        }
ddf19c
+        default:
ddf19c
+            g_assert_not_reached();
ddf19c
+        }
ddf19c
+
ddf19c
+        if (err) {
ddf19c
+            error_reportf_err(err, "Operation %s on bitmap %s failed: ",
ddf19c
+                              op, bitmap);
ddf19c
+            goto out;
ddf19c
+        }
ddf19c
+        g_free(act);
ddf19c
+    }
ddf19c
+
ddf19c
+    ret = 0;
ddf19c
+
ddf19c
+ out:
ddf19c
+    blk_unref(src);
ddf19c
+    blk_unref(blk);
ddf19c
+    qemu_opts_del(opts);
ddf19c
+    return ret;
ddf19c
+}
ddf19c
+
ddf19c
 #define C_BS      01
ddf19c
 #define C_COUNT   02
ddf19c
 #define C_IF      04
ddf19c
diff --git a/qemu-img.texi b/qemu-img.texi
ddf19c
index b5156d6..abf2771 100644
ddf19c
--- a/qemu-img.texi
ddf19c
+++ b/qemu-img.texi
ddf19c
@@ -230,6 +230,33 @@ specified as well.
ddf19c
 For write tests, by default a buffer filled with zeros is written. This can be
ddf19c
 overridden with a pattern byte specified by @var{pattern}.
ddf19c
 
ddf19c
+@item bitmap (--merge @var{source} | --add | --remove | --clear | --enable | --disable)... [-b @var{source_file} [-F @var{source_fmt}]] [-g @var{granularity}] [--object @var{objectdef}] [--image-opts | -f @var{fmt}] @var{filename} @var{bitmap}
ddf19c
+
ddf19c
+Perform one or more modifications of the persistent bitmap @var{bitmap}
ddf19c
+in the disk image @var{filename}.  The various modifications are:
ddf19c
+
ddf19c
+@table @option
ddf19c
+@item add
ddf19c
+create @var{bitmap}, enabled to record future edits.
ddf19c
+@item remove
ddf19c
+remove @var{bitmap}.
ddf19c
+@item clear
ddf19c
+clear @var{bitmap}.
ddf19c
+@item enable
ddf19c
+change @var{bitmap} to start recording future edits.
ddf19c
+@item disable
ddf19c
+change @var{bitmap} to stop recording future edits.
ddf19c
+@item merge @var{source}
ddf19c
+merge the contents of the @var{source} bitmap into @var{bitmap}.
ddf19c
+@end table
ddf19c
+
ddf19c
+Additional options include @option{-g} which sets a non-default
ddf19c
+@var{granularity} for @option{--add}, and @option{-b} and @option{-F}
ddf19c
+which select an alternative source file for all @var{source} bitmaps used by
ddf19c
+@option{--merge}.
ddf19c
+
ddf19c
+To see what bitmaps are present in an image, use @code{qemu-img info}.
ddf19c
+
ddf19c
 @item check [--object @var{objectdef}] [--image-opts] [-q] [-f @var{fmt}] [--output=@var{ofmt}] [-r [leaks | all]] [-T @var{src_cache}] [-U] @var{filename}
ddf19c
 
ddf19c
 Perform a consistency check on the disk image @var{filename}. The command can
ddf19c
-- 
ddf19c
1.8.3.1
ddf19c