Commit 7f8466e2ce for qemu.org
commit 7f8466e2ce620e3c6a6e2f32d616367174d4dbe9
Author: Kevin Wolf <kwolf@redhat.com>
Date: Mon Apr 27 19:05:18 2026 +0200
qemu-io: Add 'aio_discard' command
Testing interactions between multiple requests that include discard
requests require that qemu-io can do the discard asynchronously, like it
already does for reads and writes. To this effect, add an 'aio_discard'
command.
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Message-ID: <20260427170520.101242-3-kwolf@redhat.com>
Reviewed-by: Denis V. Lunev <den@openvz.org>
Tested-by: Denis V. Lunev <den@openvz.org>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index f6d077908f..de4c1966fe 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -2218,6 +2218,120 @@ static int discard_f(BlockBackend *blk, int argc, char **argv)
return 0;
}
+static void aio_discard_help(void)
+{
+ printf(
+"\n"
+" asynchronously discards a range of bytes from the given offset\n"
+"\n"
+" Example:\n"
+" 'aio_discard 512 1k' - discards 1 kilobyte from 512 bytes into the file\n"
+"\n"
+" Discards a segment of the currently open file.\n"
+" -C, -- report statistics in a machine parsable format\n"
+" -q, -- quiet mode, do not show I/O statistics\n"
+" The discard is performed asynchronously and the aio_flush command must be\n"
+" used to ensure all outstanding aio requests have been completed.\n"
+" Note that due to its asynchronous nature, this command will be\n"
+" considered successful once the request is submitted, independently\n"
+" of potential I/O errors.\n"
+"\n");
+}
+
+static int aio_discard_f(BlockBackend *blk, int argc, char **argv);
+
+static const cmdinfo_t aio_discard_cmd = {
+ .name = "aio_discard",
+ .cfunc = aio_discard_f,
+ .perm = BLK_PERM_WRITE,
+ .argmin = 2,
+ .argmax = -1,
+ .args = "[-Cq] off len",
+ .oneline = "asynchronously discards a number of bytes",
+ .help = aio_discard_help,
+};
+
+static void aio_discard_done(void *opaque, int ret)
+{
+ struct aio_ctx *ctx = opaque;
+ struct timespec t2;
+
+ clock_gettime(CLOCK_MONOTONIC, &t2);
+
+ if (ret < 0) {
+ printf("aio_discard failed: %s\n", strerror(-ret));
+ block_acct_failed(blk_get_stats(ctx->blk), &ctx->acct);
+ goto out;
+ }
+
+ block_acct_done(blk_get_stats(ctx->blk), &ctx->acct);
+
+ if (ctx->qflag) {
+ goto out;
+ }
+
+ /* Finally, report back -- -C gives a parsable format */
+ t2 = tsub(t2, ctx->t1);
+ print_report("discarded ", &t2, ctx->offset, ctx->qiov.size,
+ ctx->qiov.size, 1, ctx->Cflag);
+out:
+ g_free(ctx);
+}
+
+static int aio_discard_f(BlockBackend *blk, int argc, char **argv)
+{
+ int c, ret;
+ int64_t count;
+ struct aio_ctx *ctx = g_new0(struct aio_ctx, 1);
+
+ ctx->blk = blk;
+
+ while ((c = getopt(argc, argv, "Cq")) != -1) {
+ switch (c) {
+ case 'C':
+ ctx->Cflag = true;
+ break;
+ case 'q':
+ ctx->qflag = true;
+ break;
+ default:
+ g_free(ctx);
+ qemuio_command_usage(&aio_discard_cmd);
+ return -EINVAL;
+ }
+ }
+
+ if (optind != argc - 2) {
+ g_free(ctx);
+ qemuio_command_usage(&aio_discard_cmd);
+ return -EINVAL;
+ }
+
+ ctx->offset = cvtnum(argv[optind]);
+ if (ctx->offset < 0) {
+ ret = ctx->offset;
+ print_cvtnum_err(ret, argv[optind]);
+ g_free(ctx);
+ return ret;
+ }
+ optind++;
+
+ count = cvtnum(argv[optind]);
+ if (count < 0) {
+ print_cvtnum_err(count, argv[optind]);
+ g_free(ctx);
+ return count;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &ctx->t1);
+ ctx->qiov.size = count;
+ block_acct_start(blk_get_stats(blk), &ctx->acct, ctx->qiov.size,
+ BLOCK_ACCT_UNMAP);
+ blk_aio_pdiscard(blk, ctx->offset, count, aio_discard_done, ctx);
+
+ return 0;
+}
+
static int alloc_f(BlockBackend *blk, int argc, char **argv)
{
BlockDriverState *bs = blk_bs(blk);
@@ -2800,6 +2914,7 @@ static void __attribute((constructor)) init_qemuio_commands(void)
qemuio_add_command(&length_cmd);
qemuio_add_command(&info_cmd);
qemuio_add_command(&discard_cmd);
+ qemuio_add_command(&aio_discard_cmd);
qemuio_add_command(&alloc_cmd);
qemuio_add_command(&map_cmd);
qemuio_add_command(&reopen_cmd);