new file mode 100644
@@ -0,0 +1,79 @@
+/*
+ * QEMU yank feature
+ *
+ * Copyright (c) Lukas Straub <lukasstraub2@web.de>
+ *
+ * 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 YANK_H
+#define YANK_H
+
+typedef void (YankFn) (void *opaque);
+
+/**
+ * yank_register_instance: Register a new instance.
+ *
+ * This registers a new instance for yanking. Must be called before any yank
+ * function is registered for this instance.
+ *
+ * This function is thread-safe.
+ *
+ * @instance_name: The globally unique name of the instance.
+ */
+void yank_register_instance(const char *instance_name);
+
+/**
+ * yank_unregister_instance: Unregister a instance.
+ *
+ * This unregisters a instance. Must be called only after every yank function
+ * of the instance has been unregistered.
+ *
+ * This function is thread-safe.
+ *
+ * @instance_name: The name of the instance.
+ */
+void yank_unregister_instance(const char *instance_name);
+
+/**
+ * yank_register_function: Register a yank function
+ *
+ * This registers a yank function. All limitations of qmp oob commands apply
+ * to the yank function as well.
+ *
+ * This function is thread-safe.
+ *
+ * @instance_name: The name of the instance
+ * @func: The yank function
+ * @opaque: Will be passed to the yank function
+ */
+void yank_register_function(const char *instance_name,
+ YankFn *func,
+ void *opaque);
+
+/**
+ * yank_unregister_function: Unregister a yank function
+ *
+ * This unregisters a yank function.
+ *
+ * This function is thread-safe.
+ *
+ * @instance_name: The name of the instance
+ * @func: func that was passed to yank_register_function
+ * @opaque: opaque that was passed to yank_register_function
+ */
+void yank_unregister_function(const char *instance_name,
+ YankFn *func,
+ void *opaque);
+
+/**
+ * yank_unregister_function: Generic yank function for iochannel
+ *
+ * This is a generic yank function which will call qio_channel_shutdown on the
+ * provided QIOChannel.
+ *
+ * @opaque: QIOChannel to shutdown
+ */
+void yank_generic_iochannel(void *opaque);
+#endif
@@ -1614,3 +1614,48 @@
##
{ 'command': 'query-vm-generation-id', 'returns': 'GuidInfo' }
+##
+# @YankInstances:
+#
+# @instances: List of yank instances.
+#
+# Yank instances are named after the following schema:
+# "blockdev:<node-name>", "chardev:<chardev-name>" and "migration"
+#
+# Since: 5.1
+##
+{ 'struct': 'YankInstances', 'data': {'instances': ['str'] } }
+
+##
+# @yank:
+#
+# Recover from hanging qemu by yanking the specified instances.
+#
+# Takes @YankInstances as argument.
+#
+# Returns: nothing.
+#
+# Example:
+#
+# -> { "execute": "yank", "arguments": { "instances": ["blockdev:nbd0"] } }
+# <- { "return": {} }
+#
+# Since: 5.1
+##
+{ 'command': 'yank', 'data': 'YankInstances', 'allow-oob': true }
+
+##
+# @query-yank:
+#
+# Query yank instances.
+#
+# Returns: @YankInstances
+#
+# Example:
+#
+# -> { "execute": "query-yank" }
+# <- { "return": { "instances": ["blockdev:nbd0"] } }
+#
+# Since: 5.1
+##
+{ 'command': 'query-yank', 'returns': 'YankInstances', 'allow-oob': true }
@@ -45,6 +45,7 @@ util-obj-$(CONFIG_GIO) += dbus.o
dbus.o-cflags = $(GIO_CFLAGS)
dbus.o-libs = $(GIO_LIBS)
util-obj-$(CONFIG_USER_ONLY) += selfmap.o
+util-obj-y += yank.o
#######################################################################
# code used by both qemu system emulation and qemu-img
new file mode 100644
@@ -0,0 +1,179 @@
+/*
+ * QEMU yank feature
+ *
+ * Copyright (c) Lukas Straub <lukasstraub2@web.de>
+ *
+ * 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/error.h"
+#include "qemu/thread.h"
+#include "qemu/queue.h"
+#include "qapi/qapi-commands-misc.h"
+#include "io/channel.h"
+#include "qemu/yank.h"
+
+struct YankFuncAndParam {
+ YankFn *func;
+ void *opaque;
+ QLIST_ENTRY(YankFuncAndParam) next;
+};
+
+struct YankInstance {
+ char *name;
+ QLIST_HEAD(, YankFuncAndParam) yankfns;
+ QLIST_ENTRY(YankInstance) next;
+};
+
+static QemuMutex lock;
+static QLIST_HEAD(yankinst_list, YankInstance) head
+ = QLIST_HEAD_INITIALIZER(head);
+
+static struct YankInstance *yank_find_instance(const char *name)
+{
+ struct YankInstance *tmp, *instance;
+ instance = NULL;
+ QLIST_FOREACH(tmp, &head, next) {
+ if (!strcmp(tmp->name, name)) {
+ instance = tmp;
+ }
+ }
+ return instance;
+}
+
+void yank_register_instance(const char *instance_name)
+{
+ struct YankInstance *instance;
+
+ qemu_mutex_lock(&lock);
+ assert(!yank_find_instance(instance_name));
+
+ instance = g_slice_new(struct YankInstance);
+ instance->name = g_strdup(instance_name);
+ QLIST_INIT(&instance->yankfns);
+ QLIST_INSERT_HEAD(&head, instance, next);
+
+ qemu_mutex_unlock(&lock);
+}
+
+void yank_unregister_instance(const char *instance_name)
+{
+ struct YankInstance *instance;
+
+ qemu_mutex_lock(&lock);
+ instance = yank_find_instance(instance_name);
+ assert(instance);
+
+ assert(QLIST_EMPTY(&instance->yankfns));
+ QLIST_REMOVE(instance, next);
+ g_free(instance->name);
+ g_slice_free(struct YankInstance, instance);
+
+ qemu_mutex_unlock(&lock);
+}
+
+void yank_register_function(const char *instance_name,
+ YankFn *func,
+ void *opaque)
+{
+ struct YankInstance *instance;
+ struct YankFuncAndParam *entry;
+
+ qemu_mutex_lock(&lock);
+ instance = yank_find_instance(instance_name);
+ assert(instance);
+
+ entry = g_slice_new(struct YankFuncAndParam);
+ entry->func = func;
+ entry->opaque = opaque;
+
+ QLIST_INSERT_HEAD(&instance->yankfns, entry, next);
+ qemu_mutex_unlock(&lock);
+}
+
+void yank_unregister_function(const char *instance_name,
+ YankFn *func,
+ void *opaque)
+{
+ struct YankInstance *instance;
+ struct YankFuncAndParam *entry;
+
+ qemu_mutex_lock(&lock);
+ instance = yank_find_instance(instance_name);
+ assert(instance);
+
+ QLIST_FOREACH(entry, &instance->yankfns, next) {
+ if (entry->func == func && entry->opaque == opaque) {
+ QLIST_REMOVE(entry, next);
+ g_slice_free(struct YankFuncAndParam, entry);
+ qemu_mutex_unlock(&lock);
+ return;
+ }
+ }
+
+ abort();
+}
+
+void yank_generic_iochannel(void *opaque)
+{
+ QIOChannel *ioc = QIO_CHANNEL(opaque);
+
+ qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+}
+
+void qmp_yank(strList *instances,
+ Error **errp)
+{
+ strList *tmp;
+ struct YankInstance *instance;
+ struct YankFuncAndParam *entry;
+
+ qemu_mutex_lock(&lock);
+ tmp = instances;
+ for (; tmp; tmp = tmp->next) {
+ instance = yank_find_instance(tmp->value);
+ if (!instance) {
+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
+ "Instance '%s' not found", tmp->value);
+ qemu_mutex_unlock(&lock);
+ return;
+ }
+ }
+ tmp = instances;
+ for (; tmp; tmp = tmp->next) {
+ instance = yank_find_instance(tmp->value);
+ assert(instance);
+ QLIST_FOREACH(entry, &instance->yankfns, next) {
+ entry->func(entry->opaque);
+ }
+ }
+ qemu_mutex_unlock(&lock);
+}
+
+YankInstances *qmp_query_yank(Error **errp)
+{
+ struct YankInstance *instance;
+ YankInstances *ret;
+
+ ret = g_new0(YankInstances, 1);
+ ret->instances = NULL;
+
+ qemu_mutex_lock(&lock);
+ QLIST_FOREACH(instance, &head, next) {
+ strList *entry;
+ entry = g_new0(strList, 1);
+ entry->value = g_strdup(instance->name);
+ entry->next = ret->instances;
+ ret->instances = entry;
+ }
+ qemu_mutex_unlock(&lock);
+
+ return ret;
+}
+
+static void __attribute__((__constructor__)) yank_init(void)
+{
+ qemu_mutex_init(&lock);
+}