From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Fiona Ebner <f.ebner@proxmox.com>
Date: Thu, 13 Oct 2022 11:33:50 +0200
Subject: [PATCH] PVE: add IOChannel implementation for savevm-async

based on migration/channel-block.c and the implementation that was
present in migration/savevm-async.c before QEMU 7.1.

Passes along read/write requests to the given BlockBackend, while
ensuring that a read request going beyond the end results in a
graceful short read.

Additionally, allows tracking the current position from the outside
(intended to be used for progress tracking).

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 migration/channel-savevm-async.c | 183 +++++++++++++++++++++++++++++++
 migration/channel-savevm-async.h |  51 +++++++++
 migration/meson.build            |   1 +
 3 files changed, 235 insertions(+)
 create mode 100644 migration/channel-savevm-async.c
 create mode 100644 migration/channel-savevm-async.h

diff --git a/migration/channel-savevm-async.c b/migration/channel-savevm-async.c
new file mode 100644
index 0000000000..aab081ce07
--- /dev/null
+++ b/migration/channel-savevm-async.c
@@ -0,0 +1,183 @@
+/*
+ * QIO Channel implementation to be used by savevm-async QMP calls
+ */
+#include "qemu/osdep.h"
+#include "migration/channel-savevm-async.h"
+#include "qapi/error.h"
+#include "sysemu/block-backend.h"
+#include "trace.h"
+
+QIOChannelSavevmAsync *
+qio_channel_savevm_async_new(BlockBackend *be, size_t *bs_pos)
+{
+    QIOChannelSavevmAsync *ioc;
+
+    ioc = QIO_CHANNEL_SAVEVM_ASYNC(object_new(TYPE_QIO_CHANNEL_SAVEVM_ASYNC));
+
+    bdrv_ref(blk_bs(be));
+    ioc->be = be;
+    ioc->bs_pos = bs_pos;
+
+    return ioc;
+}
+
+
+static void
+qio_channel_savevm_async_finalize(Object *obj)
+{
+    QIOChannelSavevmAsync *ioc = QIO_CHANNEL_SAVEVM_ASYNC(obj);
+
+    if (ioc->be) {
+        bdrv_unref(blk_bs(ioc->be));
+        ioc->be = NULL;
+    }
+    ioc->bs_pos = NULL;
+}
+
+
+static ssize_t
+qio_channel_savevm_async_readv(QIOChannel *ioc,
+                               const struct iovec *iov,
+                               size_t niov,
+                               int **fds,
+                               size_t *nfds,
+                               int flags,
+                               Error **errp)
+{
+    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
+    BlockBackend *be = saioc->be;
+    int64_t maxlen = blk_getlength(be);
+    QEMUIOVector qiov;
+    size_t size;
+    int ret;
+
+    qemu_iovec_init_external(&qiov, (struct iovec *)iov, niov);
+
+    if (*saioc->bs_pos >= maxlen) {
+        error_setg(errp, "cannot read beyond maxlen");
+        return -1;
+    }
+
+    if (maxlen - *saioc->bs_pos < qiov.size) {
+        size = maxlen - *saioc->bs_pos;
+    } else {
+        size = qiov.size;
+    }
+
+    // returns 0 on success
+    ret = blk_preadv(be, *saioc->bs_pos, size, &qiov, 0);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "blk_preadv failed");
+        return -1;
+    }
+
+    *saioc->bs_pos += size;
+    return size;
+}
+
+
+static ssize_t
+qio_channel_savevm_async_writev(QIOChannel *ioc,
+                                const struct iovec *iov,
+                                size_t niov,
+                                int *fds,
+                                size_t nfds,
+                                int flags,
+                                Error **errp)
+{
+    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
+    BlockBackend *be = saioc->be;
+    QEMUIOVector qiov;
+    int ret;
+
+    qemu_iovec_init_external(&qiov, (struct iovec *)iov, niov);
+
+    if (qemu_in_coroutine()) {
+        ret = blk_co_pwritev(be, *saioc->bs_pos, qiov.size, &qiov, 0);
+        aio_wait_kick();
+    } else {
+        ret = blk_pwritev(be, *saioc->bs_pos, qiov.size, &qiov, 0);
+    }
+
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "blk(_co)_pwritev failed");
+        return -1;
+    }
+
+    *saioc->bs_pos += qiov.size;
+    return qiov.size;
+}
+
+
+static int
+qio_channel_savevm_async_set_blocking(QIOChannel *ioc,
+                                      bool enabled,
+                                      Error **errp)
+{
+    if (!enabled) {
+        error_setg(errp, "Non-blocking mode not supported for savevm-async");
+        return -1;
+    }
+    return 0;
+}
+
+
+static int
+qio_channel_savevm_async_close(QIOChannel *ioc,
+                               Error **errp)
+{
+    QIOChannelSavevmAsync *saioc = QIO_CHANNEL_SAVEVM_ASYNC(ioc);
+    int rv = bdrv_flush(blk_bs(saioc->be));
+
+    if (rv < 0) {
+        error_setg_errno(errp, -rv, "Unable to flush VMState");
+        return -1;
+    }
+
+    bdrv_unref(blk_bs(saioc->be));
+    saioc->be = NULL;
+    saioc->bs_pos = NULL;
+
+    return 0;
+}
+
+
+static void
+qio_channel_savevm_async_set_aio_fd_handler(QIOChannel *ioc,
+                                            AioContext *ctx,
+                                            IOHandler *io_read,
+                                            IOHandler *io_write,
+                                            void *opaque)
+{
+    // if channel-block starts doing something, check if this needs adaptation
+}
+
+
+static void
+qio_channel_savevm_async_class_init(ObjectClass *klass,
+                             void *class_data G_GNUC_UNUSED)
+{
+    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
+
+    ioc_klass->io_writev = qio_channel_savevm_async_writev;
+    ioc_klass->io_readv = qio_channel_savevm_async_readv;
+    ioc_klass->io_set_blocking = qio_channel_savevm_async_set_blocking;
+    ioc_klass->io_close = qio_channel_savevm_async_close;
+    ioc_klass->io_set_aio_fd_handler = qio_channel_savevm_async_set_aio_fd_handler;
+}
+
+static const TypeInfo qio_channel_savevm_async_info = {
+    .parent = TYPE_QIO_CHANNEL,
+    .name = TYPE_QIO_CHANNEL_SAVEVM_ASYNC,
+    .instance_size = sizeof(QIOChannelSavevmAsync),
+    .instance_finalize = qio_channel_savevm_async_finalize,
+    .class_init = qio_channel_savevm_async_class_init,
+};
+
+static void
+qio_channel_savevm_async_register_types(void)
+{
+    type_register_static(&qio_channel_savevm_async_info);
+}
+
+type_init(qio_channel_savevm_async_register_types);
diff --git a/migration/channel-savevm-async.h b/migration/channel-savevm-async.h
new file mode 100644
index 0000000000..17ae2cb261
--- /dev/null
+++ b/migration/channel-savevm-async.h
@@ -0,0 +1,51 @@
+/*
+ * QEMU I/O channels driver for savevm-async.c
+ *
+ * Copyright (c) 2022 Proxmox Server Solutions
+ *
+ * Authors:
+ *  Fiona Ebner (f.ebner@proxmox.com)
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef QIO_CHANNEL_SAVEVM_ASYNC_H
+#define QIO_CHANNEL_SAVEVM_ASYNC_H
+
+#include "io/channel.h"
+#include "qom/object.h"
+
+#define TYPE_QIO_CHANNEL_SAVEVM_ASYNC "qio-channel-savevm-async"
+OBJECT_DECLARE_SIMPLE_TYPE(QIOChannelSavevmAsync, QIO_CHANNEL_SAVEVM_ASYNC)
+
+
+/**
+ * QIOChannelSavevmAsync:
+ *
+ * The QIOChannelBlock object provides a channel implementation that is able to
+ * perform I/O on any BlockBackend whose BlockDriverState directly contains a
+ * VMState (as opposed to indirectly, like qcow2). It allows tracking the
+ * current position from the outside.
+ */
+struct QIOChannelSavevmAsync {
+    QIOChannel parent;
+    BlockBackend *be;
+    size_t *bs_pos;
+};
+
+
+/**
+ * qio_channel_savevm_async_new:
+ * @be: the block backend
+ * @bs_pos: used to keep track of the IOChannels current position
+ *
+ * Create a new IO channel object that can perform I/O on a BlockBackend object
+ * whose BlockDriverState directly contains a VMState.
+ *
+ * Returns: the new channel object
+ */
+QIOChannelSavevmAsync *
+qio_channel_savevm_async_new(BlockBackend *be, size_t *bs_pos);
+
+#endif /* QIO_CHANNEL_SAVEVM_ASYNC_H */
diff --git a/migration/meson.build b/migration/meson.build
index 0d1bb9f96e..8a142fc7a9 100644
--- a/migration/meson.build
+++ b/migration/meson.build
@@ -13,6 +13,7 @@ softmmu_ss.add(files(
   'block-dirty-bitmap.c',
   'channel.c',
   'channel-block.c',
+  'channel-savevm-async.c',
   'colo-failover.c',
   'colo.c',
   'exec.c',
