qemu-img: add sub-command --remove-all to 'qemu-img bitmap'

From time to time it is needed to remove all bitmaps from the image.
Before this patch the process is not very convenient. One should
perform
    qemu-img info
and parse the output to obtain all names. After that one should
sequentially call
    qemu-img bitmap --remove
for each present bitmap.

The patch adds --remove-all sub-command to 'qemu-img bitmap'. The
new sub-command also composes with other bitmap actions in the same
invocation, so a common "wipe and recreate" workflow can be expressed
as
    qemu-img bitmap --remove-all --add NEW FILE
instead of enumerating existing bitmaps, removing them one by one,
and only then adding the fresh one.

Cc: Kevin Wolf <kwolf@redhat.com>
Cc: Hanna Czenczek <hreitz@redhat.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
Message-ID: <20260520235952.500250-1-den@openvz.org>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Denis V. Lunev
2026-05-21 01:59:52 +02:00
committed by Kevin Wolf
parent aeea0c2804
commit 7e8cc6c886
4 changed files with 125 additions and 10 deletions

View File

@@ -301,15 +301,19 @@ Command description:
For write tests, by default a buffer filled with zeros is written. This can be For write tests, by default a buffer filled with zeros is written. This can be
overridden with a pattern byte specified by *PATTERN*. overridden with a pattern byte specified by *PATTERN*.
.. option:: bitmap (--merge SOURCE | --add | --remove | --clear | --enable | --disable)... [-b SOURCE_FILE [-F SOURCE_FMT]] [-g GRANULARITY] [--object OBJECTDEF] [--image-opts | -f FMT] FILENAME BITMAP .. option:: bitmap (--merge SOURCE | --add | --remove | --remove-all | --clear | --enable | --disable)... [-b SOURCE_FILE [-F SOURCE_FMT]] [-g GRANULARITY] [--object OBJECTDEF] [--image-opts | -f FMT] FILENAME [BITMAP]
Perform one or more modifications of the persistent bitmap *BITMAP* Perform one or more modifications of persistent bitmaps in the disk
in the disk image *FILENAME*. The various modifications are: image *FILENAME*. Most operations require *BITMAP* to be specified;
``--remove-all`` operates on all bitmaps and does not take *BITMAP*.
The various modifications are:
``--add`` to create *BITMAP*, enabled to record future edits. ``--add`` to create *BITMAP*, enabled to record future edits.
``--remove`` to remove *BITMAP*. ``--remove`` to remove *BITMAP*.
``--remove-all`` to remove all bitmaps.
``--clear`` to clear *BITMAP*. ``--clear`` to clear *BITMAP*.
``--enable`` to change *BITMAP* to start recording future edits. ``--enable`` to change *BITMAP* to start recording future edits.

View File

@@ -87,6 +87,7 @@ enum {
OPTION_FORCE = 276, OPTION_FORCE = 276,
OPTION_SKIP_BROKEN = 277, OPTION_SKIP_BROKEN = 277,
OPTION_LIMITS = 278, OPTION_LIMITS = 278,
OPTION_REMOVE_ALL = 279,
}; };
typedef enum OutputFormat { typedef enum OutputFormat {
@@ -5018,6 +5019,7 @@ enum ImgBitmapAct {
BITMAP_ENABLE, BITMAP_ENABLE,
BITMAP_DISABLE, BITMAP_DISABLE,
BITMAP_MERGE, BITMAP_MERGE,
BITMAP_REMOVE_ALL,
}; };
typedef struct ImgBitmapAction { typedef struct ImgBitmapAction {
enum ImgBitmapAct act; enum ImgBitmapAct act;
@@ -5036,7 +5038,7 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
BlockDriverState *bs = NULL, *src_bs = NULL; BlockDriverState *bs = NULL, *src_bs = NULL;
bool image_opts = false; bool image_opts = false;
int64_t granularity = 0; int64_t granularity = 0;
bool add = false, merge = false; bool add = false, merge = false, need_bitmap_name = false;
QSIMPLEQ_HEAD(, ImgBitmapAction) actions; QSIMPLEQ_HEAD(, ImgBitmapAction) actions;
ImgBitmapAction *act, *act_next; ImgBitmapAction *act, *act_next;
const char *op; const char *op;
@@ -5052,6 +5054,7 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
{"add", no_argument, 0, OPTION_ADD}, {"add", no_argument, 0, OPTION_ADD},
{"granularity", required_argument, 0, 'g'}, {"granularity", required_argument, 0, 'g'},
{"remove", no_argument, 0, OPTION_REMOVE}, {"remove", no_argument, 0, OPTION_REMOVE},
{"remove-all", no_argument, 0, OPTION_REMOVE_ALL},
{"clear", no_argument, 0, OPTION_CLEAR}, {"clear", no_argument, 0, OPTION_CLEAR},
{"enable", no_argument, 0, OPTION_ENABLE}, {"enable", no_argument, 0, OPTION_ENABLE},
{"disable", no_argument, 0, OPTION_DISABLE}, {"disable", no_argument, 0, OPTION_DISABLE},
@@ -5070,9 +5073,9 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
switch (c) { switch (c) {
case 'h': case 'h':
cmd_help(ccmd, "[-f FMT | --image-opts]\n" cmd_help(ccmd, "[-f FMT | --image-opts]\n"
" ( --add [-g SIZE] | --remove | --clear | --enable | --disable |\n" " ( --add [-g SIZE] | --remove | --remove-all | --clear | --enable |\n"
" --merge SOURCE [-b SRC_FILE [-F SRC_FMT]] )..\n" " --disable | --merge SOURCE [-b SRC_FILE [-F SRC_FMT]] )..\n"
" [--object OBJDEF] FILE BITMAP\n" " [--object OBJDEF] FILE [BITMAP]\n"
, ,
" -f, --format FMT\n" " -f, --format FMT\n"
" specify FILE format explicitly (default: probing is used)\n" " specify FILE format explicitly (default: probing is used)\n"
@@ -5086,6 +5089,8 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
" with optional multiplier suffix (in powers of 1024)\n" " with optional multiplier suffix (in powers of 1024)\n"
" --remove\n" " --remove\n"
" removes BITMAP from FILE\n" " removes BITMAP from FILE\n"
" --remove-all\n"
" removes all bitmaps from FILE\n"
" --clear\n" " --clear\n"
" clears BITMAP in FILE\n" " clears BITMAP in FILE\n"
" --enable, --disable\n" " --enable, --disable\n"
@@ -5116,6 +5121,7 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
act->act = BITMAP_ADD; act->act = BITMAP_ADD;
QSIMPLEQ_INSERT_TAIL(&actions, act, next); QSIMPLEQ_INSERT_TAIL(&actions, act, next);
add = true; add = true;
need_bitmap_name = true;
break; break;
case 'g': case 'g':
granularity = cvtnum("granularity", optarg, true); granularity = cvtnum("granularity", optarg, true);
@@ -5127,21 +5133,30 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
act = g_new0(ImgBitmapAction, 1); act = g_new0(ImgBitmapAction, 1);
act->act = BITMAP_REMOVE; act->act = BITMAP_REMOVE;
QSIMPLEQ_INSERT_TAIL(&actions, act, next); QSIMPLEQ_INSERT_TAIL(&actions, act, next);
need_bitmap_name = true;
break;
case OPTION_REMOVE_ALL:
act = g_new0(ImgBitmapAction, 1);
act->act = BITMAP_REMOVE_ALL;
QSIMPLEQ_INSERT_TAIL(&actions, act, next);
break; break;
case OPTION_CLEAR: case OPTION_CLEAR:
act = g_new0(ImgBitmapAction, 1); act = g_new0(ImgBitmapAction, 1);
act->act = BITMAP_CLEAR; act->act = BITMAP_CLEAR;
QSIMPLEQ_INSERT_TAIL(&actions, act, next); QSIMPLEQ_INSERT_TAIL(&actions, act, next);
need_bitmap_name = true;
break; break;
case OPTION_ENABLE: case OPTION_ENABLE:
act = g_new0(ImgBitmapAction, 1); act = g_new0(ImgBitmapAction, 1);
act->act = BITMAP_ENABLE; act->act = BITMAP_ENABLE;
QSIMPLEQ_INSERT_TAIL(&actions, act, next); QSIMPLEQ_INSERT_TAIL(&actions, act, next);
need_bitmap_name = true;
break; break;
case OPTION_DISABLE: case OPTION_DISABLE:
act = g_new0(ImgBitmapAction, 1); act = g_new0(ImgBitmapAction, 1);
act->act = BITMAP_DISABLE; act->act = BITMAP_DISABLE;
QSIMPLEQ_INSERT_TAIL(&actions, act, next); QSIMPLEQ_INSERT_TAIL(&actions, act, next);
need_bitmap_name = true;
break; break;
case OPTION_MERGE: case OPTION_MERGE:
act = g_new0(ImgBitmapAction, 1); act = g_new0(ImgBitmapAction, 1);
@@ -5149,6 +5164,7 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
act->src = optarg; act->src = optarg;
QSIMPLEQ_INSERT_TAIL(&actions, act, next); QSIMPLEQ_INSERT_TAIL(&actions, act, next);
merge = true; merge = true;
need_bitmap_name = true;
break; break;
case 'b': case 'b':
src_filename = optarg; src_filename = optarg;
@@ -5165,8 +5181,8 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
} }
if (QSIMPLEQ_EMPTY(&actions)) { if (QSIMPLEQ_EMPTY(&actions)) {
error_report("Need at least one of --add, --remove, --clear, " error_report("Need at least one of --add, --remove, --remove-all, "
"--enable, --disable, or --merge"); "--clear, --enable, --disable, or --merge");
goto out; goto out;
} }
@@ -5184,11 +5200,22 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
goto out; goto out;
} }
if (optind != argc - 2) { if (need_bitmap_name && optind != argc - 2) {
error_report("Expecting filename and bitmap name"); error_report("Expecting filename and bitmap name");
goto out; goto out;
} }
/*
* Every action other than --remove-all sets need_bitmap_name, so
* !need_bitmap_name means the only action(s) given were --remove-all
* and the BITMAP positional argument must be omitted. Combinations
* like '--remove-all --add foo' remain valid via the branch above.
*/
if (!need_bitmap_name && optind != argc - 1) {
error_report("Expecting filename");
goto out;
}
filename = argv[optind]; filename = argv[optind];
bitmap = argv[optind + 1]; bitmap = argv[optind + 1];
@@ -5225,6 +5252,20 @@ static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv)
qmp_block_dirty_bitmap_remove(bs->node_name, bitmap, &err); qmp_block_dirty_bitmap_remove(bs->node_name, bitmap, &err);
op = "remove"; op = "remove";
break; break;
case BITMAP_REMOVE_ALL: {
BdrvDirtyBitmap *bm;
while ((bm = bdrv_dirty_bitmap_first(bs))) {
const char *name = bdrv_dirty_bitmap_name(bm);
qmp_block_dirty_bitmap_remove(bs->node_name, name, &err);
if (err) {
/* Save name for proper error reporting */
bitmap = name;
break;
}
}
op = "remove-all";
break;
}
case BITMAP_CLEAR: case BITMAP_CLEAR:
qmp_block_dirty_bitmap_clear(bs->node_name, bitmap, &err); qmp_block_dirty_bitmap_clear(bs->node_name, bitmap, &err);
op = "clear"; op = "clear";

View File

@@ -161,6 +161,30 @@ $QEMU_IMG convert --bitmaps -O qcow2 "$TEST_IMG" "$TEST_IMG.copy"
TEST_IMG="$TEST_IMG.copy" _img_info --format-specific \ TEST_IMG="$TEST_IMG.copy" _img_info --format-specific \
| _filter_irrelevant_img_info | _filter_irrelevant_img_info
echo
echo "=== Check --remove-all ==="
echo
# Start from a fresh image so prior state does not bleed into the assertions
_rm_test_img "$TEST_IMG"
_make_test_img 10M
$QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" b0
$QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" b1
$QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" b2
_img_info --format-specific | _filter_irrelevant_img_info
# Sweep every bitmap in a single command, no BITMAP positional
echo
$QEMU_IMG bitmap --remove-all -f $IMGFMT "$TEST_IMG"
_img_info --format-specific | _filter_irrelevant_img_info
# Wipe + recreate in one invocation: only 'fresh' should remain
echo
$QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" b0
$QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" b1
$QEMU_IMG bitmap --remove-all --add -f $IMGFMT "$TEST_IMG" fresh
_img_info --format-specific | _filter_irrelevant_img_info
# success, all done # success, all done
echo '*** done' echo '*** done'
rm -f $seq.full rm -f $seq.full

View File

@@ -180,4 +180,50 @@ Format specific information:
name: b2 name: b2
granularity: 65536 granularity: 65536
corrupt: false corrupt: false
=== Check --remove-all ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=10485760
image: TEST_DIR/t.IMGFMT
file format: IMGFMT
virtual size: 10 MiB (10485760 bytes)
cluster_size: 65536
Format specific information:
bitmaps:
[0]:
flags:
[0]: auto
name: b0
granularity: 65536
[1]:
flags:
[0]: auto
name: b1
granularity: 65536
[2]:
flags:
[0]: auto
name: b2
granularity: 65536
corrupt: false
image: TEST_DIR/t.IMGFMT
file format: IMGFMT
virtual size: 10 MiB (10485760 bytes)
cluster_size: 65536
Format specific information:
corrupt: false
image: TEST_DIR/t.IMGFMT
file format: IMGFMT
virtual size: 10 MiB (10485760 bytes)
cluster_size: 65536
Format specific information:
bitmaps:
[0]:
flags:
[0]: auto
name: fresh
granularity: 65536
corrupt: false
*** done *** done