This helper registers with the session bus and libvirt and ensures
that we properly save the state when the session exits. It does this
in two ways:
1) Whenever the session dbus connection is disconnected (typically due
to a logout) we save all domains in the session libvirtd.
2) Whenever there is a active domain in the session libvirtd we
take a shutdown inhibition lock[1] which means that systemd will
delay shutdown until we saved any active VMs. We then save the VM
when we get a PrepareForShutdown event (or when the session dies as
in 1).
[1]
http://www.freedesktop.org/wiki/Software/systemd/inhibit
---
tools/Makefile.am | 21 ++++
tools/libvirt-babysitter.c | 276 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 297 insertions(+)
create mode 100644 tools/libvirt-babysitter.c
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 0d7822d..eb3e0be 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -42,6 +42,10 @@ DISTCLEANFILES =
bin_SCRIPTS = virt-xml-validate virt-pki-validate
bin_PROGRAMS = virsh virt-host-validate
+if HAVE_DBUS
+bin_PROGRAMS += libvirt-babysitter
+endif
+
if HAVE_SANLOCK
sbin_SCRIPTS = virt-sanlock-cleanup
DISTCLEANFILES += virt-sanlock-cleanup
@@ -133,6 +137,23 @@ virsh_CFLAGS = \
$(COVERAGE_CFLAGS) \
$(LIBXML_CFLAGS) \
$(READLINE_CFLAGS)
+
+libvirt_babysitter_SOURCES = \
+ libvirt-babysitter.c \
+ $(NULL)
+libvirt_babysitter_LDFLAGS = $(WARN_LDFLAGS) $(COVERAGE_LDFLAGS)
+libvirt_babysitter_LDADD = \
+ $(STATIC_BINARIES) \
+ $(WARN_CFLAGS) \
+ ../src/libvirt.la \
+ ../src/libvirt_util.la \
+ $(NULL)
+libvirt_babysitter_CFLAGS = \
+ $(WARN_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(NULL)
+
BUILT_SOURCES =
if WITH_WIN_ICON
diff --git a/tools/libvirt-babysitter.c b/tools/libvirt-babysitter.c
new file mode 100644
index 0000000..8eec2eb
--- /dev/null
+++ b/tools/libvirt-babysitter.c
@@ -0,0 +1,276 @@
+#include <config.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <dbus/dbus.h>
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+#include "internal.h"
+#include "util.h"
+#include "virdbus.h"
+#include "libvirt/libvirt.h"
+#include "virterror_internal.h"
+#include "virfile.h"
+#include "memory.h"
+
+static virConnectPtr libvirtConn;
+static DBusConnection *sessionBus;
+static DBusConnection *systemBus;
+static int numActiveDomains;
+static bool hasInhibit;
+static int inhibitFd = -1;
+
+static void die(const char *format, ...) ATTRIBUTE_FMT_PRINTF(1, 2);
+
+static void die(const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ exit(EXIT_FAILURE);
+}
+
+static void libvirtErrorFunc(void *opaque ATTRIBUTE_UNUSED,
+ virErrorPtr err)
+{
+ fprintf(stderr, "libvirt Error: %s", err->message);
+}
+
+/* As per:
http://www.freedesktop.org/wiki/Software/systemd/inhibit */
+static int callInhibit(const char *what,
+ const char *who,
+ const char *why,
+ const char *mode)
+{
+ DBusMessage *message, *reply;
+ int fd;
+
+ if (systemBus == NULL)
+ return -1;
+
+ message = dbus_message_new_method_call ("org.freedesktop.login1",
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ "Inhibit");
+ if (message == NULL)
+ return -1;
+
+ dbus_message_append_args (message,
+ DBUS_TYPE_STRING, &what,
+ DBUS_TYPE_STRING, &who,
+ DBUS_TYPE_STRING, &why,
+ DBUS_TYPE_STRING, &mode,
+ DBUS_TYPE_INVALID);
+
+ reply = dbus_connection_send_with_reply_and_block (systemBus, message,
+ 25*1000, NULL);
+ dbus_message_unref (message);
+
+ if (reply == NULL)
+ return -1;
+
+ if (!dbus_message_get_args (reply, NULL,
+ DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID)) {
+ fd = -1;
+ }
+ dbus_message_unref (reply);
+
+ return fd;
+}
+
+
+static void numActiveDomainsChanged(void)
+{
+ if (numActiveDomains > 0 && !hasInhibit) {
+ inhibitFd = callInhibit("shutdown", _("Libvirt"),
_("Virtual machines need to be saved"), "delay");
+ hasInhibit = true;
+ } else if (numActiveDomains == 0 && hasInhibit) {
+ if (inhibitFd != -1) {
+ if (VIR_CLOSE (inhibitFd) < 0) {
+ virReportSystemError(errno, "%s", _("failed to close
file"));
+ }
+ inhibitFd = -1;
+ }
+ hasInhibit = false;
+ }
+}
+
+static void saveAllDomains(virConnectPtr conn)
+{
+ virDomainPtr dom;
+ int numDomains, i;
+ int *domains;
+ int state;
+ unsigned int *flags;
+
+ numDomains = virConnectNumOfDomains(conn);
+ if (numDomains < 0)
+ return;
+
+ if (VIR_ALLOC_N(domains, numDomains) < 0) {
+ virReportOOMError();
+ return;
+ }
+
+ if (VIR_ALLOC_N(flags, numDomains) < 0) {
+ VIR_FREE (domains);
+ virReportOOMError();
+ return;
+ }
+
+ numDomains = virConnectListDomains(conn, domains, numDomains);
+
+ /* First we pause all VMs to make them stop dirtying
+ pages, etc. We remember if any VMs were paused so
+ we can restore that on resume. */
+ for (i = 0 ; i < numDomains ; i++) {
+ flags[i] = VIR_DOMAIN_SAVE_RUNNING;
+ dom = virDomainLookupByID(conn, domains[i]);
+ if (dom != NULL) {
+ if (virDomainGetState (dom, &state, NULL, 0) == 0) {
+ if (state == VIR_DOMAIN_PAUSED) {
+ flags[i] = VIR_DOMAIN_SAVE_PAUSED;
+ }
+ }
+ virDomainSuspend (dom);
+ virDomainFree (dom);
+ }
+ }
+
+ /* Then we save the VMs to disk */
+ for (i = 0 ; i < numDomains ; i++) {
+ dom = virDomainLookupByID(conn, domains[i]);
+ if (dom != NULL) {
+ virDomainManagedSave (dom, flags[i]);
+ virDomainFree (dom);
+ }
+ }
+
+ VIR_FREE (domains);
+ VIR_FREE (flags);
+
+ if (inhibitFd != -1) {
+ if (VIR_CLOSE (inhibitFd) < 0) {
+ virReportSystemError(errno, "%s", _("failed to close
file"));
+ }
+ inhibitFd = -1;
+ }
+}
+
+static int lifecycleEventCallback(virConnectPtr conn ATTRIBUTE_UNUSED,
+ virDomainPtr dom ATTRIBUTE_UNUSED,
+ int event,
+ int detail ATTRIBUTE_UNUSED,
+ void *opaque ATTRIBUTE_UNUSED)
+{
+ if (event == VIR_DOMAIN_EVENT_STOPPED)
+ numActiveDomains--;
+ else if (event == VIR_DOMAIN_EVENT_STARTED)
+ numActiveDomains++;
+
+ numActiveDomainsChanged();
+
+ return 0;
+}
+
+static void initLibvirt(void)
+{
+ virSetErrorFunc(NULL, libvirtErrorFunc);
+ if (virInitialize() < 0)
+ die ("Can't init libvirt");
+
+ if (virEventRegisterDefaultImpl() < 0)
+ die ("Can't register event implementation");
+
+ libvirtConn = virConnectOpen("qemu+unix:///session");
+ if (libvirtConn == NULL)
+ die ("Can't connect to session libvirtd\n");
+}
+
+static void initTrackActiveDomains(void) {
+ numActiveDomains = virConnectNumOfDomains(libvirtConn);
+ virConnectDomainEventRegisterAny(libvirtConn,
+ NULL,
+ VIR_DOMAIN_EVENT_ID_LIFECYCLE,
+ VIR_DOMAIN_EVENT_CALLBACK (lifecycleEventCallback),
+ NULL, NULL);
+ numActiveDomainsChanged();
+}
+
+static DBusHandlerResult handleSessionMessageFunc(DBusConnection *connection
ATTRIBUTE_UNUSED,
+ DBusMessage *message,
+ void *userData
ATTRIBUTE_UNUSED)
+{
+ if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected"))
{
+ saveAllDomains (libvirtConn);
+ exit (EXIT_SUCCESS);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult handleSystemMessageFunc(DBusConnection *connection
ATTRIBUTE_UNUSED,
+ DBusMessage *message,
+ void *userData
ATTRIBUTE_UNUSED)
+{
+ if (dbus_message_is_signal(message, "org.freedesktop.login1.Manager",
"PrepareForShutdown")) {
+ saveAllDomains (libvirtConn);
+ exit (EXIT_SUCCESS);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int main (int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED)
+{
+ DBusError error;
+ int res;
+
+ initLibvirt ();
+
+ sessionBus = virDBusGetSessionBus ();
+ if (sessionBus == NULL)
+ die ("Can't connect to session bus: %s\n", error.message);
+
+ /* Register a unique name so that we can't avoid multiple
+ babysitters */
+ dbus_error_init(&error);
+ res = dbus_bus_request_name(sessionBus, "org.libvirt.BabySitter",
+ DBUS_NAME_FLAG_DO_NOT_QUEUE,
+ &error);
+
+ /* If we didn't get the name, someone else is handling this,
+ which is fine. */
+ if (res != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
+ return 0;
+
+ dbus_connection_add_filter(sessionBus,
+ handleSessionMessageFunc, NULL, NULL);
+
+ systemBus = virDBusGetSystemBus ();
+ /* Not fatal if we don't get this */
+
+ if (systemBus != NULL) {
+ dbus_connection_add_filter(systemBus,
+ handleSystemMessageFunc, NULL, NULL);
+ dbus_bus_add_match(systemBus,
+
"type='signal',sender='org.freedesktop.login1',
interface='org.freedesktop.login1.Manager'",
+ NULL);
+ }
+
+ initTrackActiveDomains ();
+
+ while (true) {
+ if (virEventRunDefaultImpl() < 0)
+ die ("main loop exited");
+ }
+
+ return EXIT_SUCCESS;
+}
--
1.7.12.1