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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list