summaryrefslogtreecommitdiff
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);
+}