This commit introduces a possibility to migrate open chardev
socket fd through migration channel without reconnecting.

For this, user should:
 - enable new migration capability local-char-socket
 - mark the socket by an option support-local-migration=true
 - on target add local-incoming=true option to the socket

Motivation for the API:

1. We don't want to migrate all sockets. For example, QMP-connection is
   bad candidate, as it is separate on source and target. So, we need
   @support-local-migration option to mark sockets, which we want to
   migrate (after this series, we'll want to migrate chardev used to
   connect with vhost-user-server).

2. Still, for remote migration, we can't migrate any sockets, so, we
   need a capability, to enable/disable the whole feature.

3. And finally, we need a sign for the socket to not open a connection
   on initialization, but wait for incoming migration. We can't use
   @support-local-migration option for it, as it may be enabled, but we
   are in incoming-remote migration. Also, we can't rely on the
   migration capability, as user is free to setup capabilities before or
   after chardev creation, and it would be a bad precedent to create
   relations here.

Signed-off-by: Vladimir Sementsov-Ogievskiy <vsement...@yandex-team.ru>
---
 chardev/char-socket.c         | 101 +++++++++++++++++++++++++++++++++-
 include/chardev/char-socket.h |   3 +
 migration/options.c           |   7 +++
 migration/options.h           |   1 +
 qapi/char.json                |  16 +++++-
 qapi/migration.json           |   8 ++-
 stubs/meson.build             |   1 +
 stubs/qemu_file.c             |  15 +++++
 stubs/vmstate.c               |   6 ++
 tests/qtest/meson.build       |   2 +-
 tests/unit/meson.build        |   4 +-
 11 files changed, 158 insertions(+), 6 deletions(-)
 create mode 100644 stubs/qemu_file.c

diff --git a/chardev/char-socket.c b/chardev/char-socket.c
index 1e8313915b..db6616e2f2 100644
--- a/chardev/char-socket.c
+++ b/chardev/char-socket.c
@@ -24,6 +24,7 @@
 
 #include "qemu/osdep.h"
 #include "chardev/char.h"
+#include "qapi-types-char.h"
 #include "io/channel-socket.h"
 #include "io/channel-websock.h"
 #include "qemu/error-report.h"
@@ -34,6 +35,10 @@
 #include "qapi/qapi-visit-sockets.h"
 #include "qemu/yank.h"
 #include "trace.h"
+#include "migration/vmstate.h"
+#include "migration/qemu-file.h"
+#include "migration/migration.h"
+#include "hw/vmstate-if.h"
 
 #include "chardev/char-io.h"
 #include "chardev/char-socket.h"
@@ -1118,6 +1123,7 @@ static void char_socket_finalize(Object *obj)
         object_unref(OBJECT(s->tls_creds));
     }
     g_free(s->tls_authz);
+    g_free(s->vmstate_name);
     if (s->registered_yank) {
         /*
          * In the chardev-change special-case, we shouldn't unregister the yank
@@ -1276,8 +1282,15 @@ static int qmp_chardev_open_socket_client(Chardev *chr,
 {
     SocketChardev *s = SOCKET_CHARDEV(chr);
 
+    s->reconnect_time_ms = reconnect_ms;
+
+    if (s->local_incoming) {
+        /* We'll get fd at migreation load. This field works once */
+        s->local_incoming = false;
+        return 0;
+    }
+
     if (reconnect_ms > 0) {
-        s->reconnect_time_ms = reconnect_ms;
         tcp_chr_connect_client_async(chr);
         return 0;
     } else {
@@ -1367,6 +1380,52 @@ static bool qmp_chardev_validate_socket(ChardevSocket 
*sock,
     return true;
 }
 
+static int char_socket_save(QEMUFile *f, void *opaque, size_t size,
+                           const VMStateField *field, JSONWriter *vmdesc)
+{
+    SocketChardev *s = opaque;
+
+    warn_report("%s", __func__);
+    return qemu_file_put_fd(f, s->sioc->fd);
+}
+
+static int char_socket_load(QEMUFile *f, void *opaque, size_t size,
+                           const VMStateField *field)
+{
+    Chardev *chr = opaque;
+
+    int fd = qemu_file_get_fd(f);
+    warn_report("%s %d", __func__, fd);
+    if (fd < 0) {
+        return fd;
+    }
+    return tcp_chr_add_client(chr, fd);
+}
+
+static bool char_socket_needed(void *opaque)
+{
+    SocketChardev *s = opaque;
+
+    return !!s->vmstate_name;
+}
+
+const VMStateDescription vmstate_char_socket = {
+    .name = "char_socket",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = char_socket_needed,
+    .fields = (VMStateField[]) {
+        {
+            .name = "fd",
+            .info = &(const VMStateInfo) {
+                .name = "fd",
+                .get = char_socket_load,
+                .put = char_socket_save,
+            },
+        },
+        VMSTATE_END_OF_LIST()
+    },
+};
 
 static void qmp_chardev_open_socket(Chardev *chr,
                                     ChardevBackend *backend,
@@ -1381,14 +1440,36 @@ static void qmp_chardev_open_socket(Chardev *chr,
     bool is_tn3270      = sock->has_tn3270  ? sock->tn3270  : false;
     bool is_waitconnect = sock->has_wait    ? sock->wait    : false;
     bool is_websock     = sock->has_websocket ? sock->websocket : false;
+    bool support_local_mig = sock->has_support_local_migration
+                                 ? sock->support_local_migration
+                                 : false;
+    bool local_incoming = sock->local_incoming;
     int64_t reconnect_ms = 0;
     SocketAddress *addr;
 
+    if (support_local_mig && is_listen) {
+        error_setg(errp,
+                   "local migration is not supported for listening sockets");
+        return;
+    }
+
+    if (support_local_mig && !(chr->label && chr->label[0])) {
+        error_setg(errp,
+                   "local migration is not supported for unnamed chardevs");
+        return;
+    }
+
     s->is_listen = is_listen;
     s->is_telnet = is_telnet;
     s->is_tn3270 = is_tn3270;
     s->is_websock = is_websock;
     s->do_nodelay = do_nodelay;
+    s->local_incoming = local_incoming;
+
+    if (support_local_mig) {
+        s->vmstate_name = g_strdup_printf("__yc-chardev-%s", chr->label);
+    }
+
     if (sock->tls_creds) {
         Object *creds;
         creds = object_resolve_path_component(
@@ -1448,6 +1529,7 @@ static void qmp_chardev_open_socket(Chardev *chr,
     update_disconnected_filename(s);
 
     if (s->is_listen) {
+        assert(!s->vmstate_name);
         if (qmp_chardev_open_socket_server(chr, is_telnet || is_tn3270,
                                            is_waitconnect, errp) < 0) {
             return;
@@ -1463,6 +1545,8 @@ static void qmp_chardev_open_socket(Chardev *chr,
             return;
         }
     }
+
+    vmstate_register(VMSTATE_IF(s), -1, &vmstate_char_socket, s);
 }
 
 static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
@@ -1581,9 +1665,20 @@ char_socket_get_connected(Object *obj, Error **errp)
     return s->state == TCP_CHARDEV_STATE_CONNECTED;
 }
 
+static char *
+char_socket_if_get_id(VMStateIf *obj)
+{
+    SocketChardev *s = SOCKET_CHARDEV(obj);
+
+    return s->vmstate_name;
+}
+
 static void char_socket_class_init(ObjectClass *oc, const void *data)
 {
     ChardevClass *cc = CHARDEV_CLASS(oc);
+    VMStateIfClass *vc = VMSTATE_IF_CLASS(oc);
+
+    vc->get_id = char_socket_if_get_id;
 
     cc->supports_yank = true;
 
@@ -1613,6 +1708,10 @@ static const TypeInfo char_socket_type_info = {
     .instance_size = sizeof(SocketChardev),
     .instance_finalize = char_socket_finalize,
     .class_init = char_socket_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_VMSTATE_IF },
+        { }
+    }
 };
 
 static void register_types(void)
diff --git a/include/chardev/char-socket.h b/include/chardev/char-socket.h
index d6d13ad37f..69b9609215 100644
--- a/include/chardev/char-socket.h
+++ b/include/chardev/char-socket.h
@@ -78,6 +78,9 @@ struct SocketChardev {
     bool connect_err_reported;
 
     QIOTask *connect_task;
+
+    char *vmstate_name;
+    bool local_incoming;
 };
 typedef struct SocketChardev SocketChardev;
 
diff --git a/migration/options.c b/migration/options.c
index 4e923a2e07..dffb6910f4 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -262,6 +262,13 @@ bool migrate_mapped_ram(void)
     return s->capabilities[MIGRATION_CAPABILITY_MAPPED_RAM];
 }
 
+bool migrate_local_char_socket(void)
+{
+    MigrationState *s = migrate_get_current();
+
+    return s->capabilities[MIGRATION_CAPABILITY_LOCAL_CHAR_SOCKET];
+}
+
 bool migrate_ignore_shared(void)
 {
     MigrationState *s = migrate_get_current();
diff --git a/migration/options.h b/migration/options.h
index 82d839709e..40971f0aa0 100644
--- a/migration/options.h
+++ b/migration/options.h
@@ -30,6 +30,7 @@ bool migrate_colo(void);
 bool migrate_dirty_bitmaps(void);
 bool migrate_events(void);
 bool migrate_mapped_ram(void);
+bool migrate_local_char_socket(void);
 bool migrate_ignore_shared(void);
 bool migrate_late_block_activate(void);
 bool migrate_multifd(void);
diff --git a/qapi/char.json b/qapi/char.json
index f0a53f742c..5b535c196a 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -280,11 +280,23 @@
 #     mutually exclusive with @reconnect.
 #     (default: 0) (Since: 9.2)
 #
+# @support-local-migration: The socket open file descriptor will
+#     migrate if this field is true and local-char-socket migration
+#     capability enabled (default: false) (Since: 10.2)
+#
+# @local-incoming: Do load open file descriptor for the socket
+#     on incoming migration. May be used only if QEMU is started
+#     for incoming migration and only together with local-char-socket
+#     migration capability (default: false) (Since: 10.2)
+#
 # Features:
 #
 # @deprecated: Member @reconnect is deprecated.  Use @reconnect-ms
 #     instead.
 #
+# @unstable: Members @support-local-migration and @local-incoming
+#            are experimental
+#
 # Since: 1.4
 ##
 { 'struct': 'ChardevSocket',
@@ -298,7 +310,9 @@
             '*tn3270': 'bool',
             '*websocket': 'bool',
             '*reconnect': { 'type': 'int', 'features': [ 'deprecated' ] },
-            '*reconnect-ms': 'int' },
+            '*reconnect-ms': 'int',
+            '*support-local-migration': { 'type': 'bool', 'features': [ 
'unstable' ] },
+            '*local-incoming': { 'type': 'bool', 'features': [ 'unstable' ] } 
},
   'base': 'ChardevCommon' }
 
 ##
diff --git a/qapi/migration.json b/qapi/migration.json
index 2387c21e9c..4f282d168e 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -517,6 +517,11 @@
 #     each RAM page.  Requires a migration URI that supports seeking,
 #     such as a file.  (since 9.0)
 #
+# @local-char-socket: Migrate socket chardevs open file descriptors.
+#     Only may be used when migration channel is unix socket. Only
+#     involves socket chardevs with "support-local-migration" option
+#     enabled.  (since 10.2)
+#
 # Features:
 #
 # @unstable: Members @x-colo and @x-ignore-shared are experimental.
@@ -536,7 +541,8 @@
            { 'name': 'x-ignore-shared', 'features': [ 'unstable' ] },
            'validate-uuid', 'background-snapshot',
            'zero-copy-send', 'postcopy-preempt', 'switchover-ack',
-           'dirty-limit', 'mapped-ram'] }
+           'dirty-limit', 'mapped-ram',
+           { 'name': 'local-char-socket', 'features': [ 'unstable' ] } ] }
 
 ##
 # @MigrationCapabilityStatus:
diff --git a/stubs/meson.build b/stubs/meson.build
index cef046e685..7855483639 100644
--- a/stubs/meson.build
+++ b/stubs/meson.build
@@ -50,6 +50,7 @@ if have_block or have_user
   stub_ss.add(files('qtest.c'))
   stub_ss.add(files('vm-stop.c'))
   stub_ss.add(files('vmstate.c'))
+  stub_ss.add(files('qemu_file.c'))
 endif
 
 if have_user
diff --git a/stubs/qemu_file.c b/stubs/qemu_file.c
new file mode 100644
index 0000000000..854d4c13cd
--- /dev/null
+++ b/stubs/qemu_file.c
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "migration/qemu-file.h"
+
+
+int qemu_file_get_fd(QEMUFile *f)
+{
+    return -1;
+}
+
+int qemu_file_put_fd(QEMUFile *f, int fd)
+{
+    return -1;
+}
diff --git a/stubs/vmstate.c b/stubs/vmstate.c
index c190762d7c..f345edf3c9 100644
--- a/stubs/vmstate.c
+++ b/stubs/vmstate.c
@@ -2,6 +2,7 @@
 #include "migration/vmstate.h"
 #include "qapi/qapi-types-migration.h"
 #include "migration/client-options.h"
+#include "migration/options.h"
 
 int vmstate_register_with_alias_id(VMStateIf *obj,
                                    uint32_t instance_id,
@@ -28,3 +29,8 @@ MigMode migrate_mode(void)
 {
     return MIG_MODE_NORMAL;
 }
+
+bool migrate_local_char_socket(void)
+{
+    return false;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 669d07c06b..8be0c1dc7c 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -381,7 +381,7 @@ qtests = {
   'pxe-test': files('boot-sector.c'),
   'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c',
                           'pnv-xive2-nvpg_bar.c'),
-  'qos-test': [chardev, io, qos_test_ss.apply({}).sources()],
+  'qos-test': [chardev, io, qos_test_ss.apply({}).sources(), 
'../../hw/core/vmstate-if.c'],
   'tpm-crb-swtpm-test': [io, tpmemu_files],
   'tpm-crb-test': [io, tpmemu_files],
   'tpm-tis-swtpm-test': [io, tpmemu_files, 'tpm-tis-util.c'],
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index d5248ae51d..b96f4dcabe 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -139,7 +139,7 @@ if have_system
     'test-bufferiszero': [],
     'test-smp-parse': [qom, meson.project_source_root() / 
'hw/core/machine-smp.c'],
     'test-vmstate': [migration, io],
-    'test-yank': ['socket-helpers.c', qom, io, chardev]
+    'test-yank': ['socket-helpers.c', qom, io, chardev, 
'../../hw/core/vmstate-if.c']
   }
   if config_host_data.get('CONFIG_INOTIFY1')
     tests += {'test-util-filemonitor': []}
@@ -151,7 +151,7 @@ if have_system
   if not get_option('tsan')
     if host_os != 'windows'
         tests += {
-          'test-char': ['socket-helpers.c', qom, io, chardev]
+          'test-char': ['socket-helpers.c', qom, io, chardev, 
'../../hw/core/vmstate-if.c']
         }
     endif
 
-- 
2.48.1


Reply via email to