/* * virnetdaemon.c * * Copyright (C) 2015 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * * Author: Martin Kletzander */ #include #include #include #include #include "virnetdaemon.h" #include "virlog.h" #include "viralloc.h" #include "virerror.h" #include "virthread.h" #include "virthreadpool.h" #include "virutil.h" #include "virfile.h" #include "virnetserver.h" #include "virnetservermdns.h" #include "virdbus.h" #include "virhash.h" #include "virstring.h" #include "virsystemd.h" #ifndef SA_SIGINFO # define SA_SIGINFO 0 #endif #define VIR_FROM_THIS VIR_FROM_RPC VIR_LOG_INIT("rpc.netdaemon"); typedef struct _virNetDaemonSignal virNetDaemonSignal; typedef virNetDaemonSignal *virNetDaemonSignalPtr; struct _virNetDaemonSignal { struct sigaction oldaction; int signum; virNetDaemonSignalFunc func; void *opaque; }; struct _virNetDaemon { virObjectLockable parent; bool privileged; size_t nsignals; virNetDaemonSignalPtr *signals; int sigread; int sigwrite; int sigwatch; virHashTablePtr servers; virJSONValuePtr srvObject; bool quit; unsigned int autoShutdownTimeout; size_t autoShutdownInhibitions; bool autoShutdownCallingInhibit; int autoShutdownInhibitFd; }; static virClassPtr virNetDaemonClass; static void virNetDaemonDispose(void *obj) { virNetDaemonPtr dmn = obj; size_t i; VIR_FORCE_CLOSE(dmn->autoShutdownInhibitFd); for (i = 0; i < dmn->nsignals; i++) { sigaction(dmn->signals[i]->signum, &dmn->signals[i]->oldaction, NULL); VIR_FREE(dmn->signals[i]); } VIR_FREE(dmn->signals); VIR_FORCE_CLOSE(dmn->sigread); VIR_FORCE_CLOSE(dmn->sigwrite); if (dmn->sigwatch > 0) virEventRemoveHandle(dmn->sigwatch); virHashFree(dmn->servers); virJSONValueFree(dmn->srvObject); } static int virNetDaemonOnceInit(void) { if (!(virNetDaemonClass = virClassNew(virClassForObjectLockable(), "virNetDaemon", sizeof(virNetDaemon), virNetDaemonDispose))) return -1; return 0; } VIR_ONCE_GLOBAL_INIT(virNetDaemon) virNetDaemonPtr virNetDaemonNew(void) { virNetDaemonPtr dmn; struct sigaction sig_action; if (virNetDaemonInitialize() < 0) return NULL; if (!(dmn = virObjectLockableNew(virNetDaemonClass))) return NULL; if (!(dmn->servers = virHashCreate(5, virObjectFreeHashData))) goto error; dmn->sigwrite = dmn->sigread = -1; dmn->privileged = geteuid() == 0; dmn->autoShutdownInhibitFd = -1; if (virEventRegisterDefaultImpl() < 0) goto error; memset(&sig_action, 0, sizeof(sig_action)); sig_action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sig_action, NULL); return dmn; error: virObjectUnref(dmn); return NULL; } int virNetDaemonAddServer(virNetDaemonPtr dmn, virNetServerPtr srv) { int ret = -1; const char *serverName = virNetServerGetName(srv); virObjectLock(dmn); if (virHashAddEntry(dmn->servers, serverName, srv) < 0) goto cleanup; virObjectRef(srv); ret = 0; cleanup: virObjectUnlock(dmn); return ret; } virNetServerPtr virNetDaemonGetServer(virNetDaemonPtr dmn, const char *serverName) { virNetServerPtr srv = NULL; virObjectLock(dmn); srv = virObjectRef(virHashLookup(dmn->servers, serverName)); virObjectUnlock(dmn); if (!srv) { virReportError(VIR_ERR_NO_SERVER, _("No server named '%s'"), serverName); } return srv; } struct collectData { virNetServerPtr **servers; size_t nservers; }; static int collectServers(void *payload, const void *name ATTRIBUTE_UNUSED, void *opaque) { virNetServerPtr srv = virObjectRef(payload); struct collectData *data = opaque; if (!srv) return -1; return VIR_APPEND_ELEMENT(*data->servers, data->nservers, srv); } /* * Returns number of names allocated in *servers, on error sets * *servers to NULL and returns -1. List of *servers must be free()d, * but not the items in it (similarly to virHashGetItems). */ ssize_t virNetDaemonGetServers(virNetDaemonPtr dmn, virNetServerPtr **servers) { struct collectData data = { servers, 0 }; ssize_t ret = -1; *servers = NULL; virObjectLock(dmn); if (virHashForEach(dmn->servers, collectServers, &data) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Cannot get all servers from daemon")); goto cleanup; } ret = data.nservers; cleanup: if (ret < 0) virObjectListFreeCount(*servers, data.nservers); virObjectUnlock(dmn); return ret; } virNetServerPtr virNetDaemonAddServerPostExec(virNetDaemonPtr dmn, const char *serverName, virNetServerClientPrivNew clientPrivNew, virNetServerClientPrivNewPostExecRestart clientPrivNewPostExecRestart, virNetServerClientPrivPreExecRestart clientPrivPreExecRestart, virFreeCallback clientPrivFree, void *clientPrivOpaque) { virJSONValuePtr object = NULL; virNetServerPtr srv = NULL; virObjectLock(dmn); if (!dmn->srvObject) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Cannot add more servers post-exec than " "there were pre-exec")); goto error; } if (virJSONValueIsArray(dmn->srvObject)) { object = virJSONValueArraySteal(dmn->srvObject, 0); if (virJSONValueArraySize(dmn->srvObject) == 0) { virJSONValueFree(dmn->srvObject); dmn->srvObject = NULL; } } else if (virJSONValueObjectGetByType(dmn->srvObject, "min_workers", VIR_JSON_TYPE_NUMBER)) { object = dmn->srvObject; dmn->srvObject = NULL; } else { int ret = virJSONValueObjectRemoveKey(dmn->srvObject, serverName, &object); if (ret != 1) { if (ret == 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Server '%s' not found in JSON"), serverName); } goto error; } if (virJSONValueObjectKeysNumber(dmn->srvObject) == 0) { virJSONValueFree(dmn->srvObject); dmn->srvObject = NULL; } } srv = virNetServerNewPostExecRestart(object, serverName, clientPrivNew, clientPrivNewPostExecRestart, clientPrivPreExecRestart, clientPrivFree, clientPrivOpaque); if (!srv) goto error; if (virHashAddEntry(dmn->servers, serverName, srv) < 0) goto error; virJSONValueFree(object); virObjectUnlock(dmn); return srv; error: virObjectUnlock(dmn); virObjectUnref(srv); virJSONValueFree(object); return NULL; } virNetDaemonPtr virNetDaemonNewPostExecRestart(virJSONValuePtr object) { virNetDaemonPtr dmn = NULL; virJSONValuePtr servers = virJSONValueObjectGet(object, "servers"); bool new_version = virJSONValueObjectHasKey(object, "servers"); if (!(dmn = virNetDaemonNew())) goto error; if (new_version && !servers) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Malformed servers data in JSON document")); goto error; } if (!(dmn->srvObject = virJSONValueCopy(new_version ? servers : object))) goto error; return dmn; error: virObjectUnref(dmn); return NULL; } static int daemonServerCompare(const virHashKeyValuePair *a, const virHashKeyValuePair *b) { const char *as = a->key; const char *bs = b->key; return strcmp(as, bs); } virJSONValuePtr virNetDaemonPreExecRestart(virNetDaemonPtr dmn) { size_t i = 0; virJSONValuePtr object = NULL; virJSONValuePtr srvObj = NULL; virHashKeyValuePairPtr srvArray = NULL; virObjectLock(dmn); if (!(object = virJSONValueNewObject())) goto error; if (!(srvObj = virJSONValueNewObject())) goto error; if (virJSONValueObjectAppend(object, "servers", srvObj) < 0) { virJSONValueFree(srvObj); goto error; } if (!(srvArray = virHashGetItems(dmn->servers, daemonServerCompare))) goto error; for (i = 0; srvArray[i].key; i++) { virNetServerPtr server = virHashLookup(dmn->servers, srvArray[i].key); virJSONValuePtr srvJSON; if (!server) goto error; srvJSON = virNetServerPreExecRestart(server); if (!srvJSON) goto error; if (virJSONValueObjectAppend(srvObj, srvArray[i].key, srvJSON) < 0) { virJSONValueFree(srvJSON); goto error; } } VIR_FREE(srvArray); virObjectUnlock(dmn); return object; error: VIR_FREE(srvArray); virJSONValueFree(object); virObjectUnlock(dmn); return NULL; } bool virNetDaemonIsPrivileged(virNetDaemonPtr dmn) { bool priv; virObjectLock(dmn); priv = dmn->privileged; virObjectUnlock(dmn); return priv; } void virNetDaemonAutoShutdown(virNetDaemonPtr dmn, unsigned int timeout) { virObjectLock(dmn); dmn->autoShutdownTimeout = timeout; virObjectUnlock(dmn); } #if defined(WITH_DBUS) && defined(DBUS_TYPE_UNIX_FD) static void virNetDaemonGotInhibitReply(DBusPendingCall *pending, void *opaque) { virNetDaemonPtr dmn = opaque; DBusMessage *reply; int fd; virObjectLock(dmn); dmn->autoShutdownCallingInhibit = false; VIR_DEBUG("dmn=%p", dmn); reply = dbus_pending_call_steal_reply(pending); if (reply == NULL) goto cleanup; if (dbus_message_get_args(reply, NULL, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID)) { if (dmn->autoShutdownInhibitions) { dmn->autoShutdownInhibitFd = fd; VIR_DEBUG("Got inhibit FD %d", fd); } else { /* We stopped the last VM since we made the inhibit call */ VIR_DEBUG("Closing inhibit FD %d", fd); VIR_FORCE_CLOSE(fd); } } virDBusMessageUnref(reply); cleanup: virObjectUnlock(dmn); dbus_pending_call_unref(pending); } /* As per: http://www.freedesktop.org/wiki/Software/systemd/inhibit */ static void virNetDaemonCallInhibit(virNetDaemonPtr dmn, const char *what, const char *who, const char *why, const char *mode) { DBusMessage *message; DBusPendingCall *pendingReply = NULL; DBusConnection *systemBus; VIR_DEBUG("dmn=%p what=%s who=%s why=%s mode=%s", dmn, NULLSTR(what), NULLSTR(who), NULLSTR(why), NULLSTR(mode)); if (!(systemBus = virDBusGetSystemBus())) return; /* Only one outstanding call at a time */ if (dmn->autoShutdownCallingInhibit) return; message = dbus_message_new_method_call("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "Inhibit"); if (message == NULL) return; dbus_message_append_args(message, DBUS_TYPE_STRING, &what, DBUS_TYPE_STRING, &who, DBUS_TYPE_STRING, &why, DBUS_TYPE_STRING, &mode, DBUS_TYPE_INVALID); if (dbus_connection_send_with_reply(systemBus, message, &pendingReply, 25 * 1000) && pendingReply) { if (dbus_pending_call_get_completed(pendingReply)) { virNetDaemonGotInhibitReply(pendingReply, dmn); } else { dbus_pending_call_set_notify(pendingReply, virNetDaemonGotInhibitReply, dmn, NULL); } dmn->autoShutdownCallingInhibit = true; } virDBusMessageUnref(message); } #endif void virNetDaemonAddShutdownInhibition(virNetDaemonPtr dmn) { virObjectLock(dmn); dmn->autoShutdownInhibitions++; VIR_DEBUG("dmn=%p inhibitions=%zu", dmn, dmn->autoShutdownInhibitions); #if defined(WITH_DBUS) && defined(DBUS_TYPE_UNIX_FD) if (dmn->autoShutdownInhibitions == 1) virNetDaemonCallInhibit(dmn, "shutdown", _("Libvirt"), _("Virtual machines need to be saved"), "delay"); #endif virObjectUnlock(dmn); } void virNetDaemonRemoveShutdownInhibition(virNetDaemonPtr dmn) { virObjectLock(dmn); dmn->autoShutdownInhibitions--; VIR_DEBUG("dmn=%p inhibitions=%zu", dmn, dmn->autoShutdownInhibitions); if (dmn->autoShutdownInhibitions == 0) { VIR_DEBUG("Closing inhibit FD %d", dmn->autoShutdownInhibitFd); VIR_FORCE_CLOSE(dmn->autoShutdownInhibitFd); } virObjectUnlock(dmn); } static sig_atomic_t sigErrors; static int sigLastErrno; static int sigWrite = -1; static void virNetDaemonSignalHandler(int sig, siginfo_t * siginfo, void* context ATTRIBUTE_UNUSED) { int origerrno; int r; siginfo_t tmp; if (SA_SIGINFO) tmp = *siginfo; else memset(&tmp, 0, sizeof(tmp)); /* set the sig num in the struct */ tmp.si_signo = sig; origerrno = errno; r = safewrite(sigWrite, &tmp, sizeof(tmp)); if (r == -1) { sigErrors++; sigLastErrno = errno; } errno = origerrno; } static void virNetDaemonSignalEvent(int watch, int fd ATTRIBUTE_UNUSED, int events ATTRIBUTE_UNUSED, void *opaque) { virNetDaemonPtr dmn = opaque; siginfo_t siginfo; size_t i; virObjectLock(dmn); if (saferead(dmn->sigread, &siginfo, sizeof(siginfo)) != sizeof(siginfo)) { virReportSystemError(errno, "%s", _("Failed to read from signal pipe")); virEventRemoveHandle(watch); dmn->sigwatch = -1; goto cleanup; } for (i = 0; i < dmn->nsignals; i++) { if (siginfo.si_signo == dmn->signals[i]->signum) { virNetDaemonSignalFunc func = dmn->signals[i]->func; void *funcopaque = dmn->signals[i]->opaque; virObjectUnlock(dmn); func(dmn, &siginfo, funcopaque); return; } } virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected signal received: %d"), siginfo.si_signo); cleanup: virObjectUnlock(dmn); } static int virNetDaemonSignalSetup(virNetDaemonPtr dmn) { int fds[2] = { -1, -1 }; if (dmn->sigwrite != -1) return 0; if (pipe2(fds, O_CLOEXEC|O_NONBLOCK) < 0) { virReportSystemError(errno, "%s", _("Unable to create signal pipe")); return -1; } if ((dmn->sigwatch = virEventAddHandle(fds[0], VIR_EVENT_HANDLE_READABLE, virNetDaemonSignalEvent, dmn, NULL)) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to add signal handle watch")); goto error; } dmn->sigread = fds[0]; dmn->sigwrite = fds[1]; sigWrite = fds[1]; return 0; error: VIR_FORCE_CLOSE(fds[0]); VIR_FORCE_CLOSE(fds[1]); return -1; } int virNetDaemonAddSignalHandler(virNetDaemonPtr dmn, int signum, virNetDaemonSignalFunc func, void *opaque) { virNetDaemonSignalPtr sigdata = NULL; struct sigaction sig_action; virObjectLock(dmn); if (virNetDaemonSignalSetup(dmn) < 0) goto error; if (VIR_EXPAND_N(dmn->signals, dmn->nsignals, 1) < 0) goto error; if (VIR_ALLOC(sigdata) < 0) goto error; sigdata->signum = signum; sigdata->func = func; sigdata->opaque = opaque; memset(&sig_action, 0, sizeof(sig_action)); sig_action.sa_sigaction = virNetDaemonSignalHandler; sig_action.sa_flags = SA_SIGINFO; sigemptyset(&sig_action.sa_mask); sigaction(signum, &sig_action, &sigdata->oldaction); dmn->signals[dmn->nsignals-1] = sigdata; virObjectUnlock(dmn); return 0; error: VIR_FREE(sigdata); virObjectUnlock(dmn); return -1; } static void virNetDaemonAutoShutdownTimer(int timerid ATTRIBUTE_UNUSED, void *opaque) { virNetDaemonPtr dmn = opaque; virObjectLock(dmn); if (!dmn->autoShutdownInhibitions) { VIR_DEBUG("Automatic shutdown triggered"); dmn->quit = true; } virObjectUnlock(dmn); } static int daemonServerUpdateServices(void *payload, const void *key ATTRIBUTE_UNUSED, void *opaque) { bool *enable = opaque; virNetServerPtr srv = payload; virNetServerUpdateServices(srv, *enable); return 0; } void virNetDaemonUpdateServices(virNetDaemonPtr dmn, bool enabled) { virObjectLock(dmn); virHashForEach(dmn->servers, daemonServerUpdateServices, &enabled); virObjectUnlock(dmn); } static int daemonServerRun(void *payload, const void *key ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) { virNetServerPtr srv = payload; return virNetServerStart(srv); }; static int daemonServerProcessClients(void *payload, const void *key ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) { virNetServerPtr srv = payload; virNetServerProcessClients(srv); return 0; } void virNetDaemonRun(virNetDaemonPtr dmn) { int timerid = -1; bool timerActive = false; virObjectLock(dmn); if (dmn->srvObject) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Not all servers restored, cannot run server")); goto cleanup; } if (virHashForEach(dmn->servers, daemonServerRun, NULL) < 0) goto cleanup; dmn->quit = false; if (dmn->autoShutdownTimeout && (timerid = virEventAddTimeout(-1, virNetDaemonAutoShutdownTimer, dmn, NULL)) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Failed to register shutdown timeout")); goto cleanup; } /* We are accepting connections now. Notify systemd * so it can start dependent services. */ virSystemdNotifyStartup(); VIR_DEBUG("dmn=%p quit=%d", dmn, dmn->quit); while (!dmn->quit) { /* A shutdown timeout is specified, so check * if any drivers have active state, if not * shutdown after timeout seconds */ if (dmn->autoShutdownTimeout) { if (timerActive) { if (virNetDaemonHasClients(dmn)) { VIR_DEBUG("Deactivating shutdown timer %d", timerid); virEventUpdateTimeout(timerid, -1); timerActive = false; } } else { if (!virNetDaemonHasClients(dmn)) { VIR_DEBUG("Activating shutdown timer %d", timerid); virEventUpdateTimeout(timerid, dmn->autoShutdownTimeout * 1000); timerActive = true; } } } virObjectUnlock(dmn); if (virEventRunDefaultImpl() < 0) { virObjectLock(dmn); VIR_DEBUG("Loop iteration error, exiting"); break; } virObjectLock(dmn); virHashForEach(dmn->servers, daemonServerProcessClients, NULL); } cleanup: virObjectUnlock(dmn); } void virNetDaemonQuit(virNetDaemonPtr dmn) { virObjectLock(dmn); VIR_DEBUG("Quit requested %p", dmn); dmn->quit = true; virObjectUnlock(dmn); } static int daemonServerClose(void *payload, const void *key ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) { virNetServerPtr srv = payload; virNetServerClose(srv); return 0; } void virNetDaemonClose(virNetDaemonPtr dmn) { if (!dmn) return; virObjectLock(dmn); virHashForEach(dmn->servers, daemonServerClose, NULL); virObjectUnlock(dmn); } static int daemonServerHasClients(void *payload, const void *key ATTRIBUTE_UNUSED, void *opaque) { bool *clients = opaque; virNetServerPtr srv = payload; if (virNetServerHasClients(srv)) *clients = true; return 0; } bool virNetDaemonHasClients(virNetDaemonPtr dmn) { bool ret = false; virHashForEach(dmn->servers, daemonServerHasClients, &ret); return ret; }