/*
* virnetserver.c: generic network RPC server
*
* Copyright (C) 2006-2012 Red Hat, Inc.
* Copyright (C) 2006 Daniel P. Berrange
*
* 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: Daniel P. Berrange
*/
#include
#include
#include
#include
#include "virnetserver.h"
#include "virlog.h"
#include "viralloc.h"
#include "virerror.h"
#include "virthread.h"
#include "virthreadpool.h"
#include "virutil.h"
#include "virfile.h"
#include "virnetservermdns.h"
#include "virdbus.h"
#include "virstring.h"
#ifndef SA_SIGINFO
# define SA_SIGINFO 0
#endif
#define VIR_FROM_THIS VIR_FROM_RPC
typedef struct _virNetServerSignal virNetServerSignal;
typedef virNetServerSignal *virNetServerSignalPtr;
struct _virNetServerSignal {
struct sigaction oldaction;
int signum;
virNetServerSignalFunc func;
void *opaque;
};
typedef struct _virNetServerJob virNetServerJob;
typedef virNetServerJob *virNetServerJobPtr;
struct _virNetServerJob {
virNetServerClientPtr client;
virNetMessagePtr msg;
virNetServerProgramPtr prog;
};
struct _virNetServer {
virObjectLockable parent;
virThreadPoolPtr workers;
bool privileged;
size_t nsignals;
virNetServerSignalPtr *signals;
int sigread;
int sigwrite;
int sigwatch;
char *mdnsGroupName;
virNetServerMDNSPtr mdns;
virNetServerMDNSGroupPtr mdnsGroup;
size_t nservices;
virNetServerServicePtr *services;
size_t nprograms;
virNetServerProgramPtr *programs;
size_t nclients;
size_t nclients_max;
virNetServerClientPtr *clients;
int keepaliveInterval;
unsigned int keepaliveCount;
bool keepaliveRequired;
unsigned int quit :1;
#ifdef WITH_GNUTLS
virNetTLSContextPtr tls;
#endif
unsigned int autoShutdownTimeout;
size_t autoShutdownInhibitions;
bool autoShutdownCallingInhibit;
int autoShutdownInhibitFd;
virNetServerClientPrivNew clientPrivNew;
virNetServerClientPrivPreExecRestart clientPrivPreExecRestart;
virFreeCallback clientPrivFree;
void *clientPrivOpaque;
};
static virClassPtr virNetServerClass;
static void virNetServerDispose(void *obj);
static void virNetServerUpdateServicesLocked(virNetServerPtr srv,
bool enabled);
static int virNetServerOnceInit(void)
{
if (!(virNetServerClass = virClassNew(virClassForObjectLockable(),
"virNetServer",
sizeof(virNetServer),
virNetServerDispose)))
return -1;
return 0;
}
VIR_ONCE_GLOBAL_INIT(virNetServer)
static int virNetServerProcessMsg(virNetServerPtr srv,
virNetServerClientPtr client,
virNetServerProgramPtr prog,
virNetMessagePtr msg)
{
int ret = -1;
if (!prog) {
/* Only send back an error for type == CALL. Other
* message types are not expecting replies, so we
* must just log it & drop them
*/
if (msg->header.type == VIR_NET_CALL ||
msg->header.type == VIR_NET_CALL_WITH_FDS) {
if (virNetServerProgramUnknownError(client,
msg,
&msg->header) < 0)
goto cleanup;
} else {
VIR_INFO("Dropping client mesage, unknown program %d version %d type %d proc %d",
msg->header.prog, msg->header.vers,
msg->header.type, msg->header.proc);
/* Send a dummy reply to free up 'msg' & unblock client rx */
virNetMessageClear(msg);
msg->header.type = VIR_NET_REPLY;
if (virNetServerClientSendMessage(client, msg) < 0)
goto cleanup;
}
goto done;
}
if (virNetServerProgramDispatch(prog,
srv,
client,
msg) < 0)
goto cleanup;
done:
ret = 0;
cleanup:
return ret;
}
static void virNetServerHandleJob(void *jobOpaque, void *opaque)
{
virNetServerPtr srv = opaque;
virNetServerJobPtr job = jobOpaque;
VIR_DEBUG("server=%p client=%p message=%p prog=%p",
srv, job->client, job->msg, job->prog);
if (virNetServerProcessMsg(srv, job->client, job->prog, job->msg) < 0)
goto error;
virObjectUnref(job->prog);
virObjectUnref(job->client);
VIR_FREE(job);
return;
error:
virObjectUnref(job->prog);
virNetMessageFree(job->msg);
virNetServerClientClose(job->client);
virObjectUnref(job->client);
VIR_FREE(job);
}
static int virNetServerDispatchNewMessage(virNetServerClientPtr client,
virNetMessagePtr msg,
void *opaque)
{
virNetServerPtr srv = opaque;
virNetServerProgramPtr prog = NULL;
unsigned int priority = 0;
size_t i;
int ret = -1;
VIR_DEBUG("server=%p client=%p message=%p",
srv, client, msg);
virObjectLock(srv);
for (i = 0; i < srv->nprograms; i++) {
if (virNetServerProgramMatches(srv->programs[i], msg)) {
prog = srv->programs[i];
break;
}
}
if (srv->workers) {
virNetServerJobPtr job;
if (VIR_ALLOC(job) < 0)
goto cleanup;
job->client = client;
job->msg = msg;
if (prog) {
virObjectRef(prog);
job->prog = prog;
priority = virNetServerProgramGetPriority(prog, msg->header.proc);
}
ret = virThreadPoolSendJob(srv->workers, priority, job);
if (ret < 0) {
VIR_FREE(job);
virObjectUnref(prog);
}
} else {
ret = virNetServerProcessMsg(srv, client, prog, msg);
}
cleanup:
virObjectUnlock(srv);
return ret;
}
static int virNetServerAddClient(virNetServerPtr srv,
virNetServerClientPtr client)
{
virObjectLock(srv);
if (srv->nclients >= srv->nclients_max) {
virReportError(VIR_ERR_RPC,
_("Too many active clients (%zu), dropping connection from %s"),
srv->nclients_max, virNetServerClientRemoteAddrString(client));
goto error;
}
if (virNetServerClientInit(client) < 0)
goto error;
if (VIR_EXPAND_N(srv->clients, srv->nclients, 1) < 0)
goto error;
srv->clients[srv->nclients-1] = client;
virObjectRef(client);
if (srv->nclients == srv->nclients_max) {
/* Temporarily stop accepting new clients */
VIR_DEBUG("Temporarily suspending services due to max_clients");
virNetServerUpdateServicesLocked(srv, false);
}
virNetServerClientSetDispatcher(client,
virNetServerDispatchNewMessage,
srv);
virNetServerClientInitKeepAlive(client, srv->keepaliveInterval,
srv->keepaliveCount);
virObjectUnlock(srv);
return 0;
error:
virObjectUnlock(srv);
return -1;
}
static int virNetServerDispatchNewClient(virNetServerServicePtr svc,
virNetSocketPtr clientsock,
void *opaque)
{
virNetServerPtr srv = opaque;
virNetServerClientPtr client;
if (!(client = virNetServerClientNew(clientsock,
virNetServerServiceGetAuth(svc),
virNetServerServiceIsReadonly(svc),
virNetServerServiceGetMaxRequests(svc),
#if WITH_GNUTLS
virNetServerServiceGetTLSContext(svc),
#endif
srv->clientPrivNew,
srv->clientPrivPreExecRestart,
srv->clientPrivFree,
srv->clientPrivOpaque)))
return -1;
if (virNetServerAddClient(srv, client) < 0) {
virNetServerClientClose(client);
virObjectUnref(client);
return -1;
}
virObjectUnref(client);
return 0;
}
static void
virNetServerFatalSignal(int sig, siginfo_t *siginfo ATTRIBUTE_UNUSED,
void *context ATTRIBUTE_UNUSED)
{
struct sigaction sig_action;
int origerrno;
origerrno = errno;
virLogEmergencyDumpAll(sig);
/*
* If the signal is fatal, avoid looping over this handler
* by deactivating it
*/
#ifdef SIGUSR2
if (sig != SIGUSR2) {
#endif
memset(&sig_action, 0, sizeof(sig_action));
sig_action.sa_handler = SIG_DFL;
sigaction(sig, &sig_action, NULL);
raise(sig);
#ifdef SIGUSR2
}
#endif
errno = origerrno;
}
virNetServerPtr virNetServerNew(size_t min_workers,
size_t max_workers,
size_t priority_workers,
size_t max_clients,
int keepaliveInterval,
unsigned int keepaliveCount,
bool keepaliveRequired,
const char *mdnsGroupName,
virNetServerClientPrivNew clientPrivNew,
virNetServerClientPrivPreExecRestart clientPrivPreExecRestart,
virFreeCallback clientPrivFree,
void *clientPrivOpaque)
{
virNetServerPtr srv;
struct sigaction sig_action;
if (virNetServerInitialize() < 0)
return NULL;
if (!(srv = virObjectLockableNew(virNetServerClass)))
return NULL;
if (max_workers &&
!(srv->workers = virThreadPoolNew(min_workers, max_workers,
priority_workers,
virNetServerHandleJob,
srv)))
goto error;
srv->nclients_max = max_clients;
srv->keepaliveInterval = keepaliveInterval;
srv->keepaliveCount = keepaliveCount;
srv->keepaliveRequired = keepaliveRequired;
srv->sigwrite = srv->sigread = -1;
srv->clientPrivNew = clientPrivNew;
srv->clientPrivPreExecRestart = clientPrivPreExecRestart;
srv->clientPrivFree = clientPrivFree;
srv->clientPrivOpaque = clientPrivOpaque;
srv->privileged = geteuid() == 0;
srv->autoShutdownInhibitFd = -1;
if (VIR_STRDUP(srv->mdnsGroupName, mdnsGroupName) < 0)
goto error;
if (srv->mdnsGroupName) {
if (!(srv->mdns = virNetServerMDNSNew()))
goto error;
if (!(srv->mdnsGroup = virNetServerMDNSAddGroup(srv->mdns,
srv->mdnsGroupName)))
goto error;
}
if (virEventRegisterDefaultImpl() < 0)
goto error;
memset(&sig_action, 0, sizeof(sig_action));
sig_action.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sig_action, NULL);
/*
* catch fatal errors to dump a log, also hook to USR2 for dynamic
* debugging purposes or testing
*/
sig_action.sa_sigaction = virNetServerFatalSignal;
sig_action.sa_flags = SA_SIGINFO;
sigaction(SIGFPE, &sig_action, NULL);
sigaction(SIGSEGV, &sig_action, NULL);
sigaction(SIGILL, &sig_action, NULL);
sigaction(SIGABRT, &sig_action, NULL);
#ifdef SIGBUS
sigaction(SIGBUS, &sig_action, NULL);
#endif
#ifdef SIGUSR2
sigaction(SIGUSR2, &sig_action, NULL);
#endif
return srv;
error:
virObjectUnref(srv);
return NULL;
}
virNetServerPtr virNetServerNewPostExecRestart(virJSONValuePtr object,
virNetServerClientPrivNew clientPrivNew,
virNetServerClientPrivNewPostExecRestart clientPrivNewPostExecRestart,
virNetServerClientPrivPreExecRestart clientPrivPreExecRestart,
virFreeCallback clientPrivFree,
void *clientPrivOpaque)
{
virNetServerPtr srv = NULL;
virJSONValuePtr clients;
virJSONValuePtr services;
size_t i;
int n;
unsigned int min_workers;
unsigned int max_workers;
unsigned int priority_workers;
unsigned int max_clients;
unsigned int keepaliveInterval;
unsigned int keepaliveCount;
bool keepaliveRequired;
const char *mdnsGroupName = NULL;
if (virJSONValueObjectGetNumberUint(object, "min_workers", &min_workers) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing min_workers data in JSON document"));
goto error;
}
if (virJSONValueObjectGetNumberUint(object, "max_workers", &max_workers) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing max_workers data in JSON document"));
goto error;
}
if (virJSONValueObjectGetNumberUint(object, "priority_workers", &priority_workers) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing priority_workers data in JSON document"));
goto error;
}
if (virJSONValueObjectGetNumberUint(object, "max_clients", &max_clients) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing max_clients data in JSON document"));
goto error;
}
if (virJSONValueObjectGetNumberUint(object, "keepaliveInterval", &keepaliveInterval) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing keepaliveInterval data in JSON document"));
goto error;
}
if (virJSONValueObjectGetNumberUint(object, "keepaliveCount", &keepaliveCount) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing keepaliveCount data in JSON document"));
goto error;
}
if (virJSONValueObjectGetBoolean(object, "keepaliveRequired", &keepaliveRequired) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing keepaliveRequired data in JSON document"));
goto error;
}
if (virJSONValueObjectHasKey(object, "mdnsGroupName") &&
(!(mdnsGroupName = virJSONValueObjectGetString(object, "mdnsGroupName")))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed mdnsGroupName data in JSON document"));
goto error;
}
if (!(srv = virNetServerNew(min_workers, max_clients,
priority_workers, max_clients,
keepaliveInterval, keepaliveCount,
keepaliveRequired, mdnsGroupName,
clientPrivNew, clientPrivPreExecRestart,
clientPrivFree, clientPrivOpaque)))
goto error;
if (!(services = virJSONValueObjectGet(object, "services"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing services data in JSON document"));
goto error;
}
n = virJSONValueArraySize(services);
if (n < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed services data in JSON document"));
goto error;
}
for (i = 0; i < n; i++) {
virNetServerServicePtr service;
virJSONValuePtr child = virJSONValueArrayGet(services, i);
if (!child) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing service data in JSON document"));
goto error;
}
if (!(service = virNetServerServiceNewPostExecRestart(child)))
goto error;
/* XXX mdns entry names ? */
if (virNetServerAddService(srv, service, NULL) < 0) {
virObjectUnref(service);
goto error;
}
}
if (!(clients = virJSONValueObjectGet(object, "clients"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing clients data in JSON document"));
goto error;
}
n = virJSONValueArraySize(clients);
if (n < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed clients data in JSON document"));
goto error;
}
for (i = 0; i < n; i++) {
virNetServerClientPtr client;
virJSONValuePtr child = virJSONValueArrayGet(clients, i);
if (!child) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing client data in JSON document"));
goto error;
}
if (!(client = virNetServerClientNewPostExecRestart(child,
clientPrivNewPostExecRestart,
clientPrivPreExecRestart,
clientPrivFree,
clientPrivOpaque)))
goto error;
if (virNetServerAddClient(srv, client) < 0) {
virObjectUnref(client);
goto error;
}
virObjectUnref(client);
}
return srv;
error:
virObjectUnref(srv);
return NULL;
}
virJSONValuePtr virNetServerPreExecRestart(virNetServerPtr srv)
{
virJSONValuePtr object;
virJSONValuePtr clients;
virJSONValuePtr services;
size_t i;
virObjectLock(srv);
if (!(object = virJSONValueNewObject()))
goto error;
if (virJSONValueObjectAppendNumberUint(object, "min_workers",
virThreadPoolGetMinWorkers(srv->workers)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot set min_workers data in JSON document"));
goto error;
}
if (virJSONValueObjectAppendNumberUint(object, "max_workers",
virThreadPoolGetMaxWorkers(srv->workers)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot set max_workers data in JSON document"));
goto error;
}
if (virJSONValueObjectAppendNumberUint(object, "priority_workers",
virThreadPoolGetPriorityWorkers(srv->workers)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot set priority_workers data in JSON document"));
goto error;
}
if (virJSONValueObjectAppendNumberUint(object, "max_clients", srv->nclients_max) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot set max_clients data in JSON document"));
goto error;
}
if (virJSONValueObjectAppendNumberUint(object, "keepaliveInterval", srv->keepaliveInterval) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot set keepaliveInterval data in JSON document"));
goto error;
}
if (virJSONValueObjectAppendNumberUint(object, "keepaliveCount", srv->keepaliveCount) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot set keepaliveCount data in JSON document"));
goto error;
}
if (virJSONValueObjectAppendBoolean(object, "keepaliveRequired", srv->keepaliveRequired) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot set keepaliveRequired data in JSON document"));
goto error;
}
if (srv->mdnsGroupName &&
virJSONValueObjectAppendString(object, "mdnsGroupName", srv->mdnsGroupName) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Cannot set mdnsGroupName data in JSON document"));
goto error;
}
services = virJSONValueNewArray();
if (virJSONValueObjectAppend(object, "services", services) < 0) {
virJSONValueFree(services);
goto error;
}
for (i = 0; i < srv->nservices; i++) {
virJSONValuePtr child;
if (!(child = virNetServerServicePreExecRestart(srv->services[i])))
goto error;
if (virJSONValueArrayAppend(services, child) < 0) {
virJSONValueFree(child);
goto error;
}
}
clients = virJSONValueNewArray();
if (virJSONValueObjectAppend(object, "clients", clients) < 0) {
virJSONValueFree(clients);
goto error;
}
for (i = 0; i < srv->nclients; i++) {
virJSONValuePtr child;
if (!(child = virNetServerClientPreExecRestart(srv->clients[i])))
goto error;
if (virJSONValueArrayAppend(clients, child) < 0) {
virJSONValueFree(child);
goto error;
}
}
virObjectUnlock(srv);
return object;
error:
virJSONValueFree(object);
virObjectUnlock(srv);
return NULL;
}
bool virNetServerIsPrivileged(virNetServerPtr srv)
{
bool priv;
virObjectLock(srv);
priv = srv->privileged;
virObjectUnlock(srv);
return priv;
}
void virNetServerAutoShutdown(virNetServerPtr srv,
unsigned int timeout)
{
virObjectLock(srv);
srv->autoShutdownTimeout = timeout;
virObjectUnlock(srv);
}
#if defined(HAVE_DBUS) && defined(DBUS_TYPE_UNIX_FD)
static void virNetServerGotInhibitReply(DBusPendingCall *pending,
void *opaque)
{
virNetServerPtr srv = opaque;
DBusMessage *reply;
int fd;
virObjectLock(srv);
srv->autoShutdownCallingInhibit = false;
VIR_DEBUG("srv=%p", srv);
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 (srv->autoShutdownInhibitions) {
srv->autoShutdownInhibitFd = fd;
} else {
/* We stopped the last VM since we made the inhibit call */
VIR_FORCE_CLOSE(fd);
}
}
dbus_message_unref(reply);
cleanup:
virObjectUnlock(srv);
}
/* As per: http://www.freedesktop.org/wiki/Software/systemd/inhibit */
static void virNetServerCallInhibit(virNetServerPtr srv,
const char *what,
const char *who,
const char *why,
const char *mode)
{
DBusMessage *message;
DBusPendingCall *pendingReply;
DBusConnection *systemBus;
VIR_DEBUG("srv=%p what=%s who=%s why=%s mode=%s",
srv, NULLSTR(what), NULLSTR(who), NULLSTR(why), NULLSTR(mode));
if (!(systemBus = virDBusGetSystemBus()))
return;
/* Only one outstanding call at a time */
if (srv->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);
pendingReply = NULL;
if (dbus_connection_send_with_reply(systemBus, message,
&pendingReply,
25*1000)) {
dbus_pending_call_set_notify(pendingReply,
virNetServerGotInhibitReply,
srv, NULL);
srv->autoShutdownCallingInhibit = true;
}
dbus_message_unref(message);
}
#endif
void virNetServerAddShutdownInhibition(virNetServerPtr srv)
{
virObjectLock(srv);
srv->autoShutdownInhibitions++;
VIR_DEBUG("srv=%p inhibitions=%zu", srv, srv->autoShutdownInhibitions);
#if defined(HAVE_DBUS) && defined(DBUS_TYPE_UNIX_FD)
if (srv->autoShutdownInhibitions == 1)
virNetServerCallInhibit(srv,
"shutdown",
_("Libvirt"),
_("Virtual machines need to be saved"),
"delay");
#endif
virObjectUnlock(srv);
}
void virNetServerRemoveShutdownInhibition(virNetServerPtr srv)
{
virObjectLock(srv);
srv->autoShutdownInhibitions--;
VIR_DEBUG("srv=%p inhibitions=%zu", srv, srv->autoShutdownInhibitions);
if (srv->autoShutdownInhibitions == 0)
VIR_FORCE_CLOSE(srv->autoShutdownInhibitFd);
virObjectUnlock(srv);
}
static sig_atomic_t sigErrors = 0;
static int sigLastErrno = 0;
static int sigWrite = -1;
static void
virNetServerSignalHandler(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
virNetServerSignalEvent(int watch,
int fd ATTRIBUTE_UNUSED,
int events ATTRIBUTE_UNUSED,
void *opaque) {
virNetServerPtr srv = opaque;
siginfo_t siginfo;
size_t i;
virObjectLock(srv);
if (saferead(srv->sigread, &siginfo, sizeof(siginfo)) != sizeof(siginfo)) {
virReportSystemError(errno, "%s",
_("Failed to read from signal pipe"));
virEventRemoveHandle(watch);
srv->sigwatch = -1;
goto cleanup;
}
for (i = 0; i < srv->nsignals; i++) {
if (siginfo.si_signo == srv->signals[i]->signum) {
virNetServerSignalFunc func = srv->signals[i]->func;
void *funcopaque = srv->signals[i]->opaque;
virObjectUnlock(srv);
func(srv, &siginfo, funcopaque);
return;
}
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unexpected signal received: %d"), siginfo.si_signo);
cleanup:
virObjectUnlock(srv);
}
static int virNetServerSignalSetup(virNetServerPtr srv)
{
int fds[2] = { -1, -1 };
if (srv->sigwrite != -1)
return 0;
if (pipe2(fds, O_CLOEXEC|O_NONBLOCK) < 0) {
virReportSystemError(errno, "%s",
_("Unable to create signal pipe"));
return -1;
}
if ((srv->sigwatch = virEventAddHandle(fds[0],
VIR_EVENT_HANDLE_READABLE,
virNetServerSignalEvent,
srv, NULL)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Failed to add signal handle watch"));
goto error;
}
srv->sigread = fds[0];
srv->sigwrite = fds[1];
sigWrite = fds[1];
return 0;
error:
VIR_FORCE_CLOSE(fds[0]);
VIR_FORCE_CLOSE(fds[1]);
return -1;
}
int virNetServerAddSignalHandler(virNetServerPtr srv,
int signum,
virNetServerSignalFunc func,
void *opaque)
{
virNetServerSignalPtr sigdata = NULL;
struct sigaction sig_action;
virObjectLock(srv);
if (virNetServerSignalSetup(srv) < 0)
goto error;
if (VIR_EXPAND_N(srv->signals, srv->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 = virNetServerSignalHandler;
sig_action.sa_flags = SA_SIGINFO;
sigemptyset(&sig_action.sa_mask);
sigaction(signum, &sig_action, &sigdata->oldaction);
srv->signals[srv->nsignals-1] = sigdata;
virObjectUnlock(srv);
return 0;
error:
VIR_FREE(sigdata);
virObjectUnlock(srv);
return -1;
}
int virNetServerAddService(virNetServerPtr srv,
virNetServerServicePtr svc,
const char *mdnsEntryName)
{
virObjectLock(srv);
if (VIR_EXPAND_N(srv->services, srv->nservices, 1) < 0)
goto error;
if (mdnsEntryName) {
int port = virNetServerServiceGetPort(svc);
if (!virNetServerMDNSAddEntry(srv->mdnsGroup,
mdnsEntryName,
port))
goto error;
}
srv->services[srv->nservices-1] = svc;
virObjectRef(svc);
virNetServerServiceSetDispatcher(svc,
virNetServerDispatchNewClient,
srv);
virObjectUnlock(srv);
return 0;
error:
virObjectUnlock(srv);
return -1;
}
int virNetServerAddProgram(virNetServerPtr srv,
virNetServerProgramPtr prog)
{
virObjectLock(srv);
if (VIR_EXPAND_N(srv->programs, srv->nprograms, 1) < 0)
goto error;
srv->programs[srv->nprograms-1] = virObjectRef(prog);
virObjectUnlock(srv);
return 0;
error:
virObjectUnlock(srv);
return -1;
}
#if WITH_GNUTLS
int virNetServerSetTLSContext(virNetServerPtr srv,
virNetTLSContextPtr tls)
{
srv->tls = virObjectRef(tls);
return 0;
}
#endif
static void virNetServerAutoShutdownTimer(int timerid ATTRIBUTE_UNUSED,
void *opaque) {
virNetServerPtr srv = opaque;
virObjectLock(srv);
if (!srv->autoShutdownInhibitions) {
VIR_DEBUG("Automatic shutdown triggered");
srv->quit = 1;
}
virObjectUnlock(srv);
}
static void
virNetServerUpdateServicesLocked(virNetServerPtr srv,
bool enabled)
{
size_t i;
for (i = 0; i < srv->nservices; i++)
virNetServerServiceToggle(srv->services[i], enabled);
}
void virNetServerUpdateServices(virNetServerPtr srv,
bool enabled)
{
virObjectLock(srv);
virNetServerUpdateServicesLocked(srv, enabled);
virObjectUnlock(srv);
}
void virNetServerRun(virNetServerPtr srv)
{
int timerid = -1;
bool timerActive = false;
size_t i;
virObjectLock(srv);
if (srv->mdns &&
virNetServerMDNSStart(srv->mdns) < 0)
goto cleanup;
srv->quit = 0;
if (srv->autoShutdownTimeout &&
(timerid = virEventAddTimeout(-1,
virNetServerAutoShutdownTimer,
srv, NULL)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Failed to register shutdown timeout"));
goto cleanup;
}
VIR_DEBUG("srv=%p quit=%d", srv, srv->quit);
while (!srv->quit) {
/* A shutdown timeout is specified, so check
* if any drivers have active state, if not
* shutdown after timeout seconds
*/
if (srv->autoShutdownTimeout) {
if (timerActive) {
if (srv->clients) {
VIR_DEBUG("Deactivating shutdown timer %d", timerid);
virEventUpdateTimeout(timerid, -1);
timerActive = false;
}
} else {
if (!srv->clients) {
VIR_DEBUG("Activating shutdown timer %d", timerid);
virEventUpdateTimeout(timerid,
srv->autoShutdownTimeout * 1000);
timerActive = true;
}
}
}
virObjectUnlock(srv);
if (virEventRunDefaultImpl() < 0) {
virObjectLock(srv);
VIR_DEBUG("Loop iteration error, exiting");
break;
}
virObjectLock(srv);
reprocess:
for (i = 0; i < srv->nclients; i++) {
/* Coverity 5.3.0 couldn't see that srv->clients is non-NULL
* if srv->nclients is non-zero. */
sa_assert(srv->clients);
if (virNetServerClientWantClose(srv->clients[i]))
virNetServerClientClose(srv->clients[i]);
if (virNetServerClientIsClosed(srv->clients[i])) {
virNetServerClientPtr client = srv->clients[i];
if (srv->nclients > 1) {
memmove(srv->clients + i,
srv->clients + i + 1,
sizeof(*srv->clients) * (srv->nclients - (i + 1)));
VIR_SHRINK_N(srv->clients, srv->nclients, 1);
} else {
VIR_FREE(srv->clients);
srv->nclients = 0;
}
/* Enable services if we can accept a new client.
* The new client can be accepted if we are at the limit. */
if (srv->nclients == srv->nclients_max - 1) {
/* Now it makes sense to accept() a new client. */
VIR_DEBUG("Re-enabling services");
virNetServerUpdateServicesLocked(srv, true);
}
virObjectUnlock(srv);
virObjectUnref(client);
virObjectLock(srv);
goto reprocess;
}
}
}
cleanup:
virObjectUnlock(srv);
}
void virNetServerQuit(virNetServerPtr srv)
{
virObjectLock(srv);
VIR_DEBUG("Quit requested %p", srv);
srv->quit = 1;
virObjectUnlock(srv);
}
void virNetServerDispose(void *obj)
{
virNetServerPtr srv = obj;
size_t i;
VIR_FORCE_CLOSE(srv->autoShutdownInhibitFd);
for (i = 0; i < srv->nservices; i++)
virNetServerServiceToggle(srv->services[i], false);
virThreadPoolFree(srv->workers);
for (i = 0; i < srv->nsignals; i++) {
sigaction(srv->signals[i]->signum, &srv->signals[i]->oldaction, NULL);
VIR_FREE(srv->signals[i]);
}
VIR_FREE(srv->signals);
VIR_FORCE_CLOSE(srv->sigread);
VIR_FORCE_CLOSE(srv->sigwrite);
if (srv->sigwatch > 0)
virEventRemoveHandle(srv->sigwatch);
for (i = 0; i < srv->nservices; i++)
virObjectUnref(srv->services[i]);
VIR_FREE(srv->services);
for (i = 0; i < srv->nprograms; i++)
virObjectUnref(srv->programs[i]);
VIR_FREE(srv->programs);
for (i = 0; i < srv->nclients; i++) {
virNetServerClientClose(srv->clients[i]);
virObjectUnref(srv->clients[i]);
}
VIR_FREE(srv->clients);
VIR_FREE(srv->mdnsGroupName);
virNetServerMDNSFree(srv->mdns);
}
void virNetServerClose(virNetServerPtr srv)
{
size_t i;
if (!srv)
return;
virObjectLock(srv);
for (i = 0; i < srv->nservices; i++) {
virNetServerServiceClose(srv->services[i]);
}
virObjectUnlock(srv);
}
bool virNetServerKeepAliveRequired(virNetServerPtr srv)
{
bool required;
virObjectLock(srv);
required = srv->keepaliveRequired;
virObjectUnlock(srv);
return required;
}