summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS12
-rw-r--r--Makefile.objs1
-rw-r--r--backends/Makefile.objs4
-rw-r--r--backends/dbus-vmstate.c510
-rw-r--r--backends/trace-events7
-rwxr-xr-xconfigure7
-rw-r--r--docs/interop/dbus-vmstate.rst74
-rw-r--r--docs/interop/dbus.rst110
-rw-r--r--docs/interop/index.rst2
-rw-r--r--hw/block/onenand.c2
-rw-r--r--hw/core/Makefile.objs1
-rw-r--r--hw/core/qdev.c21
-rw-r--r--hw/core/vmstate-if.c23
-rw-r--r--hw/ide/cmd646.c2
-rw-r--r--hw/ide/isa.c2
-rw-r--r--hw/ide/piix.c2
-rw-r--r--hw/ide/via.c2
-rw-r--r--hw/misc/max111x.c2
-rw-r--r--hw/net/eepro100.c4
-rw-r--r--hw/net/virtio-net.c3
-rw-r--r--hw/nvram/eeprom93xx.c4
-rw-r--r--hw/ppc/spapr_drc.c9
-rw-r--r--hw/ppc/spapr_iommu.c4
-rw-r--r--hw/s390x/s390-skeys.c2
-rw-r--r--include/hw/vmstate-if.h40
-rw-r--r--include/migration/register.h4
-rw-r--r--include/migration/vmstate.h10
-rw-r--r--include/qemu/dbus.h19
-rw-r--r--migration/savevm.c20
-rw-r--r--stubs/vmstate.c4
-rw-r--r--tests/Makefile.include25
-rwxr-xr-xtests/dbus-vmstate-daemon.sh95
-rw-r--r--tests/dbus-vmstate-test.c382
-rw-r--r--tests/dbus-vmstate1.xml12
-rw-r--r--tests/docker/dockerfiles/centos7.docker1
-rw-r--r--tests/docker/dockerfiles/debian10.docker1
-rw-r--r--tests/docker/dockerfiles/fedora.docker1
-rw-r--r--tests/docker/dockerfiles/ubuntu.docker1
-rw-r--r--tests/migration-helpers.c167
-rw-r--r--tests/migration-helpers.h37
-rw-r--r--tests/migration-test.c176
-rw-r--r--util/Makefile.objs3
-rw-r--r--util/dbus.c57
43 files changed, 1660 insertions, 205 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 8571327881..cd2dc137a3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2196,6 +2196,8 @@ Migration
 M: Juan Quintela <quintela@redhat.com>
 M: Dr. David Alan Gilbert <dgilbert@redhat.com>
 S: Maintained
+F: hw/core/vmstate-if.c
+F: include/hw/vmstate-if.h
 F: include/migration/
 F: migration/
 F: scripts/vmstate-static-checker.py
@@ -2204,6 +2206,16 @@ F: tests/migration-test.c
 F: docs/devel/migration.rst
 F: qapi/migration.json
 
+D-Bus
+M: Marc-André Lureau <marcandre.lureau@redhat.com>
+S: Maintained
+F: backends/dbus-vmstate.c
+F: tests/dbus-vmstate*
+F: util/dbus.c
+F: include/qemu/dbus.h
+F: docs/interop/dbus.rst
+F: docs/interop/dbus-vmstate.rst
+
 Seccomp
 M: Eduardo Otubo <otubo@redhat.com>
 S: Supported
diff --git a/Makefile.objs b/Makefile.objs
index 02bf5ce11d..7c1e50f9d6 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -128,6 +128,7 @@ vhost-user-gpu-obj-y = contrib/vhost-user-gpu/
 trace-events-subdirs =
 trace-events-subdirs += accel/kvm
 trace-events-subdirs += accel/tcg
+trace-events-subdirs += backends
 trace-events-subdirs += crypto
 trace-events-subdirs += monitor
 ifeq ($(CONFIG_USER_ONLY),y)
diff --git a/backends/Makefile.objs b/backends/Makefile.objs
index f0691116e8..28a847cd57 100644
--- a/backends/Makefile.objs
+++ b/backends/Makefile.objs
@@ -17,3 +17,7 @@ endif
 common-obj-$(call land,$(CONFIG_VHOST_USER),$(CONFIG_VIRTIO)) += vhost-user.o
 
 common-obj-$(CONFIG_LINUX) += hostmem-memfd.o
+
+common-obj-$(CONFIG_GIO) += dbus-vmstate.o
+dbus-vmstate.o-cflags = $(GIO_CFLAGS)
+dbus-vmstate.o-libs = $(GIO_LIBS)
diff --git a/backends/dbus-vmstate.c b/backends/dbus-vmstate.c
new file mode 100644
index 0000000000..56b482a7d6
--- /dev/null
+++ b/backends/dbus-vmstate.c
@@ -0,0 +1,510 @@
+/*
+ * QEMU dbus-vmstate
+ *
+ * Copyright (C) 2019 Red Hat Inc
+ *
+ * Authors:
+ *  Marc-André Lureau <marcandre.lureau@redhat.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.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/dbus.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "qom/object_interfaces.h"
+#include "qapi/qmp/qerror.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+typedef struct DBusVMState DBusVMState;
+typedef struct DBusVMStateClass DBusVMStateClass;
+
+#define TYPE_DBUS_VMSTATE "dbus-vmstate"
+#define DBUS_VMSTATE(obj)                                \
+    OBJECT_CHECK(DBusVMState, (obj), TYPE_DBUS_VMSTATE)
+#define DBUS_VMSTATE_GET_CLASS(obj)                              \
+    OBJECT_GET_CLASS(DBusVMStateClass, (obj), TYPE_DBUS_VMSTATE)
+#define DBUS_VMSTATE_CLASS(klass)                                    \
+    OBJECT_CLASS_CHECK(DBusVMStateClass, (klass), TYPE_DBUS_VMSTATE)
+
+struct DBusVMStateClass {
+    ObjectClass parent_class;
+};
+
+struct DBusVMState {
+    Object parent;
+
+    GDBusConnection *bus;
+    char *dbus_addr;
+    char *id_list;
+
+    uint32_t data_size;
+    uint8_t *data;
+};
+
+static const GDBusPropertyInfo vmstate_property_info[] = {
+    { -1, (char *) "Id", (char *) "s",
+      G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL },
+};
+
+static const GDBusPropertyInfo * const vmstate_property_info_pointers[] = {
+    &vmstate_property_info[0],
+    NULL
+};
+
+static const GDBusInterfaceInfo vmstate1_interface_info = {
+    -1,
+    (char *) "org.qemu.VMState1",
+    (GDBusMethodInfo **) NULL,
+    (GDBusSignalInfo **) NULL,
+    (GDBusPropertyInfo **) &vmstate_property_info_pointers,
+    NULL,
+};
+
+#define DBUS_VMSTATE_SIZE_LIMIT (1 * MiB)
+
+static GHashTable *
+get_id_list_set(DBusVMState *self)
+{
+    g_auto(GStrv) ids = NULL;
+    g_autoptr(GHashTable) set = NULL;
+    int i;
+
+    if (!self->id_list) {
+        return NULL;
+    }
+
+    ids = g_strsplit(self->id_list, ",", -1);
+    set = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+    for (i = 0; ids[i]; i++) {
+        g_hash_table_add(set, ids[i]);
+        ids[i] = NULL;
+    }
+
+    return g_steal_pointer(&set);
+}
+
+static GHashTable *
+dbus_get_proxies(DBusVMState *self, GError **err)
+{
+    g_autoptr(GHashTable) proxies = NULL;
+    g_autoptr(GHashTable) ids = NULL;
+    g_auto(GStrv) names = NULL;
+    Error *error = NULL;
+    size_t i;
+
+    ids = get_id_list_set(self);
+    proxies = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                    g_free, g_object_unref);
+
+    names = qemu_dbus_get_queued_owners(self->bus, "org.qemu.VMState1", &error);
+    if (!names) {
+        g_set_error(err, G_IO_ERROR, G_IO_ERROR_FAILED, "%s",
+                    error_get_pretty(error));
+        error_free(error);
+        return NULL;
+    }
+
+    for (i = 0; names[i]; i++) {
+        g_autoptr(GDBusProxy) proxy = NULL;
+        g_autoptr(GVariant) result = NULL;
+        g_autofree char *id = NULL;
+        size_t size;
+
+        proxy = g_dbus_proxy_new_sync(self->bus, G_DBUS_PROXY_FLAGS_NONE,
+                    (GDBusInterfaceInfo *) &vmstate1_interface_info,
+                    names[i],
+                    "/org/qemu/VMState1",
+                    "org.qemu.VMState1",
+                    NULL, err);
+        if (!proxy) {
+            return NULL;
+        }
+
+        result = g_dbus_proxy_get_cached_property(proxy, "Id");
+        if (!result) {
+            g_set_error_literal(err, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                "VMState Id property is missing.");
+            return NULL;
+        }
+
+        id = g_variant_dup_string(result, &size);
+        if (ids && !g_hash_table_remove(ids, id)) {
+            g_clear_pointer(&id, g_free);
+            g_clear_object(&proxy);
+            continue;
+        }
+        if (size == 0 || size >= 256) {
+            g_set_error(err, G_IO_ERROR, G_IO_ERROR_FAILED,
+                        "VMState Id '%s' is invalid.", id);
+            return NULL;
+        }
+
+        if (!g_hash_table_insert(proxies, id, proxy)) {
+            g_set_error(err, G_IO_ERROR, G_IO_ERROR_FAILED,
+                        "Duplicated VMState Id '%s'", id);
+            return NULL;
+        }
+        id = NULL;
+        proxy = NULL;
+
+        g_clear_pointer(&result, g_variant_unref);
+    }
+
+    if (ids) {
+        g_autofree char **left = NULL;
+
+        left = (char **)g_hash_table_get_keys_as_array(ids, NULL);
+        if (*left) {
+            g_autofree char *leftids = g_strjoinv(",", left);
+            g_set_error(err, G_IO_ERROR, G_IO_ERROR_FAILED,
+                        "Required VMState Id are missing: %s", leftids);
+            return NULL;
+        }
+    }
+
+    return g_steal_pointer(&proxies);
+}
+
+static int
+dbus_load_state_proxy(GDBusProxy *proxy, const uint8_t *data, size_t size)
+{
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GVariant) result = NULL;
+    g_autoptr(GVariant) value = NULL;
+
+    value = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
+                                      data, size, sizeof(char));
+    result = g_dbus_proxy_call_sync(proxy, "Load",
+                                    g_variant_new("(@ay)",
+                                                  g_steal_pointer(&value)),
+                                    G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                    -1, NULL, &err);
+    if (!result) {
+        error_report("%s: Failed to Load: %s", __func__, err->message);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int dbus_vmstate_post_load(void *opaque, int version_id)
+{
+    DBusVMState *self = DBUS_VMSTATE(opaque);
+    g_autoptr(GInputStream) m = NULL;
+    g_autoptr(GDataInputStream) s = NULL;
+    g_autoptr(GError) err = NULL;
+    g_autoptr(GHashTable) proxies = NULL;
+    uint32_t nelem;
+
+    trace_dbus_vmstate_post_load(version_id);
+
+    proxies = dbus_get_proxies(self, &err);
+    if (!proxies) {
+        error_report("%s: Failed to get proxies: %s", __func__, err->message);
+        return -1;
+    }
+
+    m = g_memory_input_stream_new_from_data(self->data, self->data_size, NULL);
+    s = g_data_input_stream_new(m);
+    g_data_input_stream_set_byte_order(s, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
+
+    nelem = g_data_input_stream_read_uint32(s, NULL, &err);
+    if (err) {
+        goto error;
+    }
+
+    while (nelem > 0) {
+        GDBusProxy *proxy = NULL;
+        uint32_t len;
+        gsize bytes_read, avail;
+        char id[256];
+
+        len = g_data_input_stream_read_uint32(s, NULL, &err);
+        if (err) {
+            goto error;
+        }
+        if (len >= 256) {
+            error_report("%s: Invalid DBus vmstate proxy name %u",
+                         __func__, len);
+            return -1;
+        }
+        if (!g_input_stream_read_all(G_INPUT_STREAM(s), id, len,
+                                     &bytes_read, NULL, &err)) {
+            goto error;
+        }
+        g_return_val_if_fail(bytes_read == len, -1);
+        id[len] = 0;
+
+        trace_dbus_vmstate_loading(id);
+
+        proxy = g_hash_table_lookup(proxies, id);
+        if (!proxy) {
+            error_report("%s: Failed to find proxy Id '%s'", __func__, id);
+            return -1;
+        }
+
+        len = g_data_input_stream_read_uint32(s, NULL, &err);
+        avail = g_buffered_input_stream_get_available(
+            G_BUFFERED_INPUT_STREAM(s));
+
+        if (len > DBUS_VMSTATE_SIZE_LIMIT || len > avail) {
+            error_report("%s: Invalid vmstate size: %u", __func__, len);
+            return -1;
+        }
+
+        if (dbus_load_state_proxy(proxy,
+                g_buffered_input_stream_peek_buffer(G_BUFFERED_INPUT_STREAM(s),
+                                                    NULL),
+                len) < 0) {
+            error_report("%s: Failed to restore Id '%s'", __func__, id);
+            return -1;
+        }
+
+        if (!g_seekable_seek(G_SEEKABLE(s), len, G_SEEK_CUR, NULL, &err)) {
+            goto error;
+        }
+
+        nelem -= 1;
+    }
+
+    return 0;
+
+error:
+    error_report("%s: Failed to read from stream: %s", __func__, err->message);
+    return -1;
+}
+
+static void
+dbus_save_state_proxy(gpointer key,
+                      gpointer value,
+                      gpointer user_data)
+{
+    GDataOutputStream *s = user_data;
+    const char *id = key;
+    GDBusProxy *proxy = value;
+    g_autoptr(GVariant) result = NULL;
+    g_autoptr(GVariant) child = NULL;
+    g_autoptr(GError) err = NULL;
+    const uint8_t *data;
+    gsize size;
+
+    trace_dbus_vmstate_saving(id);
+
+    result = g_dbus_proxy_call_sync(proxy, "Save",
+                                    NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                    -1, NULL, &err);
+    if (!result) {
+        error_report("%s: Failed to Save: %s", __func__, err->message);
+        return;
+    }
+
+    child = g_variant_get_child_value(result, 0);
+    data = g_variant_get_fixed_array(child, &size, sizeof(char));
+    if (!data) {
+        error_report("%s: Failed to Save: not a byte array", __func__);
+        return;
+    }
+    if (size > DBUS_VMSTATE_SIZE_LIMIT) {
+        error_report("%s: Too large vmstate data to save: %zu",
+                     __func__, (size_t)size);
+        return;
+    }
+
+    if (!g_data_output_stream_put_uint32(s, strlen(id), NULL, &err) ||
+        !g_data_output_stream_put_string(s, id, NULL, &err) ||
+        !g_data_output_stream_put_uint32(s, size, NULL, &err) ||
+        !g_output_stream_write_all(G_OUTPUT_STREAM(s),
+                                   data, size, NULL, NULL, &err)) {
+        error_report("%s: Failed to write to stream: %s",
+                     __func__, err->message);
+    }
+}
+
+static int dbus_vmstate_pre_save(void *opaque)
+{
+    DBusVMState *self = DBUS_VMSTATE(opaque);
+    g_autoptr(GOutputStream) m = NULL;
+    g_autoptr(GDataOutputStream) s = NULL;
+    g_autoptr(GHashTable) proxies = NULL;
+    g_autoptr(GError) err = NULL;
+
+    trace_dbus_vmstate_pre_save();
+
+    proxies = dbus_get_proxies(self, &err);
+    if (!proxies) {
+        error_report("%s: Failed to get proxies: %s", __func__, err->message);
+        return -1;
+    }
+
+    m = g_memory_output_stream_new_resizable();
+    s = g_data_output_stream_new(m);
+    g_data_output_stream_set_byte_order(s, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
+
+    if (!g_data_output_stream_put_uint32(s, g_hash_table_size(proxies),
+                                         NULL, &err)) {
+        error_report("%s: Failed to write to stream: %s",
+                     __func__, err->message);
+        return -1;
+    }
+
+    g_hash_table_foreach(proxies, dbus_save_state_proxy, s);
+
+    if (g_memory_output_stream_get_size(G_MEMORY_OUTPUT_STREAM(m))
+        > UINT32_MAX) {
+        error_report("%s: DBus vmstate buffer is too large", __func__);
+        return -1;
+    }
+
+    if (!g_output_stream_close(G_OUTPUT_STREAM(m), NULL, &err)) {
+        error_report("%s: Failed to close stream: %s", __func__, err->message);
+        return -1;
+    }
+
+    g_free(self->data);
+    self->data_size =
+        g_memory_output_stream_get_size(G_MEMORY_OUTPUT_STREAM(m));
+    self->data =
+        g_memory_output_stream_steal_data(G_MEMORY_OUTPUT_STREAM(m));
+
+    return 0;
+}
+
+static const VMStateDescription dbus_vmstate = {
+    .name = TYPE_DBUS_VMSTATE,
+    .version_id = 0,
+    .pre_save = dbus_vmstate_pre_save,
+    .post_load = dbus_vmstate_post_load,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(data_size, DBusVMState),
+        VMSTATE_VBUFFER_ALLOC_UINT32(data, DBusVMState, 0, 0, data_size),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void
+dbus_vmstate_complete(UserCreatable *uc, Error **errp)
+{
+    DBusVMState *self = DBUS_VMSTATE(uc);
+    g_autoptr(GError) err = NULL;
+
+    if (!object_resolve_path_type("", TYPE_DBUS_VMSTATE, NULL)) {
+        error_setg(errp, "There is already an instance of %s",
+                   TYPE_DBUS_VMSTATE);
+        return;
+    }
+
+    if (!self->dbus_addr) {
+        error_setg(errp, QERR_MISSING_PARAMETER, "addr");
+        return;
+    }
+
+    self->bus = g_dbus_connection_new_for_address_sync(self->dbus_addr,
+                    G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+                    G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+                    NULL, NULL, &err);
+    if (err) {
+        error_setg(errp, "failed to connect to DBus: '%s'", err->message);
+        return;
+    }
+
+    if (vmstate_register(VMSTATE_IF(self), -1, &dbus_vmstate, self) < 0) {
+        error_setg(errp, "Failed to register vmstate");
+    }
+}
+
+static void
+dbus_vmstate_finalize(Object *o)
+{
+    DBusVMState *self = DBUS_VMSTATE(o);
+
+    vmstate_unregister(VMSTATE_IF(self), &dbus_vmstate, self);
+
+    g_clear_object(&self->bus);
+    g_free(self->dbus_addr);
+    g_free(self->id_list);
+    g_free(self->data);
+}
+
+static char *
+get_dbus_addr(Object *o, Error **errp)
+{
+    DBusVMState *self = DBUS_VMSTATE(o);
+
+    return g_strdup(self->dbus_addr);
+}
+
+static void
+set_dbus_addr(Object *o, const char *str, Error **errp)
+{
+    DBusVMState *self = DBUS_VMSTATE(o);
+
+    g_free(self->dbus_addr);
+    self->dbus_addr = g_strdup(str);
+}
+
+static char *
+get_id_list(Object *o, Error **errp)
+{
+    DBusVMState *self = DBUS_VMSTATE(o);
+
+    return g_strdup(self->id_list);
+}
+
+static void
+set_id_list(Object *o, const char *str, Error **errp)
+{
+    DBusVMState *self = DBUS_VMSTATE(o);
+
+    g_free(self->id_list);
+    self->id_list = g_strdup(str);
+}
+
+static char *
+dbus_vmstate_get_id(VMStateIf *vmif)
+{
+    return g_strdup(TYPE_DBUS_VMSTATE);
+}
+
+static void
+dbus_vmstate_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+    VMStateIfClass *vc = VMSTATE_IF_CLASS(oc);
+
+    ucc->complete = dbus_vmstate_complete;
+    vc->get_id = dbus_vmstate_get_id;
+
+    object_class_property_add_str(oc, "addr",
+                                  get_dbus_addr, set_dbus_addr,
+                                  &error_abort);
+    object_class_property_add_str(oc, "id-list",
+                                  get_id_list, set_id_list,
+                                  &error_abort);
+}
+
+static const TypeInfo dbus_vmstate_info = {
+    .name = TYPE_DBUS_VMSTATE,
+    .parent = TYPE_OBJECT,
+    .instance_size = sizeof(DBusVMState),
+    .instance_finalize = dbus_vmstate_finalize,
+    .class_size = sizeof(DBusVMStateClass),
+    .class_init = dbus_vmstate_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { TYPE_VMSTATE_IF },
+        { }
+    }
+};
+
+static void
+register_types(void)
+{
+    type_register_static(&dbus_vmstate_info);
+}
+
+type_init(register_types);
diff --git a/backends/trace-events b/backends/trace-events
new file mode 100644
index 0000000000..59058f7630
--- /dev/null
+++ b/backends/trace-events
@@ -0,0 +1,7 @@
+# See docs/devel/tracing.txt for syntax documentation.
+
+# dbus-vmstate.c
+dbus_vmstate_pre_save(void)
+dbus_vmstate_post_load(int version_id) "version_id: %d"
+dbus_vmstate_loading(const char *id) "id: %s"
+dbus_vmstate_saving(const char *id) "id: %s"
diff --git a/configure b/configure
index 747d3b4120..0ce2c0354a 100755
--- a/configure
+++ b/configure
@@ -3701,10 +3701,16 @@ if $pkg_config --atleast-version=$glib_req_ver gio-2.0; then
     gio=yes
     gio_cflags=$($pkg_config --cflags gio-2.0)
     gio_libs=$($pkg_config --libs gio-2.0)
+    gdbus_codegen=$($pkg_config --variable=gdbus_codegen gio-2.0)
 else
     gio=no
 fi
 
+if $pkg_config --atleast-version=$glib_req_ver gio-unix-2.0; then
+    gio_cflags="$gio_cflags $($pkg_config --cflags gio-unix-2.0)"
+    gio_libs="$gio_libs $($pkg_config --libs gio-unix-2.0)"
+fi
+
 # Sanity check that the current size_t matches the
 # size that glib thinks it should be. This catches
 # problems on multi-arch where people try to build
@@ -6904,6 +6910,7 @@ if test "$gio" = "yes" ; then
     echo "CONFIG_GIO=y" >> $config_host_mak
     echo "GIO_CFLAGS=$gio_cflags" >> $config_host_mak
     echo "GIO_LIBS=$gio_libs" >> $config_host_mak
+    echo "GDBUS_CODEGEN=$gdbus_codegen" >> $config_host_mak
 fi
 echo "CONFIG_TLS_PRIORITY=\"$tls_priority\"" >> $config_host_mak
 if test "$gnutls" = "yes" ; then
diff --git a/docs/interop/dbus-vmstate.rst b/docs/interop/dbus-vmstate.rst
new file mode 100644
index 0000000000..1d719c1c60
--- /dev/null
+++ b/docs/interop/dbus-vmstate.rst
@@ -0,0 +1,74 @@
+=============
+D-Bus VMState
+=============
+
+Introduction
+============
+
+The QEMU dbus-vmstate object's aim is to migrate helpers' data running
+on a QEMU D-Bus bus. (refer to the :doc:`dbus` document for
+some recommendations on D-Bus usage)
+
+Upon migration, QEMU will go through the queue of
+``org.qemu.VMState1`` D-Bus name owners and query their ``Id``. It
+must be unique among the helpers.
+
+It will then save arbitrary data of each Id to be transferred in the
+migration stream and restored/loaded at the corresponding destination
+helper.
+
+For now, the data amount to be transferred is arbitrarily limited to
+1Mb. The state must be saved quickly (a fraction of a second). (D-Bus
+imposes a time limit on reply anyway, and migration would fail if data
+isn't given quickly enough.)
+
+dbus-vmstate object can be configured with the expected list of
+helpers by setting its ``id-list`` property, with a comma-separated
+``Id`` list.
+
+Interface
+=========
+
+On object path ``/org/qemu/VMState1``, the following
+``org.qemu.VMState1`` interface should be implemented:
+
+.. code:: xml
+
+  <interface name="org.qemu.VMState1">
+    <property name="Id" type="s" access="read"/>
+    <method name="Load">
+      <arg type="ay" name="data" direction="in"/>
+    </method>
+    <method name="Save">
+      <arg type="ay" name="data" direction="out"/>
+    </method>
+  </interface>
+
+"Id" property
+-------------
+
+A string that identifies the helper uniquely. (maximum 256 bytes
+including terminating NUL byte)
+
+.. note::
+
+   The helper ID namespace is a separate namespace. In particular, it is not
+   related to QEMU "id" used in -object/-device objects.
+
+Load(in u8[] bytes) method
+--------------------------
+
+The method called on destination with the state to restore.
+
+The helper may be initially started in a waiting state (with
+an --incoming argument for example), and it may resume on success.
+
+An error may be returned to the caller.
+
+Save(out u8[] bytes) method
+---------------------------
+
+The method called on the source to get the current state to be
+migrated. The helper should continue to run normally.
+
+An error may be returned to the caller.
diff --git a/docs/interop/dbus.rst b/docs/interop/dbus.rst
new file mode 100644
index 0000000000..76a5bde625
--- /dev/null
+++ b/docs/interop/dbus.rst
@@ -0,0 +1,110 @@
+=====
+D-Bus
+=====
+
+Introduction
+============
+
+QEMU may be running with various helper processes involved:
+ - vhost-user* processes (gpu, virtfs, input, etc...)
+ - TPM emulation (or other devices)
+ - user networking (slirp)
+ - network services (DHCP/DNS, samba/ftp etc)
+ - background tasks (compression, streaming etc)
+ - client UI
+ - admin & cli
+
+Having several processes allows stricter security rules, as well as
+greater modularity.
+
+While QEMU itself uses QMP as primary IPC (and Spice/VNC for remote
+display), D-Bus is the de facto IPC of choice on Unix systems. The
+wire format is machine friendly, good bindings exist for various
+languages, and there are various tools available.
+
+Using a bus, helper processes can discover and communicate with each
+other easily, without going through QEMU. The bus topology is also
+easier to apprehend and debug than a mesh. However, it is wise to
+consider the security aspects of it.
+
+Security
+========
+
+A QEMU D-Bus bus should be private to a single VM. Thus, only
+cooperative tasks are running on the same bus to serve the VM.
+
+D-Bus, the protocol and standard, doesn't have mechanisms to enforce
+security between peers once the connection is established. Peers may
+have additional mechanisms to enforce security rules, based for
+example on UNIX credentials.
+
+The daemon can control which peers can send/recv messages using
+various metadata attributes, however, this is alone is not generally
+sufficient to make the deployment secure.  The semantics of the actual
+methods implemented using D-Bus are just as critical. Peers need to
+carefully validate any information they received from a peer with a
+different trust level.
+
+dbus-daemon policy
+------------------
+
+dbus-daemon can enforce various policies based on the UID/GID of the
+processes that are connected to it. It is thus a good idea to run
+helpers as different UID from QEMU and set appropriate policies.
+
+Depending on the use case, you may choose different scenarios:
+
+ - Everything the same UID
+
+   - Convenient for developers
+   - Improved reliability - crash of one part doens't take
+     out entire VM
+   - No security benefit over traditional QEMU, unless additional
+     unless additional controls such as SELinux or AppArmor are
+     applied
+
+ - Two UIDs, one for QEMU, one for dbus & helpers
+
+   - Moderately improved user based security isolation
+
+ - Many UIDs, one for QEMU one for dbus and one for each helpers
+
+   - Best user based security isolation
+   - Complex to manager distinct UIDs needed for each VM
+
+For example, to allow only ``qemu`` user to talk to ``qemu-helper``
+``org.qemu.Helper1`` service, a dbus-daemon policy may contain:
+
+.. code:: xml
+
+  <policy user="qemu">
+     <allow send_destination="org.qemu.Helper1"/>
+     <allow receive_sender="org.qemu.Helper1"/>
+  </policy>
+
+  <policy user="qemu-helper">
+     <allow own="org.qemu.Helper1"/>
+  </policy>
+
+
+dbus-daemon can also perfom SELinux checks based on the security
+context of the source and the target. For example, ``virtiofs_t``
+could be allowed to send a message to ``svirt_t``, but ``virtiofs_t``
+wouldn't be allowed to send a message to ``virtiofs_t``.
+
+See dbus-daemon man page for details.
+
+Guidelines
+==========
+
+When implementing new D-Bus interfaces, it is recommended to follow
+the "D-Bus API Design Guidelines":
+https://dbus.freedesktop.org/doc/dbus-api-design.html
+
+The "org.qemu.*" prefix is reserved for services implemented &
+distributed by the QEMU project.
+
+QEMU Interfaces
+===============
+
+:doc:`dbus-vmstate`
diff --git a/docs/interop/index.rst b/docs/interop/index.rst
index 3e33fb5933..049387ac6d 100644
--- a/docs/interop/index.rst
+++ b/docs/interop/index.rst
@@ -13,6 +13,8 @@ Contents:
    :maxdepth: 2
 
    bitmaps
+   dbus
+   dbus-vmstate
    live-block-operations
    pr-helper
    qemu-ga
diff --git a/hw/block/onenand.c b/hw/block/onenand.c
index fcc5a69b90..9c233c12e4 100644
--- a/hw/block/onenand.c
+++ b/hw/block/onenand.c
@@ -822,7 +822,7 @@ static void onenand_realize(DeviceState *dev, Error **errp)
     onenand_mem_setup(s);
     sysbus_init_irq(sbd, &s->intr);
     sysbus_init_mmio(sbd, &s->container);
-    vmstate_register(dev,
+    vmstate_register(VMSTATE_IF(dev),
                      ((s->shift & 0x7f) << 24)
                      | ((s->id.man & 0xff) << 16)
                      | ((s->id.dev & 0xff) << 8)
diff --git a/hw/core/Makefile.objs b/hw/core/Makefile.objs
index fd0550d1d9..0edd9e635d 100644
--- a/hw/core/Makefile.objs
+++ b/hw/core/Makefile.objs
@@ -9,6 +9,7 @@ common-obj-y += hotplug.o
 common-obj-$(CONFIG_SOFTMMU) += nmi.o
 common-obj-$(CONFIG_SOFTMMU) += vm-change-state-handler.o
 common-obj-y += cpu.o
+common-obj-y += vmstate-if.o
 
 common-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o
 common-obj-$(CONFIG_XILINX_AXI) += stream.o
diff --git a/hw/core/qdev.c b/hw/core/qdev.c
index 82d3ee590a..501228ba08 100644
--- a/hw/core/qdev.c
+++ b/hw/core/qdev.c
@@ -889,7 +889,8 @@ static void device_set_realized(Object *obj, bool value, Error **errp)
         dev->canonical_path = object_get_canonical_path(OBJECT(dev));
 
         if (qdev_get_vmsd(dev)) {
-            if (vmstate_register_with_alias_id(dev, -1, qdev_get_vmsd(dev), dev,
+            if (vmstate_register_with_alias_id(VMSTATE_IF(dev),
+                                               -1, qdev_get_vmsd(dev), dev,
                                                dev->instance_id_alias,
                                                dev->alias_required_for_version,
                                                &local_err) < 0) {
@@ -923,7 +924,7 @@ static void device_set_realized(Object *obj, bool value, Error **errp)
                                      local_err ? NULL : &local_err);
         }
         if (qdev_get_vmsd(dev)) {
-            vmstate_unregister(dev, qdev_get_vmsd(dev), dev);
+            vmstate_unregister(VMSTATE_IF(dev), qdev_get_vmsd(dev), dev);
         }
         if (dc->unrealize) {
             dc->unrealize(dev, local_err ? NULL : &local_err);
@@ -947,7 +948,7 @@ child_realize_fail:
     }
 
     if (qdev_get_vmsd(dev)) {
-        vmstate_unregister(dev, qdev_get_vmsd(dev), dev);
+        vmstate_unregister(VMSTATE_IF(dev), qdev_get_vmsd(dev), dev);
     }
 
 post_realize_fail:
@@ -1087,9 +1088,18 @@ static void device_unparent(Object *obj)
     }
 }
 
+static char *
+device_vmstate_if_get_id(VMStateIf *obj)
+{
+    DeviceState *dev = DEVICE(obj);
+
+    return qdev_get_dev_path(dev);
+}
+
 static void device_class_init(ObjectClass *class, void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(class);
+    VMStateIfClass *vc = VMSTATE_IF_CLASS(class);
 
     class->unparent = device_unparent;
 
@@ -1101,6 +1111,7 @@ static void device_class_init(ObjectClass *class, void *data)
      */
     dc->hotpluggable = true;
     dc->user_creatable = true;
+    vc->get_id = device_vmstate_if_get_id;
 }
 
 void device_class_set_parent_reset(DeviceClass *dc,
@@ -1158,6 +1169,10 @@ static const TypeInfo device_type_info = {
     .class_init = device_class_init,
     .abstract = true,
     .class_size = sizeof(DeviceClass),
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_VMSTATE_IF },
+        { }
+    }
 };
 
 static void qdev_register_types(void)
diff --git a/hw/core/vmstate-if.c b/hw/core/vmstate-if.c
new file mode 100644
index 0000000000..bf453620fe
--- /dev/null
+++ b/hw/core/vmstate-if.c
@@ -0,0 +1,23 @@
+/*
+ * VMState interface
+ *
+ * Copyright (c) 2009-2019 Red Hat Inc
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/vmstate-if.h"
+
+static const TypeInfo vmstate_if_info = {
+    .name = TYPE_VMSTATE_IF,
+    .parent = TYPE_INTERFACE,
+    .class_size = sizeof(VMStateIfClass),
+};
+
+static void vmstate_register_types(void)
+{
+    type_register_static(&vmstate_if_info);
+}
+
+type_init(vmstate_register_types);
diff --git a/hw/ide/cmd646.c b/hw/ide/cmd646.c
index 19984d2af9..3f9be968d1 100644
--- a/hw/ide/cmd646.c
+++ b/hw/ide/cmd646.c
@@ -302,7 +302,7 @@ static void pci_cmd646_ide_realize(PCIDevice *dev, Error **errp)
     }
     g_free(irq);
 
-    vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
+    vmstate_register(VMSTATE_IF(dev), 0, &vmstate_ide_pci, d);
     qemu_register_reset(cmd646_reset, d);
 }
 
diff --git a/hw/ide/isa.c b/hw/ide/isa.c
index 7b6e283679..9c7f88b2d5 100644
--- a/hw/ide/isa.c
+++ b/hw/ide/isa.c
@@ -75,7 +75,7 @@ static void isa_ide_realizefn(DeviceState *dev, Error **errp)
     ide_init_ioport(&s->bus, isadev, s->iobase, s->iobase2);
     isa_init_irq(isadev, &s->irq, s->isairq);
     ide_init2(&s->bus, s->irq);
-    vmstate_register(dev, 0, &vmstate_ide_isa, s);
+    vmstate_register(VMSTATE_IF(dev), 0, &vmstate_ide_isa, s);
     ide_register_restart_cb(&s->bus);
 }
 
diff --git a/hw/ide/piix.c b/hw/ide/piix.c
index db313dd3b1..bc575b4d70 100644
--- a/hw/ide/piix.c
+++ b/hw/ide/piix.c
@@ -156,7 +156,7 @@ static void pci_piix_ide_realize(PCIDevice *dev, Error **errp)
     bmdma_setup_bar(d);
     pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &d->bmdma_bar);
 
-    vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
+    vmstate_register(VMSTATE_IF(dev), 0, &vmstate_ide_pci, d);
 
     pci_piix_init_ports(d);
 }
diff --git a/hw/ide/via.c b/hw/ide/via.c
index 053622bd82..096de8dba0 100644
--- a/hw/ide/via.c
+++ b/hw/ide/via.c
@@ -190,7 +190,7 @@ static void via_ide_realize(PCIDevice *dev, Error **errp)
     bmdma_setup_bar(d);
     pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &d->bmdma_bar);
 
-    vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
+    vmstate_register(VMSTATE_IF(dev), 0, &vmstate_ide_pci, d);
 
     for (i = 0; i < 2; i++) {
         ide_bus_new(&d->bus[i], sizeof(d->bus[i]), DEVICE(d), i, 2);
diff --git a/hw/misc/max111x.c b/hw/misc/max111x.c
index a713149f16..211008ce02 100644
--- a/hw/misc/max111x.c
+++ b/hw/misc/max111x.c
@@ -146,7 +146,7 @@ static int max111x_init(SSISlave *d, int inputs)
     s->input[7] = 0x80;
     s->com = 0;
 
-    vmstate_register(dev, -1, &vmstate_max111x, s);
+    vmstate_register(VMSTATE_IF(dev), -1, &vmstate_max111x, s);
     return 0;
 }
 
diff --git a/hw/net/eepro100.c b/hw/net/eepro100.c
index cc2dd8b1c9..cc71a7a036 100644
--- a/hw/net/eepro100.c
+++ b/hw/net/eepro100.c
@@ -1815,7 +1815,7 @@ static void pci_nic_uninit(PCIDevice *pci_dev)
 {
     EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, pci_dev);
 
-    vmstate_unregister(&pci_dev->qdev, s->vmstate, s);
+    vmstate_unregister(VMSTATE_IF(&pci_dev->qdev), s->vmstate, s);
     g_free(s->vmstate);
     eeprom93xx_free(&pci_dev->qdev, s->eeprom);
     qemu_del_nic(s->nic);
@@ -1874,7 +1874,7 @@ static void e100_nic_realize(PCIDevice *pci_dev, Error **errp)
 
     s->vmstate = g_memdup(&vmstate_eepro100, sizeof(vmstate_eepro100));
     s->vmstate->name = qemu_get_queue(s->nic)->model;
-    vmstate_register(&pci_dev->qdev, -1, s->vmstate, s);
+    vmstate_register(VMSTATE_IF(&pci_dev->qdev), -1, s->vmstate, s);
 }
 
 static void eepro100_instance_init(Object *obj)
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index db3d7c38e6..777d62d3c8 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -2853,7 +2853,8 @@ static void virtio_net_handle_migration_primary(VirtIONet *n,
 
     if (migration_in_setup(s) && !should_be_hidden) {
         if (failover_unplug_primary(n)) {
-            vmstate_unregister(n->primary_dev, qdev_get_vmsd(n->primary_dev),
+            vmstate_unregister(VMSTATE_IF(n->primary_dev),
+                    qdev_get_vmsd(n->primary_dev),
                     n->primary_dev);
             qapi_event_send_unplug_primary(n->primary_device_id);
             atomic_set(&n->primary_should_be_hidden, true);
diff --git a/hw/nvram/eeprom93xx.c b/hw/nvram/eeprom93xx.c
index 5b01b9b03f..07f09549ed 100644
--- a/hw/nvram/eeprom93xx.c
+++ b/hw/nvram/eeprom93xx.c
@@ -321,7 +321,7 @@ eeprom_t *eeprom93xx_new(DeviceState *dev, uint16_t nwords)
     /* Output DO is tristate, read results in 1. */
     eeprom->eedo = 1;
     logout("eeprom = 0x%p, nwords = %u\n", eeprom, nwords);
-    vmstate_register(dev, 0, &vmstate_eeprom, eeprom);
+    vmstate_register(VMSTATE_IF(dev), 0, &vmstate_eeprom, eeprom);
     return eeprom;
 }
 
@@ -329,7 +329,7 @@ void eeprom93xx_free(DeviceState *dev, eeprom_t *eeprom)
 {
     /* Destroy EEPROM. */
     logout("eeprom = 0x%p\n", eeprom);
-    vmstate_unregister(dev, &vmstate_eeprom, eeprom);
+    vmstate_unregister(VMSTATE_IF(dev), &vmstate_eeprom, eeprom);
     g_free(eeprom);
 }
 
diff --git a/hw/ppc/spapr_drc.c b/hw/ppc/spapr_drc.c
index 62f1a42592..17aeac3801 100644
--- a/hw/ppc/spapr_drc.c
+++ b/hw/ppc/spapr_drc.c
@@ -511,7 +511,7 @@ static void realize(DeviceState *d, Error **errp)
         error_propagate(errp, err);
         return;
     }
-    vmstate_register(DEVICE(drc), spapr_drc_index(drc), &vmstate_spapr_drc,
+    vmstate_register(VMSTATE_IF(drc), spapr_drc_index(drc), &vmstate_spapr_drc,
                      drc);
     trace_spapr_drc_realize_complete(spapr_drc_index(drc));
 }
@@ -523,7 +523,7 @@ static void unrealize(DeviceState *d, Error **errp)
     gchar *name;
 
     trace_spapr_drc_unrealize(spapr_drc_index(drc));
-    vmstate_unregister(DEVICE(drc), &vmstate_spapr_drc, drc);
+    vmstate_unregister(VMSTATE_IF(drc), &vmstate_spapr_drc, drc);
     root_container = container_get(object_get_root(), DRC_CONTAINER_PATH);
     name = g_strdup_printf("%x", spapr_drc_index(drc));
     object_property_del(root_container, name, errp);
@@ -619,7 +619,8 @@ static void realize_physical(DeviceState *d, Error **errp)
         return;
     }
 
-    vmstate_register(DEVICE(drcp), spapr_drc_index(SPAPR_DR_CONNECTOR(drcp)),
+    vmstate_register(VMSTATE_IF(drcp),
+                     spapr_drc_index(SPAPR_DR_CONNECTOR(drcp)),
                      &vmstate_spapr_drc_physical, drcp);
     qemu_register_reset(drc_physical_reset, drcp);
 }
@@ -635,7 +636,7 @@ static void unrealize_physical(DeviceState *d, Error **errp)
         return;
     }
 
-    vmstate_unregister(DEVICE(drcp), &vmstate_spapr_drc_physical, drcp);
+    vmstate_unregister(VMSTATE_IF(drcp), &vmstate_spapr_drc_physical, drcp);
     qemu_unregister_reset(drc_physical_reset, drcp);
 }
 
diff --git a/hw/ppc/spapr_iommu.c b/hw/ppc/spapr_iommu.c
index 3d3bcc8649..5704fe6051 100644
--- a/hw/ppc/spapr_iommu.c
+++ b/hw/ppc/spapr_iommu.c
@@ -317,7 +317,7 @@ static void spapr_tce_table_realize(DeviceState *dev, Error **errp)
 
     QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);
 
-    vmstate_register(DEVICE(tcet), tcet->liobn, &vmstate_spapr_tce_table,
+    vmstate_register(VMSTATE_IF(tcet), tcet->liobn, &vmstate_spapr_tce_table,
                      tcet);
 }
 
@@ -420,7 +420,7 @@ static void spapr_tce_table_unrealize(DeviceState *dev, Error **errp)
 {
     SpaprTceTable *tcet = SPAPR_TCE_TABLE(dev);
 
-    vmstate_unregister(DEVICE(tcet), &vmstate_spapr_tce_table, tcet);
+    vmstate_unregister(VMSTATE_IF(tcet), &vmstate_spapr_tce_table, tcet);
 
     QLIST_REMOVE(tcet, list);
 
diff --git a/hw/s390x/s390-skeys.c b/hw/s390x/s390-skeys.c
index bd37f39120..5da6e5292f 100644
--- a/hw/s390x/s390-skeys.c
+++ b/hw/s390x/s390-skeys.c
@@ -392,7 +392,7 @@ static inline void s390_skeys_set_migration_enabled(Object *obj, bool value,
         register_savevm_live(TYPE_S390_SKEYS, 0, 1,
                              &savevm_s390_storage_keys, ss);
     } else {
-        unregister_savevm(DEVICE(ss), TYPE_S390_SKEYS, ss);
+        unregister_savevm(VMSTATE_IF(ss), TYPE_S390_SKEYS, ss);
     }
 }
 
diff --git a/include/hw/vmstate-if.h b/include/hw/vmstate-if.h
new file mode 100644
index 0000000000..8ff7f0f292
--- /dev/null
+++ b/include/hw/vmstate-if.h
@@ -0,0 +1,40 @@
+/*
+ * VMState interface
+ *
+ * Copyright (c) 2009-2019 Red Hat Inc
+ * 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 VMSTATE_IF_H
+#define VMSTATE_IF_H
+
+#include "qom/object.h"
+
+#define TYPE_VMSTATE_IF "vmstate-if"
+
+#define VMSTATE_IF_CLASS(klass)                                     \
+    OBJECT_CLASS_CHECK(VMStateIfClass, (klass), TYPE_VMSTATE_IF)
+#define VMSTATE_IF_GET_CLASS(obj)                           \
+    OBJECT_GET_CLASS(VMStateIfClass, (obj), TYPE_VMSTATE_IF)
+#define VMSTATE_IF(obj)                             \
+    INTERFACE_CHECK(VMStateIf, (obj), TYPE_VMSTATE_IF)
+
+typedef struct VMStateIf VMStateIf;
+
+typedef struct VMStateIfClass {
+    InterfaceClass parent_class;
+
+    char * (*get_id)(VMStateIf *obj);
+} VMStateIfClass;
+
+static inline char *vmstate_if_get_id(VMStateIf *vmif)
+{
+    if (!vmif) {
+        return NULL;
+    }
+
+    return VMSTATE_IF_GET_CLASS(vmif)->get_id(vmif);
+}
+
+#endif /* VMSTATE_IF_H */
diff --git a/include/migration/register.h b/include/migration/register.h
index a13359a08d..00c38ebe9f 100644
--- a/include/migration/register.h
+++ b/include/migration/register.h
@@ -14,6 +14,8 @@
 #ifndef MIGRATION_REGISTER_H
 #define MIGRATION_REGISTER_H
 
+#include "hw/vmstate-if.h"
+
 typedef struct SaveVMHandlers {
     /* This runs inside the iothread lock.  */
     SaveStateHandler *save_state;
@@ -74,6 +76,6 @@ int register_savevm_live(const char *idstr,
                          const SaveVMHandlers *ops,
                          void *opaque);
 
-void unregister_savevm(DeviceState *dev, const char *idstr, void *opaque);
+void unregister_savevm(VMStateIf *obj, const char *idstr, void *opaque);
 
 #endif
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index ac4f46a67d..4aef72c426 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -27,6 +27,8 @@
 #ifndef QEMU_VMSTATE_H
 #define QEMU_VMSTATE_H
 
+#include "hw/vmstate-if.h"
+
 typedef struct VMStateInfo VMStateInfo;
 typedef struct VMStateField VMStateField;
 
@@ -1156,22 +1158,22 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
 bool vmstate_save_needed(const VMStateDescription *vmsd, void *opaque);
 
 /* Returns: 0 on success, -1 on failure */
-int vmstate_register_with_alias_id(DeviceState *dev, int instance_id,
+int vmstate_register_with_alias_id(VMStateIf *obj, int instance_id,
                                    const VMStateDescription *vmsd,
                                    void *base, int alias_id,
                                    int required_for_version,
                                    Error **errp);
 
 /* Returns: 0 on success, -1 on failure */
-static inline int vmstate_register(DeviceState *dev, int instance_id,
+static inline int vmstate_register(VMStateIf *obj, int instance_id,
                                    const VMStateDescription *vmsd,
                                    void *opaque)
 {
-    return vmstate_register_with_alias_id(dev, instance_id, vmsd,
+    return vmstate_register_with_alias_id(obj, instance_id, vmsd,
                                           opaque, -1, 0, NULL);
 }
 
-void vmstate_unregister(DeviceState *dev, const VMStateDescription *vmsd,
+void vmstate_unregister(VMStateIf *obj, const VMStateDescription *vmsd,
                         void *opaque);
 
 struct MemoryRegion;
diff --git a/include/qemu/dbus.h b/include/qemu/dbus.h
new file mode 100644
index 0000000000..9d591f9ee4
--- /dev/null
+++ b/include/qemu/dbus.h
@@ -0,0 +1,19 @@
+/*
+ * Helpers for using D-Bus
+ *
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef DBUS_H
+#define DBUS_H
+
+#include <gio/gio.h>
+
+GStrv qemu_dbus_get_queued_owners(GDBusConnection *connection,
+                                  const char *name,
+                                  Error **errp);
+
+#endif /* DBUS_H */
diff --git a/migration/savevm.c b/migration/savevm.c
index a71b930b91..59efc1981d 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -760,17 +760,17 @@ int register_savevm_live(const char *idstr,
     return 0;
 }
 
-void unregister_savevm(DeviceState *dev, const char *idstr, void *opaque)
+void unregister_savevm(VMStateIf *obj, const char *idstr, void *opaque)
 {
     SaveStateEntry *se, *new_se;
     char id[256] = "";
 
-    if (dev) {
-        char *path = qdev_get_dev_path(dev);
-        if (path) {
-            pstrcpy(id, sizeof(id), path);
+    if (obj) {
+        char *oid = vmstate_if_get_id(obj);
+        if (oid) {
+            pstrcpy(id, sizeof(id), oid);
             pstrcat(id, sizeof(id), "/");
-            g_free(path);
+            g_free(oid);
         }
     }
     pstrcat(id, sizeof(id), idstr);
@@ -784,7 +784,7 @@ void unregister_savevm(DeviceState *dev, const char *idstr, void *opaque)
     }
 }
 
-int vmstate_register_with_alias_id(DeviceState *dev, int instance_id,
+int vmstate_register_with_alias_id(VMStateIf *obj, int instance_id,
                                    const VMStateDescription *vmsd,
                                    void *opaque, int alias_id,
                                    int required_for_version,
@@ -802,8 +802,8 @@ int vmstate_register_with_alias_id(DeviceState *dev, int instance_id,
     se->vmsd = vmsd;
     se->alias_id = alias_id;
 
-    if (dev) {
-        char *id = qdev_get_dev_path(dev);
+    if (obj) {
+        char *id = vmstate_if_get_id(obj);
         if (id) {
             if (snprintf(se->idstr, sizeof(se->idstr), "%s/", id) >=
                 sizeof(se->idstr)) {
@@ -834,7 +834,7 @@ int vmstate_register_with_alias_id(DeviceState *dev, int instance_id,
     return 0;
 }
 
-void vmstate_unregister(DeviceState *dev, const VMStateDescription *vmsd,
+void vmstate_unregister(VMStateIf *obj, const VMStateDescription *vmsd,
                         void *opaque)
 {
     SaveStateEntry *se, *new_se;
diff --git a/stubs/vmstate.c b/stubs/vmstate.c
index e1e89b87f0..6951d9fdc5 100644
--- a/stubs/vmstate.c
+++ b/stubs/vmstate.c
@@ -3,7 +3,7 @@
 
 const VMStateDescription vmstate_dummy = {};
 
-int vmstate_register_with_alias_id(DeviceState *dev,
+int vmstate_register_with_alias_id(VMStateIf *obj,
                                    int instance_id,
                                    const VMStateDescription *vmsd,
                                    void *base, int alias_id,
@@ -13,7 +13,7 @@ int vmstate_register_with_alias_id(DeviceState *dev,
     return 0;
 }
 
-void vmstate_unregister(DeviceState *dev,
+void vmstate_unregister(VMStateIf *obj,
                         const VMStateDescription *vmsd,
                         void *opaque)
 {
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 9146e1bdee..49e3b0d319 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -158,12 +158,17 @@ check-qtest-generic-$(CONFIG_MODULES) += tests/modules-test$(EXESUF)
 
 check-qtest-generic-y += tests/device-introspect-test$(EXESUF)
 check-qtest-generic-y += tests/cdrom-test$(EXESUF)
+DBUS_DAEMON := $(shell which dbus-daemon 2>/dev/null)
+ifneq ($(GDBUS_CODEGEN),)
+ifneq ($(DBUS_DAEMON),)
+check-qtest-pci-$(CONFIG_GIO) += tests/dbus-vmstate-test$(EXESUF)
+endif
+endif
 
 check-qtest-pci-$(CONFIG_RTL8139_PCI) += tests/rtl8139-test$(EXESUF)
 check-qtest-pci-$(CONFIG_VGA) += tests/display-vga-test$(EXESUF)
 check-qtest-pci-$(CONFIG_HDA) += tests/intel-hda-test$(EXESUF)
 check-qtest-pci-$(CONFIG_IVSHMEM_DEVICE) += tests/ivshmem-test$(EXESUF)
-
 check-qtest-i386-$(CONFIG_ISA_TESTDEV) = tests/endianness-test$(EXESUF)
 check-qtest-i386-y += tests/fdc-test$(EXESUF)
 check-qtest-i386-y += tests/ide-test$(EXESUF)
@@ -579,6 +584,7 @@ tests/test-qdev-global-props$(EXESUF): tests/test-qdev-global-props.o \
 	hw/core/irq.o \
 	hw/core/fw-path-provider.o \
 	hw/core/reset.o \
+	hw/core/vmstate-if.o \
 	$(test-qapi-obj-y)
 tests/test-vmstate$(EXESUF): tests/test-vmstate.o \
 	migration/vmstate.o migration/vmstate-types.o migration/qemu-file.o \
@@ -633,6 +639,19 @@ tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.jso
 	@mv tests/qapi-schema/doc-good-qapi-doc.texi $@
 	@rm -f tests/qapi-schema/doc-good-qapi-*.[ch] tests/qapi-schema/doc-good-qmp-*.[ch]
 
+tests/dbus-vmstate1.h tests/dbus-vmstate1.c: tests/dbus-vmstate1-gen-timestamp ;
+tests/dbus-vmstate1-gen-timestamp: $(SRC_PATH)/tests/dbus-vmstate1.xml
+	$(call quiet-command,$(GDBUS_CODEGEN) $< \
+		--interface-prefix org.qemu --generate-c-code tests/dbus-vmstate1, \
+		"GEN","$(@:%-timestamp=%)")
+	@>$@
+
+tests/dbus-vmstate-test.o-cflags := -DSRCDIR="$(SRC_PATH)"
+tests/dbus-vmstate1.o-cflags := $(GIO_CFLAGS)
+tests/dbus-vmstate1.o-libs := $(GIO_LIBS)
+
+tests/dbus-vmstate-test.o: tests/dbus-vmstate1.h
+
 tests/test-string-output-visitor$(EXESUF): tests/test-string-output-visitor.o $(test-qapi-obj-y)
 tests/test-string-input-visitor$(EXESUF): tests/test-string-input-visitor.o $(test-qapi-obj-y)
 tests/test-qmp-event$(EXESUF): tests/test-qmp-event.o $(test-qapi-obj-y) tests/test-qapi-events.o
@@ -826,7 +845,7 @@ tests/usb-hcd-uhci-test$(EXESUF): tests/usb-hcd-uhci-test.o $(libqos-usb-obj-y)
 tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-usb-obj-y)
 tests/usb-hcd-xhci-test$(EXESUF): tests/usb-hcd-xhci-test.o $(libqos-usb-obj-y)
 tests/cpu-plug-test$(EXESUF): tests/cpu-plug-test.o
-tests/migration-test$(EXESUF): tests/migration-test.o
+tests/migration-test$(EXESUF): tests/migration-test.o tests/migration-helpers.o
 tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o
 tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o $(test-util-obj-y)
 tests/test-keyval$(EXESUF): tests/test-keyval.o $(test-util-obj-y) $(test-qapi-obj-y)
@@ -836,6 +855,7 @@ tests/test-filter-mirror$(EXESUF): tests/test-filter-mirror.o $(qtest-obj-y)
 tests/test-filter-redirector$(EXESUF): tests/test-filter-redirector.o $(qtest-obj-y)
 tests/test-x86-cpuid-compat$(EXESUF): tests/test-x86-cpuid-compat.o $(qtest-obj-y)
 tests/ivshmem-test$(EXESUF): tests/ivshmem-test.o contrib/ivshmem-server/ivshmem-server.o $(libqos-pc-obj-y) $(libqos-spapr-obj-y)
+tests/dbus-vmstate-test$(EXESUF): tests/dbus-vmstate-test.o tests/migration-helpers.o tests/dbus-vmstate1.o $(libqos-pc-obj-y) $(libqos-spapr-obj-y)
 tests/vhost-user-bridge$(EXESUF): tests/vhost-user-bridge.o $(test-util-obj-y) libvhost-user.a
 tests/test-uuid$(EXESUF): tests/test-uuid.o $(test-util-obj-y)
 tests/test-arm-mptimer$(EXESUF): tests/test-arm-mptimer.o
@@ -1194,6 +1214,7 @@ check-clean:
 	rm -rf $(check-unit-y) tests/*.o $(QEMU_IOTESTS_HELPERS-y)
 	rm -rf $(sort $(foreach target,$(SYSEMU_TARGET_LIST), $(check-qtest-$(target)-y)) $(check-qtest-generic-y))
 	rm -f tests/test-qapi-gen-timestamp
+	rm -f tests/dbus-vmstate1-gen-timestamp
 	rm -rf $(TESTS_VENV_DIR) $(TESTS_RESULTS_DIR)
 
 clean: check-clean
diff --git a/tests/dbus-vmstate-daemon.sh b/tests/dbus-vmstate-daemon.sh
new file mode 100755
index 0000000000..474e250154
--- /dev/null
+++ b/tests/dbus-vmstate-daemon.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+# dbus-daemon wrapper script for dbus-vmstate testing
+#
+# This script allows to tweak the dbus-daemon policy during the test
+# to test different configurations.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+# Copyright (C) 2019 Red Hat, Inc.
+
+write_config()
+{
+    CONF="$1"
+    cat > "$CONF" <<EOF
+<busconfig>
+  <type>session</type>
+  <listen>unix:tmpdir=$DBUS_VMSTATE_TEST_TMPDIR</listen>
+
+  <policy context="default">
+     <!-- Holes must be punched in service configuration files for
+          name ownership and sending method calls -->
+     <deny own="*"/>
+     <deny send_type="method_call"/>
+
+     <!-- Signals and reply messages (method returns, errors) are allowed
+          by default -->
+     <allow send_type="signal"/>
+     <allow send_requested_reply="true" send_type="method_return"/>
+     <allow send_requested_reply="true" send_type="error"/>
+
+     <!-- All messages may be received by default -->
+     <allow receive_type="method_call"/>
+     <allow receive_type="method_return"/>
+     <allow receive_type="error"/>
+     <allow receive_type="signal"/>
+
+     <!-- Allow anyone to talk to the message bus -->
+     <allow send_destination="org.freedesktop.DBus"
+            send_interface="org.freedesktop.DBus" />
+     <allow send_destination="org.freedesktop.DBus"
+            send_interface="org.freedesktop.DBus.Introspectable"/>
+     <allow send_destination="org.freedesktop.DBus"
+            send_interface="org.freedesktop.DBus.Properties"/>
+     <!-- But disallow some specific bus services -->
+     <deny send_destination="org.freedesktop.DBus"
+           send_interface="org.freedesktop.DBus"
+           send_member="UpdateActivationEnvironment"/>
+     <deny send_destination="org.freedesktop.DBus"
+           send_interface="org.freedesktop.DBus.Debug.Stats"/>
+     <deny send_destination="org.freedesktop.DBus"
+           send_interface="org.freedesktop.systemd1.Activator"/>
+
+     <allow own="org.qemu.VMState1"/>
+     <allow send_destination="org.qemu.VMState1"/>
+     <allow receive_sender="org.qemu.VMState1"/>
+
+  </policy>
+
+  <include if_selinux_enabled="yes"
+   selinux_root_relative="yes">contexts/dbus_contexts</include>
+
+</busconfig>
+EOF
+}
+
+ARGS=
+for arg in "$@"
+do
+    case $arg in
+        --config-file=*)
+          CONF="${arg#*=}"
+          write_config "$CONF"
+          ARGS="$ARGS $1"
+          shift
+        ;;
+        *)
+          ARGS="$ARGS $1"
+          shift
+        ;;
+    esac
+done
+
+exec dbus-daemon $ARGS
diff --git a/tests/dbus-vmstate-test.c b/tests/dbus-vmstate-test.c
new file mode 100644
index 0000000000..2e5e47dec2
--- /dev/null
+++ b/tests/dbus-vmstate-test.c
@@ -0,0 +1,382 @@
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include "libqtest.h"
+#include "qemu-common.h"
+#include "dbus-vmstate1.h"
+#include "migration-helpers.h"
+
+static char *workdir;
+
+typedef struct TestServerId {
+    const char *name;
+    const char *data;
+    size_t size;
+} TestServerId;
+
+static const TestServerId idA = {
+    "idA", "I'am\0idA!", sizeof("I'am\0idA!")
+};
+
+static const TestServerId idB = {
+    "idB", "I'am\0idB!", sizeof("I'am\0idB!")
+};
+
+typedef struct TestServer {
+    const TestServerId *id;
+    bool save_called;
+    bool load_called;
+} TestServer;
+
+typedef struct Test {
+    const char *id_list;
+    bool migrate_fail;
+    bool without_dst_b;
+    TestServer srcA;
+    TestServer dstA;
+    TestServer srcB;
+    TestServer dstB;
+    GMainLoop *loop;
+    QTestState *src_qemu;
+} Test;
+
+static gboolean
+vmstate_load(VMState1 *object, GDBusMethodInvocation *invocation,
+             const gchar *arg_data, gpointer user_data)
+{
+    TestServer *h = user_data;
+    g_autoptr(GVariant) var = NULL;
+    GVariant *args;
+    const uint8_t *data;
+    size_t size;
+
+    args = g_dbus_method_invocation_get_parameters(invocation);
+    var = g_variant_get_child_value(args, 0);
+    data = g_variant_get_fixed_array(var, &size, sizeof(char));
+    g_assert_cmpuint(size, ==, h->id->size);
+    g_assert(!memcmp(data, h->id->data, h->id->size));
+    h->load_called = true;
+
+    g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
+    return TRUE;
+}
+
+static gboolean
+vmstate_save(VMState1 *object, GDBusMethodInvocation *invocation,
+             gpointer user_data)
+{
+    TestServer *h = user_data;
+    GVariant *var;
+
+    var = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
+                                    h->id->data, h->id->size, sizeof(char));
+    g_dbus_method_invocation_return_value(invocation,
+                                          g_variant_new("(@ay)", var));
+    h->save_called = true;
+
+    return TRUE;
+}
+
+typedef struct WaitNamed {
+    GMainLoop *loop;
+    bool named;
+} WaitNamed;
+
+static void
+named_cb(GDBusConnection *connection,
+         const gchar *name,
+         gpointer user_data)
+{
+    WaitNamed *t = user_data;
+
+    t->named = true;
+    g_main_loop_quit(t->loop);
+}
+
+static GDBusConnection *
+get_connection(Test *test, guint *ownid)
+{
+    g_autofree gchar *addr = NULL;
+    WaitNamed *wait;
+    GError *err = NULL;
+    GDBusConnection *c;
+
+    wait = g_new0(WaitNamed, 1);
+    wait->loop = test->loop;
+    addr = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, NULL, &err);
+    g_assert_no_error(err);
+
+    c = g_dbus_connection_new_for_address_sync(
+        addr,
+        G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
+        G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+        NULL, NULL, &err);
+    g_assert_no_error(err);
+    *ownid = g_bus_own_name_on_connection(c, "org.qemu.VMState1",
+                                          G_BUS_NAME_OWNER_FLAGS_NONE,
+                                          named_cb, named_cb, wait, g_free);
+    if (!wait->named) {
+        g_main_loop_run(wait->loop);
+    }
+
+    return c;
+}
+
+static GDBusObjectManagerServer *
+get_server(GDBusConnection *conn, TestServer *s, const TestServerId *id)
+{
+    g_autoptr(GDBusObjectSkeleton) sk = NULL;
+    g_autoptr(VMState1Skeleton) v = NULL;
+    GDBusObjectManagerServer *os;
+
+    s->id = id;
+    os = g_dbus_object_manager_server_new("/org/qemu");
+    sk = g_dbus_object_skeleton_new("/org/qemu/VMState1");
+
+    v = VMSTATE1_SKELETON(vmstate1_skeleton_new());
+    g_object_set(v, "id", id->name, NULL);
+
+    g_signal_connect(v, "handle-load", G_CALLBACK(vmstate_load), s);
+    g_signal_connect(v, "handle-save", G_CALLBACK(vmstate_save), s);
+
+    g_dbus_object_skeleton_add_interface(sk, G_DBUS_INTERFACE_SKELETON(v));
+    g_dbus_object_manager_server_export(os, sk);
+    g_dbus_object_manager_server_set_connection(os, conn);
+
+    return os;
+}
+
+static void
+set_id_list(Test *test, QTestState *s)
+{
+    if (!test->id_list) {
+        return;
+    }
+
+    g_assert(!qmp_rsp_is_err(qtest_qmp(s,
+        "{ 'execute': 'qom-set', 'arguments': "
+        "{ 'path': '/objects/dv', 'property': 'id-list', 'value': %s } }",
+        test->id_list)));
+}
+
+static gpointer
+dbus_vmstate_thread(gpointer data)
+{
+    GMainLoop *loop = data;
+
+    g_main_loop_run(loop);
+
+    return NULL;
+}
+
+static void
+test_dbus_vmstate(Test *test)
+{
+    g_autofree char *src_qemu_args = NULL;
+    g_autofree char *dst_qemu_args = NULL;
+    g_autoptr(GTestDBus) srcbus = NULL;
+    g_autoptr(GTestDBus) dstbus = NULL;
+    g_autoptr(GDBusConnection) srcconnA = NULL;
+    g_autoptr(GDBusConnection) srcconnB = NULL;
+    g_autoptr(GDBusConnection) dstconnA = NULL;
+    g_autoptr(GDBusConnection) dstconnB = NULL;
+    g_autoptr(GDBusObjectManagerServer) srcserverA = NULL;
+    g_autoptr(GDBusObjectManagerServer) srcserverB = NULL;
+    g_autoptr(GDBusObjectManagerServer) dstserverA = NULL;
+    g_autoptr(GDBusObjectManagerServer) dstserverB = NULL;
+    g_auto(GStrv) srcaddr = NULL;
+    g_auto(GStrv) dstaddr = NULL;
+    g_autoptr(GThread) thread = NULL;
+    g_autoptr(GMainLoop) loop = NULL;
+    g_autofree char *uri = NULL;
+    QTestState *src_qemu = NULL, *dst_qemu = NULL;
+    guint ownsrcA, ownsrcB, owndstA, owndstB;
+
+    uri = g_strdup_printf("unix:%s/migsocket", workdir);
+
+    loop = g_main_loop_new(NULL, FALSE);
+    test->loop = loop;
+
+    srcbus = g_test_dbus_new(G_TEST_DBUS_NONE);
+    g_test_dbus_up(srcbus);
+    srcconnA = get_connection(test, &ownsrcA);
+    srcserverA = get_server(srcconnA, &test->srcA, &idA);
+    srcconnB = get_connection(test, &ownsrcB);
+    srcserverB = get_server(srcconnB, &test->srcB, &idB);
+
+    /* remove ,guid=foo part */
+    srcaddr = g_strsplit(g_test_dbus_get_bus_address(srcbus), ",", 2);
+    src_qemu_args =
+        g_strdup_printf("-object dbus-vmstate,id=dv,addr=%s", srcaddr[0]);
+
+    dstbus = g_test_dbus_new(G_TEST_DBUS_NONE);
+    g_test_dbus_up(dstbus);
+    dstconnA = get_connection(test, &owndstA);
+    dstserverA = get_server(dstconnA, &test->dstA, &idA);
+    if (!test->without_dst_b) {
+        dstconnB = get_connection(test, &owndstB);
+        dstserverB = get_server(dstconnB, &test->dstB, &idB);
+    }
+
+    dstaddr = g_strsplit(g_test_dbus_get_bus_address(dstbus), ",", 2);
+    dst_qemu_args =
+        g_strdup_printf("-object dbus-vmstate,id=dv,addr=%s -incoming %s",
+                        dstaddr[0], uri);
+
+    src_qemu = qtest_init(src_qemu_args);
+    dst_qemu = qtest_init(dst_qemu_args);
+    set_id_list(test, src_qemu);
+    set_id_list(test, dst_qemu);
+
+    thread = g_thread_new("dbus-vmstate-thread", dbus_vmstate_thread, loop);
+
+    migrate_qmp(src_qemu, uri, "{}");
+    test->src_qemu = src_qemu;
+    if (test->migrate_fail) {
+        wait_for_migration_fail(src_qemu, true);
+        qtest_set_expected_status(dst_qemu, 1);
+    } else {
+        wait_for_migration_complete(src_qemu);
+    }
+
+    qtest_quit(dst_qemu);
+    qtest_quit(src_qemu);
+    g_bus_unown_name(ownsrcA);
+    g_bus_unown_name(ownsrcB);
+    g_bus_unown_name(owndstA);
+    if (!test->without_dst_b) {
+        g_bus_unown_name(owndstB);
+    }
+
+    g_main_loop_quit(test->loop);
+}
+
+static void
+check_not_migrated(TestServer *s, TestServer *d)
+{
+    assert(!s->save_called);
+    assert(!s->load_called);
+    assert(!d->save_called);
+    assert(!d->load_called);
+}
+
+static void
+check_migrated(TestServer *s, TestServer *d)
+{
+    assert(s->save_called);
+    assert(!s->load_called);
+    assert(!d->save_called);
+    assert(d->load_called);
+}
+
+static void
+test_dbus_vmstate_without_list(void)
+{
+    Test test = { 0, };
+
+    test_dbus_vmstate(&test);
+
+    check_migrated(&test.srcA, &test.dstA);
+    check_migrated(&test.srcB, &test.dstB);
+}
+
+static void
+test_dbus_vmstate_with_list(void)
+{
+    Test test = { .id_list = "idA,idB" };
+
+    test_dbus_vmstate(&test);
+
+    check_migrated(&test.srcA, &test.dstA);
+    check_migrated(&test.srcB, &test.dstB);
+}
+
+static void
+test_dbus_vmstate_only_a(void)
+{
+    Test test = { .id_list = "idA" };
+
+    test_dbus_vmstate(&test);
+
+    check_migrated(&test.srcA, &test.dstA);
+    check_not_migrated(&test.srcB, &test.dstB);
+}
+
+static void
+test_dbus_vmstate_missing_src(void)
+{
+    Test test = { .id_list = "idA,idC", .migrate_fail = true };
+
+    /* run in subprocess to silence QEMU error reporting */
+    if (g_test_subprocess()) {
+        test_dbus_vmstate(&test);
+        check_not_migrated(&test.srcA, &test.dstA);
+        check_not_migrated(&test.srcB, &test.dstB);
+        return;
+    }
+
+    g_test_trap_subprocess(NULL, 0, 0);
+    g_test_trap_assert_passed();
+}
+
+static void
+test_dbus_vmstate_missing_dst(void)
+{
+    Test test = { .id_list = "idA,idB",
+                  .without_dst_b = true,
+                  .migrate_fail = true };
+
+    /* run in subprocess to silence QEMU error reporting */
+    if (g_test_subprocess()) {
+        test_dbus_vmstate(&test);
+        assert(test.srcA.save_called);
+        assert(test.srcB.save_called);
+        assert(!test.dstB.save_called);
+        return;
+    }
+
+    g_test_trap_subprocess(NULL, 0, 0);
+    g_test_trap_assert_passed();
+}
+
+int
+main(int argc, char **argv)
+{
+    GError *err = NULL;
+    g_autofree char *dbus_daemon = NULL;
+    int ret;
+
+    dbus_daemon = g_build_filename(G_STRINGIFY(SRCDIR),
+                                   "tests",
+                                   "dbus-vmstate-daemon.sh",
+                                   NULL);
+    g_setenv("G_TEST_DBUS_DAEMON", dbus_daemon, true);
+
+    g_test_init(&argc, &argv, NULL);
+
+    workdir = g_dir_make_tmp("dbus-vmstate-test-XXXXXX", &err);
+    if (!workdir) {
+        g_error("Unable to create temporary dir: %s\n", err->message);
+        exit(1);
+    }
+
+    g_setenv("DBUS_VMSTATE_TEST_TMPDIR", workdir, true);
+
+    qtest_add_func("/dbus-vmstate/without-list",
+                   test_dbus_vmstate_without_list);
+    qtest_add_func("/dbus-vmstate/with-list",
+                   test_dbus_vmstate_with_list);
+    qtest_add_func("/dbus-vmstate/only-a",
+                   test_dbus_vmstate_only_a);
+    qtest_add_func("/dbus-vmstate/missing-src",
+                   test_dbus_vmstate_missing_src);
+    qtest_add_func("/dbus-vmstate/missing-dst",
+                   test_dbus_vmstate_missing_dst);
+
+    ret = g_test_run();
+
+    rmdir(workdir);
+    g_free(workdir);
+
+    return ret;
+}
diff --git a/tests/dbus-vmstate1.xml b/tests/dbus-vmstate1.xml
new file mode 100644
index 0000000000..cc8563be4c
--- /dev/null
+++ b/tests/dbus-vmstate1.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
+  <interface name="org.qemu.VMState1">
+    <property name="Id" type="s" access="read"/>
+    <method name="Load">
+      <arg type="ay" name="data" direction="in"/>
+    </method>
+    <method name="Save">
+      <arg type="ay" name="data" direction="out"/>
+    </method>
+  </interface>
+</node>
diff --git a/tests/docker/dockerfiles/centos7.docker b/tests/docker/dockerfiles/centos7.docker
index 953637065c..562d65be9e 100644
--- a/tests/docker/dockerfiles/centos7.docker
+++ b/tests/docker/dockerfiles/centos7.docker
@@ -8,6 +8,7 @@ ENV PACKAGES \
     bzip2-devel \
     ccache \
     csnappy-devel \
+    dbus-daemon \
     flex \
     gcc-c++ \
     gcc \
diff --git a/tests/docker/dockerfiles/debian10.docker b/tests/docker/dockerfiles/debian10.docker
index dad498b52e..5de79ae552 100644
--- a/tests/docker/dockerfiles/debian10.docker
+++ b/tests/docker/dockerfiles/debian10.docker
@@ -21,6 +21,7 @@ RUN apt update && \
         build-essential \
         ca-certificates \
         clang \
+        dbus \
         flex \
         gettext \
         git \
diff --git a/tests/docker/dockerfiles/fedora.docker b/tests/docker/dockerfiles/fedora.docker
index 51d475e6be..987a3c170a 100644
--- a/tests/docker/dockerfiles/fedora.docker
+++ b/tests/docker/dockerfiles/fedora.docker
@@ -8,6 +8,7 @@ ENV PACKAGES \
     ccache \
     clang \
     cyrus-sasl-devel \
+    dbus-daemon \
     device-mapper-multipath-devel \
     findutils \
     flex \
diff --git a/tests/docker/dockerfiles/ubuntu.docker b/tests/docker/dockerfiles/ubuntu.docker
index 18f1100409..4177f33691 100644
--- a/tests/docker/dockerfiles/ubuntu.docker
+++ b/tests/docker/dockerfiles/ubuntu.docker
@@ -13,6 +13,7 @@ FROM ubuntu:19.04
 ENV PACKAGES flex bison \
     ccache \
     clang \
+    dbus \
     gcc \
     gettext \
     git \
diff --git a/tests/migration-helpers.c b/tests/migration-helpers.c
new file mode 100644
index 0000000000..516093b39a
--- /dev/null
+++ b/tests/migration-helpers.c
@@ -0,0 +1,167 @@
+/*
+ * QTest migration helpers
+ *
+ * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
+ *   based on the vhost-user-test.c that is:
+ *      Copyright (c) 2014 Virtual Open Systems Sarl.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/qmp/qjson.h"
+
+#include "migration-helpers.h"
+
+bool got_stop;
+
+static void stop_cb(void *opaque, const char *name, QDict *data)
+{
+    if (!strcmp(name, "STOP")) {
+        got_stop = true;
+    }
+}
+
+/*
+ * Events can get in the way of responses we are actually waiting for.
+ */
+QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...)
+{
+    va_list ap;
+
+    va_start(ap, command);
+    qtest_qmp_vsend_fds(who, &fd, 1, command, ap);
+    va_end(ap);
+
+    return qtest_qmp_receive_success(who, stop_cb, NULL);
+}
+
+/*
+ * Events can get in the way of responses we are actually waiting for.
+ */
+QDict *wait_command(QTestState *who, const char *command, ...)
+{
+    va_list ap;
+
+    va_start(ap, command);
+    qtest_qmp_vsend(who, command, ap);
+    va_end(ap);
+
+    return qtest_qmp_receive_success(who, stop_cb, NULL);
+}
+
+/*
+ * Send QMP command "migrate".
+ * Arguments are built from @fmt... (formatted like
+ * qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
+ */
+void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...)
+{
+    va_list ap;
+    QDict *args, *rsp;
+
+    va_start(ap, fmt);
+    args = qdict_from_vjsonf_nofail(fmt, ap);
+    va_end(ap);
+
+    g_assert(!qdict_haskey(args, "uri"));
+    qdict_put_str(args, "uri", uri);
+
+    rsp = qtest_qmp(who, "{ 'execute': 'migrate', 'arguments': %p}", args);
+
+    g_assert(qdict_haskey(rsp, "return"));
+    qobject_unref(rsp);
+}
+
+/*
+ * Note: caller is responsible to free the returned object via
+ * qobject_unref() after use
+ */
+QDict *migrate_query(QTestState *who)
+{
+    return wait_command(who, "{ 'execute': 'query-migrate' }");
+}
+
+/*
+ * Note: caller is responsible to free the returned object via
+ * g_free() after use
+ */
+static gchar *migrate_query_status(QTestState *who)
+{
+    QDict *rsp_return = migrate_query(who);
+    gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
+
+    g_assert(status);
+    qobject_unref(rsp_return);
+
+    return status;
+}
+
+static bool check_migration_status(QTestState *who, const char *goal,
+                                   const char **ungoals)
+{
+    bool ready;
+    char *current_status;
+    const char **ungoal;
+
+    current_status = migrate_query_status(who);
+    ready = strcmp(current_status, goal) == 0;
+    if (!ungoals) {
+        g_assert_cmpstr(current_status, !=, "failed");
+        /*
+         * If looking for a state other than completed,
+         * completion of migration would cause the test to
+         * hang.
+         */
+        if (strcmp(goal, "completed") != 0) {
+            g_assert_cmpstr(current_status, !=, "completed");
+        }
+    } else {
+        for (ungoal = ungoals; *ungoal; ungoal++) {
+            g_assert_cmpstr(current_status, !=,  *ungoal);
+        }
+    }
+    g_free(current_status);
+    return ready;
+}
+
+void wait_for_migration_status(QTestState *who,
+                               const char *goal, const char **ungoals)
+{
+    while (!check_migration_status(who, goal, ungoals)) {
+        usleep(1000);
+    }
+}
+
+void wait_for_migration_complete(QTestState *who)
+{
+    wait_for_migration_status(who, "completed", NULL);
+}
+
+void wait_for_migration_fail(QTestState *from, bool allow_active)
+{
+    QDict *rsp_return;
+    char *status;
+    bool failed;
+
+    do {
+        status = migrate_query_status(from);
+        bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
+            (allow_active && !strcmp(status, "active"));
+        if (!result) {
+            fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
+                    __func__, status, allow_active);
+        }
+        g_assert(result);
+        failed = !strcmp(status, "failed");
+        g_free(status);
+    } while (!failed);
+
+    /* Is the machine currently running? */
+    rsp_return = wait_command(from, "{ 'execute': 'query-status' }");
+    g_assert(qdict_haskey(rsp_return, "running"));
+    g_assert(qdict_get_bool(rsp_return, "running"));
+    qobject_unref(rsp_return);
+}
diff --git a/tests/migration-helpers.h b/tests/migration-helpers.h
new file mode 100644
index 0000000000..a11808b3b7
--- /dev/null
+++ b/tests/migration-helpers.h
@@ -0,0 +1,37 @@
+/*
+ * QTest migration helpers
+ *
+ * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
+ *   based on the vhost-user-test.c that is:
+ *      Copyright (c) 2014 Virtual Open Systems Sarl.
+ *
+ * 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 MIGRATION_HELPERS_H_
+#define MIGRATION_HELPERS_H_
+
+#include "libqtest.h"
+
+extern bool got_stop;
+
+GCC_FMT_ATTR(3, 4)
+QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...);
+
+GCC_FMT_ATTR(2, 3)
+QDict *wait_command(QTestState *who, const char *command, ...);
+
+GCC_FMT_ATTR(3, 4)
+void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...);
+
+QDict *migrate_query(QTestState *who);
+
+void wait_for_migration_status(QTestState *who,
+                               const char *goal, const char **ungoals);
+
+void wait_for_migration_complete(QTestState *who);
+
+void wait_for_migration_fail(QTestState *from, bool allow_active);
+
+#endif /* MIGRATION_HELPERS_H_ */
diff --git a/tests/migration-test.c b/tests/migration-test.c
index e56e6dcb00..53afec4395 100644
--- a/tests/migration-test.c
+++ b/tests/migration-test.c
@@ -14,7 +14,6 @@
 
 #include "libqtest.h"
 #include "qapi/qmp/qdict.h"
-#include "qapi/qmp/qjson.h"
 #include "qemu/module.h"
 #include "qemu/option.h"
 #include "qemu/range.h"
@@ -24,6 +23,7 @@
 #include "qapi/qobject-input-visitor.h"
 #include "qapi/qobject-output-visitor.h"
 
+#include "migration-helpers.h"
 #include "migration/migration-test.h"
 
 /* TODO actually test the results and get rid of this */
@@ -31,7 +31,6 @@
 
 unsigned start_address;
 unsigned end_address;
-bool got_stop;
 static bool uffd_feature_thread_id;
 
 #if defined(__linux__)
@@ -157,67 +156,6 @@ static void wait_for_serial(const char *side)
     } while (true);
 }
 
-static void stop_cb(void *opaque, const char *name, QDict *data)
-{
-    if (!strcmp(name, "STOP")) {
-        got_stop = true;
-    }
-}
-
-/*
- * Events can get in the way of responses we are actually waiting for.
- */
-GCC_FMT_ATTR(3, 4)
-static QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...)
-{
-    va_list ap;
-
-    va_start(ap, command);
-    qtest_qmp_vsend_fds(who, &fd, 1, command, ap);
-    va_end(ap);
-
-    return qtest_qmp_receive_success(who, stop_cb, NULL);
-}
-
-/*
- * Events can get in the way of responses we are actually waiting for.
- */
-GCC_FMT_ATTR(2, 3)
-static QDict *wait_command(QTestState *who, const char *command, ...)
-{
-    va_list ap;
-
-    va_start(ap, command);
-    qtest_qmp_vsend(who, command, ap);
-    va_end(ap);
-
-    return qtest_qmp_receive_success(who, stop_cb, NULL);
-}
-
-/*
- * Note: caller is responsible to free the returned object via
- * qobject_unref() after use
- */
-static QDict *migrate_query(QTestState *who)
-{
-    return wait_command(who, "{ 'execute': 'query-migrate' }");
-}
-
-/*
- * Note: caller is responsible to free the returned object via
- * g_free() after use
- */
-static gchar *migrate_query_status(QTestState *who)
-{
-    QDict *rsp_return = migrate_query(who);
-    gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
-
-    g_assert(status);
-    qobject_unref(rsp_return);
-
-    return status;
-}
-
 /*
  * It's tricky to use qemu's migration event capability with qtest,
  * events suddenly appearing confuse the qmp()/hmp() responses.
@@ -265,48 +203,6 @@ static void read_blocktime(QTestState *who)
     qobject_unref(rsp_return);
 }
 
-static bool check_migration_status(QTestState *who, const char *goal,
-                                   const char **ungoals)
-{
-    bool ready;
-    char *current_status;
-    const char **ungoal;
-
-    current_status = migrate_query_status(who);
-    ready = strcmp(current_status, goal) == 0;
-    if (!ungoals) {
-        g_assert_cmpstr(current_status, !=, "failed");
-        /*
-         * If looking for a state other than completed,
-         * completion of migration would cause the test to
-         * hang.
-         */
-        if (strcmp(goal, "completed") != 0) {
-            g_assert_cmpstr(current_status, !=, "completed");
-        }
-    } else {
-        for (ungoal = ungoals; *ungoal; ungoal++) {
-            g_assert_cmpstr(current_status, !=,  *ungoal);
-        }
-    }
-    g_free(current_status);
-    return ready;
-}
-
-static void wait_for_migration_status(QTestState *who,
-                                      const char *goal,
-                                      const char **ungoals)
-{
-    while (!check_migration_status(who, goal, ungoals)) {
-        usleep(1000);
-    }
-}
-
-static void wait_for_migration_complete(QTestState *who)
-{
-    wait_for_migration_status(who, "completed", NULL);
-}
-
 static void wait_for_migration_pass(QTestState *who)
 {
     uint64_t initial_pass = get_migration_pass(who);
@@ -506,30 +402,6 @@ static void migrate_set_capability(QTestState *who, const char *capability,
     qobject_unref(rsp);
 }
 
-/*
- * Send QMP command "migrate".
- * Arguments are built from @fmt... (formatted like
- * qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
- */
-GCC_FMT_ATTR(3, 4)
-static void migrate(QTestState *who, const char *uri, const char *fmt, ...)
-{
-    va_list ap;
-    QDict *args, *rsp;
-
-    va_start(ap, fmt);
-    args = qdict_from_vjsonf_nofail(fmt, ap);
-    va_end(ap);
-
-    g_assert(!qdict_haskey(args, "uri"));
-    qdict_put_str(args, "uri", uri);
-
-    rsp = qtest_qmp(who, "{ 'execute': 'migrate', 'arguments': %p}", args);
-
-    g_assert(qdict_haskey(rsp, "return"));
-    qobject_unref(rsp);
-}
-
 static void migrate_postcopy_start(QTestState *from, QTestState *to)
 {
     QDict *rsp;
@@ -800,7 +672,7 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
     /* Wait for the first serial output from the source */
     wait_for_serial("src_serial");
 
-    migrate(from, uri, "{}");
+    migrate_qmp(from, uri, "{}");
     g_free(uri);
 
     wait_for_migration_pass(from);
@@ -891,7 +763,7 @@ static void test_postcopy_recovery(void)
     wait_for_migration_status(from, "postcopy-paused",
                               (const char * []) { "failed", "active",
                                                   "completed", NULL });
-    migrate(from, uri, "{'resume': true}");
+    migrate_qmp(from, uri, "{'resume': true}");
     g_free(uri);
 
     /* Restore the postcopy bandwidth to unlimited */
@@ -900,32 +772,6 @@ static void test_postcopy_recovery(void)
     migrate_postcopy_complete(from, to);
 }
 
-static void wait_for_migration_fail(QTestState *from, bool allow_active)
-{
-    QDict *rsp_return;
-    char *status;
-    bool failed;
-
-    do {
-        status = migrate_query_status(from);
-        bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
-                 (allow_active && !strcmp(status, "active"));
-        if (!result) {
-            fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
-                    __func__, status, allow_active);
-        }
-        g_assert(result);
-        failed = !strcmp(status, "failed");
-        g_free(status);
-    } while (!failed);
-
-    /* Is the machine currently running? */
-    rsp_return = wait_command(from, "{ 'execute': 'query-status' }");
-    g_assert(qdict_haskey(rsp_return, "running"));
-    g_assert(qdict_get_bool(rsp_return, "running"));
-    qobject_unref(rsp_return);
-}
-
 static void test_baddest(void)
 {
     MigrateStart *args = migrate_start_new();
@@ -936,7 +782,7 @@ static void test_baddest(void)
     if (test_migrate_start(&from, &to, "tcp:0:0", args)) {
         return;
     }
-    migrate(from, "tcp:0:0", "{}");
+    migrate_qmp(from, "tcp:0:0", "{}");
     wait_for_migration_fail(from, false);
     test_migrate_end(from, to, false);
 }
@@ -963,7 +809,7 @@ static void test_precopy_unix(void)
     /* Wait for the first serial output from the source */
     wait_for_serial("src_serial");
 
-    migrate(from, uri, "{}");
+    migrate_qmp(from, uri, "{}");
 
     wait_for_migration_pass(from);
 
@@ -1000,7 +846,7 @@ static void test_ignore_shared(void)
     /* Wait for the first serial output from the source */
     wait_for_serial("src_serial");
 
-    migrate(from, uri, "{}");
+    migrate_qmp(from, uri, "{}");
 
     wait_for_migration_pass(from);
 
@@ -1047,7 +893,7 @@ static void test_xbzrle(const char *uri)
     /* Wait for the first serial output from the source */
     wait_for_serial("src_serial");
 
-    migrate(from, uri, "{}");
+    migrate_qmp(from, uri, "{}");
 
     wait_for_migration_pass(from);
 
@@ -1098,7 +944,7 @@ static void test_precopy_tcp(void)
 
     uri = migrate_get_socket_address(to, "socket-address");
 
-    migrate(from, uri, "{}");
+    migrate_qmp(from, uri, "{}");
 
     wait_for_migration_pass(from);
 
@@ -1167,7 +1013,7 @@ static void test_migrate_fd_proto(void)
     close(pair[1]);
 
     /* Start migration to the 2nd socket*/
-    migrate(from, "fd:fd-mig", "{}");
+    migrate_qmp(from, "fd:fd-mig", "{}");
 
     wait_for_migration_pass(from);
 
@@ -1222,7 +1068,7 @@ static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
     /* Wait for the first serial output from the source */
     wait_for_serial("src_serial");
 
-    migrate(from, uri, "{}");
+    migrate_qmp(from, uri, "{}");
 
     if (should_fail) {
         qtest_set_expected_status(to, 1);
@@ -1316,7 +1162,7 @@ static void test_migrate_auto_converge(void)
     /* Wait for the first serial output from the source */
     wait_for_serial("src_serial");
 
-    migrate(from, uri, "{}");
+    migrate_qmp(from, uri, "{}");
 
     /* Wait for throttling begins */
     percentage = 0;
diff --git a/util/Makefile.objs b/util/Makefile.objs
index 63599d62aa..11262aafaf 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -56,3 +56,6 @@ util-obj-$(call lnot,$(CONFIG_INOTIFY1)) += filemonitor-stub.o
 util-obj-$(CONFIG_LINUX) += vfio-helpers.o
 util-obj-$(CONFIG_POSIX) += drm.o
 util-obj-y += guest-random.o
+util-obj-$(CONFIG_GIO) += dbus.o
+dbus.o-cflags = $(GIO_CFLAGS)
+dbus.o-libs = $(GIO_LIBS)
diff --git a/util/dbus.c b/util/dbus.c
new file mode 100644
index 0000000000..9099dc5b4b
--- /dev/null
+++ b/util/dbus.c
@@ -0,0 +1,57 @@
+/*
+ * Helpers for using D-Bus
+ *
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/dbus.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+
+/*
+ * qemu_dbus_get_queued_owners() - return the list of queued unique names
+ * @connection: A GDBusConnection
+ * @name: a service name
+ *
+ * Return: a GStrv of unique names, or NULL on failure.
+ */
+GStrv
+qemu_dbus_get_queued_owners(GDBusConnection *connection, const char *name,
+                            Error **errp)
+{
+    g_autoptr(GDBusProxy) proxy = NULL;
+    g_autoptr(GVariant) result = NULL;
+    g_autoptr(GVariant) child = NULL;
+    g_autoptr(GError) err = NULL;
+
+    proxy = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
+                                  "org.freedesktop.DBus",
+                                  "/org/freedesktop/DBus",
+                                  "org.freedesktop.DBus",
+                                  NULL, &err);
+    if (!proxy) {
+        error_setg(errp, "Failed to create DBus proxy: %s", err->message);
+        return NULL;
+    }
+
+    result = g_dbus_proxy_call_sync(proxy, "ListQueuedOwners",
+                                    g_variant_new("(s)", name),
+                                    G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                    -1, NULL, &err);
+    if (!result) {
+        if (g_error_matches(err,
+                            G_DBUS_ERROR,
+                            G_DBUS_ERROR_NAME_HAS_NO_OWNER)) {
+            return g_new0(char *, 1);
+        }
+        error_setg(errp, "Failed to call ListQueuedOwners: %s", err->message);
+        return NULL;
+    }
+
+    child = g_variant_get_child_value(result, 0);
+    return g_variant_dup_strv(child, NULL);
+}