/*
 * virnetserver.c: generic network RPC server
 *
 * Copyright (C) 2006-2015 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
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include "virnetserver.h"
#include "virlog.h"
#include "viralloc.h"
#include "virerror.h"
#include "virthread.h"
#include "virthreadpool.h"
#include "virstring.h"
#include "virutil.h"

#define VIR_FROM_THIS VIR_FROM_RPC

VIR_LOG_INIT("rpc.netserver");


typedef struct _virNetServerJob virNetServerJob;
typedef virNetServerJob *virNetServerJobPtr;

struct _virNetServerJob {
    virNetServerClientPtr client;
    virNetMessagePtr msg;
    virNetServerProgramPtr prog;
};

struct _virNetServer {
    virObjectLockable parent;

    char *name;

    /* Immutable pointer, self-locking APIs */
    virThreadPoolPtr workers;

    size_t nservices;
    virNetServerServicePtr *services;

    size_t nprograms;
    virNetServerProgramPtr *programs;

    size_t nclients;                    /* Current clients count */
    virNetServerClientPtr *clients;     /* Clients */
    unsigned long long next_client_id;  /* next client ID */
    size_t nclients_max;                /* Max allowed clients count */
    size_t nclients_unauth;             /* Unauthenticated clients count */
    size_t nclients_unauth_max;         /* Max allowed unauth clients count */

    int keepaliveInterval;
    unsigned int keepaliveCount;

    virNetTLSContextPtr tls;

    virNetServerClientPrivNew clientPrivNew;
    virNetServerClientPrivPreExecRestart clientPrivPreExecRestart;
    virFreeCallback clientPrivFree;
    void *clientPrivOpaque;
};


static virClassPtr virNetServerClass;
static void virNetServerDispose(void *obj);
static void virNetServerUpdateServicesLocked(virNetServerPtr srv,
                                             bool enabled);
static inline size_t virNetServerTrackPendingAuthLocked(virNetServerPtr srv);
static inline size_t virNetServerTrackCompletedAuthLocked(virNetServerPtr srv);

static int virNetServerOnceInit(void)
{
    if (!VIR_CLASS_NEW(virNetServer, virClassForObjectLockable()))
        return -1;

    return 0;
}

VIR_ONCE_GLOBAL_INIT(virNetServer);

unsigned long long virNetServerNextClientID(virNetServerPtr srv)
{
    unsigned long long val;

    virObjectLock(srv);
    val = srv->next_client_id++;
    virObjectUnlock(srv);

    return val;
}

static int virNetServerProcessMsg(virNetServerPtr srv,
                                  virNetServerClientPtr client,
                                  virNetServerProgramPtr prog,
                                  virNetMessagePtr msg)
{
    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)
                return -1;
        } else {
            VIR_INFO("Dropping client message, 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)
                return -1;
        }
        return 0;
    }

    if (virNetServerProgramDispatch(prog,
                                    srv,
                                    client,
                                    msg) < 0)
        return -1;

    return 0;
}

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);
}

/**
 * virNetServerGetProgramLocked:
 * @srv: server (must be locked by the caller)
 * @msg: message
 *
 * Searches @srv for the right program for a given message @msg.
 *
 * Returns a pointer to the server program or NULL if not found.
 */
static virNetServerProgramPtr
virNetServerGetProgramLocked(virNetServerPtr srv,
                             virNetMessagePtr msg)
{
    size_t i;
    for (i = 0; i < srv->nprograms; i++) {
        if (virNetServerProgramMatches(srv->programs[i], msg))
            return srv->programs[i];
    }
    return NULL;
}

static void
virNetServerDispatchNewMessage(virNetServerClientPtr client,
                               virNetMessagePtr msg,
                               void *opaque)
{
    virNetServerPtr srv = opaque;
    virNetServerProgramPtr prog = NULL;
    unsigned int priority = 0;

    VIR_DEBUG("server=%p client=%p message=%p",
              srv, client, msg);

    virObjectLock(srv);
    prog = virNetServerGetProgramLocked(srv, msg);
    /* we can unlock @srv since @prog can only become invalid in case
     * of disposing @srv, but let's grab a ref first to ensure nothing
     * disposes of it before we use it. */
    virObjectRef(srv);
    virObjectUnlock(srv);

    if (virThreadPoolGetMaxWorkers(srv->workers) > 0)  {
        virNetServerJobPtr job;

        if (VIR_ALLOC(job) < 0)
            goto error;

        job->client = virObjectRef(client);
        job->msg = msg;

        if (prog) {
            job->prog = virObjectRef(prog);
            priority = virNetServerProgramGetPriority(prog, msg->header.proc);
        }

        if (virThreadPoolSendJob(srv->workers, priority, job) < 0) {
            virObjectUnref(client);
            VIR_FREE(job);
            virObjectUnref(prog);
            goto error;
        }
    } else {
        if (virNetServerProcessMsg(srv, client, prog, msg) < 0)
            goto error;
    }

    virObjectUnref(srv);
    return;

 error:
    virNetMessageFree(msg);
    virNetServerClientClose(client);
    virObjectUnref(srv);
}


/**
 * virNetServerCheckLimits:
 * @srv: server to check limits on
 *
 * Check if limits like max_clients or max_anonymous_clients
 * are satisfied. If so, re-enable accepting new clients. If these are violated
 * however, temporarily disable accepting new clients.
 * The @srv must be locked when this function is called.
 */
static void
virNetServerCheckLimits(virNetServerPtr srv)
{
    VIR_DEBUG("Checking client-related limits to re-enable or temporarily "
              "suspend services: nclients=%zu nclients_max=%zu "
              "nclients_unauth=%zu nclients_unauth_max=%zu",
              srv->nclients, srv->nclients_max,
              srv->nclients_unauth, srv->nclients_unauth_max);

    /* Check the max_anonymous_clients and max_clients limits so that we can
     * decide whether the services should be temporarily suspended, thus not
     * accepting any more clients for a while or re-enabling the previously
     * suspended services in order to accept new clients again.
     * A new client can only be accepted if both max_clients and
     * max_anonymous_clients wouldn't get overcommitted by accepting it.
     */
    if (srv->nclients >= srv->nclients_max ||
        (srv->nclients_unauth_max &&
         srv->nclients_unauth >= srv->nclients_unauth_max)) {
        /* Temporarily stop accepting new clients */
        VIR_INFO("Temporarily suspending services");
        virNetServerUpdateServicesLocked(srv, false);
    } else if (srv->nclients < srv->nclients_max &&
               (!srv->nclients_unauth_max ||
                srv->nclients_unauth < srv->nclients_unauth_max)) {
        /* Now it makes sense to accept() a new client. */
        VIR_INFO("Re-enabling services");
        virNetServerUpdateServicesLocked(srv, true);
    }
}

int virNetServerAddClient(virNetServerPtr srv,
                          virNetServerClientPtr client)
{
    virObjectLock(srv);

    if (virNetServerClientInit(client) < 0)
        goto error;

    if (VIR_EXPAND_N(srv->clients, srv->nclients, 1) < 0)
        goto error;
    srv->clients[srv->nclients-1] = virObjectRef(client);

    virObjectLock(client);
    if (virNetServerClientIsAuthPendingLocked(client))
        virNetServerTrackPendingAuthLocked(srv);
    virObjectUnlock(client);

    virNetServerCheckLimits(srv);

    virNetServerClientSetDispatcher(client,
                                    virNetServerDispatchNewMessage,
                                    srv);

    if (virNetServerClientInitKeepAlive(client, srv->keepaliveInterval,
                                        srv->keepaliveCount) < 0)
        goto error;

    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(virNetServerNextClientID(srv),
                                         clientsock,
                                         virNetServerServiceGetAuth(svc),
                                         virNetServerServiceIsReadonly(svc),
                                         virNetServerServiceGetMaxRequests(svc),
                                         virNetServerServiceGetTLSContext(svc),
                                         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;
}


virNetServerPtr virNetServerNew(const char *name,
                                unsigned long long next_client_id,
                                size_t min_workers,
                                size_t max_workers,
                                size_t priority_workers,
                                size_t max_clients,
                                size_t max_anonymous_clients,
                                int keepaliveInterval,
                                unsigned int keepaliveCount,
                                virNetServerClientPrivNew clientPrivNew,
                                virNetServerClientPrivPreExecRestart clientPrivPreExecRestart,
                                virFreeCallback clientPrivFree,
                                void *clientPrivOpaque)
{
    virNetServerPtr srv;

    if (virNetServerInitialize() < 0)
        return NULL;

    if (!(srv = virObjectLockableNew(virNetServerClass)))
        return NULL;

    if (!(srv->workers = virThreadPoolNewFull(min_workers, max_workers,
                                              priority_workers,
                                              virNetServerHandleJob,
                                              "rpc-worker",
                                              srv)))
        goto error;

    srv->name = g_strdup(name);

    srv->next_client_id = next_client_id;
    srv->nclients_max = max_clients;
    srv->nclients_unauth_max = max_anonymous_clients;
    srv->keepaliveInterval = keepaliveInterval;
    srv->keepaliveCount = keepaliveCount;
    srv->clientPrivNew = clientPrivNew;
    srv->clientPrivPreExecRestart = clientPrivPreExecRestart;
    srv->clientPrivFree = clientPrivFree;
    srv->clientPrivOpaque = clientPrivOpaque;

    return srv;
 error:
    virObjectUnref(srv);
    return NULL;
}


virNetServerPtr virNetServerNewPostExecRestart(virJSONValuePtr object,
                                               const char *name,
                                               virNetServerClientPrivNew clientPrivNew,
                                               virNetServerClientPrivNewPostExecRestart clientPrivNewPostExecRestart,
                                               virNetServerClientPrivPreExecRestart clientPrivPreExecRestart,
                                               virFreeCallback clientPrivFree,
                                               void *clientPrivOpaque)
{
    virNetServerPtr srv = NULL;
    virJSONValuePtr clients;
    virJSONValuePtr services;
    size_t i;
    unsigned int min_workers;
    unsigned int max_workers;
    unsigned int priority_workers;
    unsigned int max_clients;
    unsigned int max_anonymous_clients;
    unsigned int keepaliveInterval;
    unsigned int keepaliveCount;
    unsigned long long next_client_id;

    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 (virJSONValueObjectHasKey(object, "max_anonymous_clients")) {
        if (virJSONValueObjectGetNumberUint(object, "max_anonymous_clients",
                                            &max_anonymous_clients) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Malformed max_anonymous_clients data in JSON document"));
            goto error;
        }
    } else {
        max_anonymous_clients = max_clients;
    }
    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 (virJSONValueObjectGetNumberUlong(object, "next_client_id",
                                         &next_client_id) < 0) {
        VIR_WARN("Missing next_client_id data in JSON document");
        next_client_id = 1;
    }

    if (!(srv = virNetServerNew(name, next_client_id,
                                min_workers, max_workers,
                                priority_workers, max_clients,
                                max_anonymous_clients,
                                keepaliveInterval, keepaliveCount,
                                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;
    }

    if (!virJSONValueIsArray(services)) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Malformed services array"));
        goto error;
    }

    for (i = 0; i < virJSONValueArraySize(services); 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;

        if (virNetServerAddService(srv, service) < 0) {
            virObjectUnref(service);
            goto error;
        }
    }


    if (!(clients = virJSONValueObjectGet(object, "clients"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing clients data in JSON document"));
        goto error;
    }

    if (!virJSONValueIsArray(clients)) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Malformed clients array"));
        goto error;
    }

    for (i = 0; i < virJSONValueArraySize(clients); 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(srv,
                                                            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 = virJSONValueNewObject();
    virJSONValuePtr clients;
    virJSONValuePtr services;
    size_t i;

    virObjectLock(srv);

    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, "max_anonymous_clients",
                                           srv->nclients_unauth_max) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Cannot set max_anonymous_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 (virJSONValueObjectAppendNumberUlong(object, "next_client_id",
                                            srv->next_client_id) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Cannot set next_client_id 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;
}



int virNetServerAddService(virNetServerPtr srv,
                           virNetServerServicePtr svc)
{
    virObjectLock(srv);

    if (VIR_EXPAND_N(srv->services, srv->nservices, 1) < 0)
        goto error;

    srv->services[srv->nservices-1] = virObjectRef(svc);

    virNetServerServiceSetDispatcher(svc,
                                     virNetServerDispatchNewClient,
                                     srv);

    virObjectUnlock(srv);
    return 0;

 error:
    virObjectUnlock(srv);
    return -1;
}


static int
virNetServerAddServiceActivation(virNetServerPtr srv,
                                 virSystemdActivationPtr act,
                                 const char *actname,
                                 int auth,
                                 virNetTLSContextPtr tls,
                                 bool readonly,
                                 size_t max_queued_clients,
                                 size_t nrequests_client_max)
{
    int *fds;
    size_t nfds;

    if (act == NULL)
        return 0;

    virSystemdActivationClaimFDs(act, actname, &fds, &nfds);

    if (nfds) {
        virNetServerServicePtr svc;

        svc = virNetServerServiceNewFDs(fds,
                                        nfds,
                                        false,
                                        auth,
                                        tls,
                                        readonly,
                                        max_queued_clients,
                                        nrequests_client_max);
        if (!svc)
            return -1;

        if (virNetServerAddService(srv, svc) < 0) {
            virObjectUnref(svc);
            return -1;
        }
    }

    /* Intentionally return 1 any time activation is present,
     * even if we didn't find any sockets with the matching
     * name. The user needs to be free to disable some of the
     * services via unit files without causing us to fallback
     * to creating the service manually.
     */
    return 1;
}


int virNetServerAddServiceTCP(virNetServerPtr srv,
                              virSystemdActivationPtr act,
                              const char *actname,
                              const char *nodename,
                              const char *service,
                              int family,
                              int auth,
                              virNetTLSContextPtr tls,
                              bool readonly,
                              size_t max_queued_clients,
                              size_t nrequests_client_max)
{
    virNetServerServicePtr svc = NULL;
    int ret;

    ret = virNetServerAddServiceActivation(srv, act, actname,
                                           auth,
                                           tls,
                                           readonly,
                                           max_queued_clients,
                                           nrequests_client_max);
    if (ret < 0)
        return -1;

    if (ret == 1)
        return 0;

    if (!(svc = virNetServerServiceNewTCP(nodename,
                                          service,
                                          family,
                                          auth,
                                          tls,
                                          readonly,
                                          max_queued_clients,
                                          nrequests_client_max)))
        return -1;

    if (virNetServerAddService(srv, svc) < 0) {
        virObjectUnref(svc);
        return -1;
    }

    virObjectUnref(svc);

    return 0;
}


int virNetServerAddServiceUNIX(virNetServerPtr srv,
                               virSystemdActivationPtr act,
                               const char *actname,
                               const char *path,
                               mode_t mask,
                               gid_t grp,
                               int auth,
                               virNetTLSContextPtr tls,
                               bool readonly,
                               size_t max_queued_clients,
                               size_t nrequests_client_max)
{
    virNetServerServicePtr svc = NULL;
    int ret;

    ret = virNetServerAddServiceActivation(srv, act, actname,
                                           auth,
                                           tls,
                                           readonly,
                                           max_queued_clients,
                                           nrequests_client_max);
    if (ret < 0)
        return -1;

    if (ret == 1)
        return 0;

    if (!(svc = virNetServerServiceNewUNIX(path,
                                           mask,
                                           grp,
                                           auth,
                                           tls,
                                           readonly,
                                           max_queued_clients,
                                           nrequests_client_max)))
        return -1;

    if (virNetServerAddService(srv, svc) < 0) {
        virObjectUnref(svc);
        return -1;
    }

    virObjectUnref(svc);

    return 0;
}


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;
}

int virNetServerSetTLSContext(virNetServerPtr srv,
                              virNetTLSContextPtr tls)
{
    srv->tls = virObjectRef(tls);
    return 0;
}


/**
 * virNetServerSetClientAuthCompletedLocked:
 * @srv: server must be locked by the caller
 * @client: client must be locked by the caller
 *
 * If the client authentication was pending, clear that pending and
 * update the server tracking.
 */
static void
virNetServerSetClientAuthCompletedLocked(virNetServerPtr srv,
                                         virNetServerClientPtr client)
{
    if (virNetServerClientIsAuthPendingLocked(client)) {
        virNetServerClientSetAuthPendingLocked(client, false);
        virNetServerTrackCompletedAuthLocked(srv);
    }
}


/**
 * virNetServerSetClientAuthenticated:
 * @srv: server must be unlocked
 * @client: client must be unlocked
 *
 * Mark @client as authenticated and tracks on @srv that the
 * authentication of this @client has been completed. Also it checks
 * the limits of @srv.
 */
void
virNetServerSetClientAuthenticated(virNetServerPtr srv,
                                   virNetServerClientPtr client)
{
    virObjectLock(srv);
    virObjectLock(client);
    virNetServerClientSetAuthLocked(client, VIR_NET_SERVER_SERVICE_AUTH_NONE);
    virNetServerSetClientAuthCompletedLocked(srv, client);
    virNetServerCheckLimits(srv);
    virObjectUnlock(client);
    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 virNetServerDispose(void *obj)
{
    virNetServerPtr srv = obj;
    size_t i;

    VIR_FREE(srv->name);

    virThreadPoolFree(srv->workers);

    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++)
        virObjectUnref(srv->clients[i]);
    VIR_FREE(srv->clients);
}

void virNetServerClose(virNetServerPtr srv)
{
    size_t i;

    if (!srv)
        return;

    virObjectLock(srv);

    for (i = 0; i < srv->nservices; i++)
        virNetServerServiceClose(srv->services[i]);

    for (i = 0; i < srv->nclients; i++)
        virNetServerClientClose(srv->clients[i]);

    virObjectUnlock(srv);
}

static inline size_t
virNetServerTrackPendingAuthLocked(virNetServerPtr srv)
{
    return ++srv->nclients_unauth;
}

static inline size_t
virNetServerTrackCompletedAuthLocked(virNetServerPtr srv)
{
    return --srv->nclients_unauth;
}


bool
virNetServerHasClients(virNetServerPtr srv)
{
    bool ret;

    virObjectLock(srv);
    ret = !!srv->nclients;
    virObjectUnlock(srv);

    return ret;
}

void
virNetServerProcessClients(virNetServerPtr srv)
{
    size_t i;
    virNetServerClientPtr client;

    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);

        client = srv->clients[i];
        virObjectLock(client);
        if (virNetServerClientWantCloseLocked(client))
            virNetServerClientCloseLocked(client);

        if (virNetServerClientIsClosedLocked(client)) {
            VIR_DELETE_ELEMENT(srv->clients, i, srv->nclients);

            /* Update server authentication tracking */
            virNetServerSetClientAuthCompletedLocked(srv, client);
            virObjectUnlock(client);

            virNetServerCheckLimits(srv);

            virObjectUnlock(srv);
            virObjectUnref(client);
            virObjectLock(srv);

            goto reprocess;
        } else {
            virObjectUnlock(client);
        }
    }

    virObjectUnlock(srv);
}

const char *
virNetServerGetName(virNetServerPtr srv)
{
    return srv->name;
}

int
virNetServerGetThreadPoolParameters(virNetServerPtr srv,
                                    size_t *minWorkers,
                                    size_t *maxWorkers,
                                    size_t *nWorkers,
                                    size_t *freeWorkers,
                                    size_t *nPrioWorkers,
                                    size_t *jobQueueDepth)
{
    virObjectLock(srv);

    *minWorkers = virThreadPoolGetMinWorkers(srv->workers);
    *maxWorkers = virThreadPoolGetMaxWorkers(srv->workers);
    *freeWorkers = virThreadPoolGetFreeWorkers(srv->workers);
    *nWorkers = virThreadPoolGetCurrentWorkers(srv->workers);
    *nPrioWorkers = virThreadPoolGetPriorityWorkers(srv->workers);
    *jobQueueDepth = virThreadPoolGetJobQueueDepth(srv->workers);

    virObjectUnlock(srv);
    return 0;
}

int
virNetServerSetThreadPoolParameters(virNetServerPtr srv,
                                    long long int minWorkers,
                                    long long int maxWorkers,
                                    long long int prioWorkers)
{
    int ret;

    virObjectLock(srv);
    ret = virThreadPoolSetParameters(srv->workers, minWorkers,
                                     maxWorkers, prioWorkers);
    virObjectUnlock(srv);

    return ret;
}

size_t
virNetServerGetMaxClients(virNetServerPtr srv)
{
    size_t ret;

    virObjectLock(srv);
    ret = srv->nclients_max;
    virObjectUnlock(srv);

    return ret;
}

size_t
virNetServerGetCurrentClients(virNetServerPtr srv)
{
    size_t ret;

    virObjectLock(srv);
    ret = srv->nclients;
    virObjectUnlock(srv);

    return ret;
}

size_t
virNetServerGetMaxUnauthClients(virNetServerPtr srv)
{
    size_t ret;

    virObjectLock(srv);
    ret = srv->nclients_unauth_max;
    virObjectUnlock(srv);

    return ret;
}

size_t
virNetServerGetCurrentUnauthClients(virNetServerPtr srv)
{
    size_t ret;

    virObjectLock(srv);
    ret = srv->nclients_unauth;
    virObjectUnlock(srv);

    return ret;
}


bool virNetServerNeedsAuth(virNetServerPtr srv,
                           int auth)
{
    bool ret = false;
    size_t i;

    virObjectLock(srv);
    for (i = 0; i < srv->nservices; i++) {
        if (virNetServerServiceGetAuth(srv->services[i]) == auth)
            ret = true;
    }
    virObjectUnlock(srv);

    return ret;
}

int
virNetServerGetClients(virNetServerPtr srv,
                       virNetServerClientPtr **clts)
{
    int ret = -1;
    size_t i;
    size_t nclients = 0;
    virNetServerClientPtr *list = NULL;

    virObjectLock(srv);

    for (i = 0; i < srv->nclients; i++) {
        virNetServerClientPtr client = virObjectRef(srv->clients[i]);
        if (VIR_APPEND_ELEMENT(list, nclients, client) < 0) {
            virObjectUnref(client);
            goto cleanup;
        }
    }

    *clts = list;
    list = NULL;
    ret = nclients;

 cleanup:
    virObjectListFreeCount(list, nclients);
    virObjectUnlock(srv);
    return ret;
}

virNetServerClientPtr
virNetServerGetClient(virNetServerPtr srv,
                      unsigned long long id)
{
    size_t i;
    virNetServerClientPtr ret = NULL;

    virObjectLock(srv);

    for (i = 0; i < srv->nclients; i++) {
        virNetServerClientPtr client = srv->clients[i];
        if (virNetServerClientGetID(client) == id)
            ret = virObjectRef(client);
    }

    virObjectUnlock(srv);

    if (!ret)
        virReportError(VIR_ERR_NO_CLIENT,
                       _("No client with matching ID '%llu'"), id);
    return ret;
}

int
virNetServerSetClientLimits(virNetServerPtr srv,
                            long long int maxClients,
                            long long int maxClientsUnauth)
{
    int ret = -1;
    size_t max, max_unauth;

    virObjectLock(srv);

    max = maxClients >= 0 ? maxClients : srv->nclients_max;
    max_unauth = maxClientsUnauth >= 0 ?
        maxClientsUnauth : srv->nclients_unauth_max;

    if (max < max_unauth) {
        virReportError(VIR_ERR_INVALID_ARG, "%s",
                       _("The overall maximum number of clients must be "
                         "greater than the maximum number of clients waiting "
                         "for authentication"));
        goto cleanup;
    }

    if (maxClients >= 0)
        srv->nclients_max = maxClients;

    if (maxClientsUnauth >= 0)
        srv->nclients_unauth_max = maxClientsUnauth;

    virNetServerCheckLimits(srv);

    ret = 0;
 cleanup:
    virObjectUnlock(srv);
    return ret;
}

static virNetTLSContextPtr
virNetServerGetTLSContext(virNetServerPtr srv)
{
    size_t i;
    virNetTLSContextPtr ctxt = NULL;
    virNetServerServicePtr svc = NULL;

    /* find svcTLS from srv, get svcTLS->tls */
    for (i = 0; i < srv->nservices; i++) {
        svc = srv->services[i];
        ctxt = virNetServerServiceGetTLSContext(svc);
        if (ctxt != NULL)
            break;
    }

    return ctxt;
}

int
virNetServerUpdateTlsFiles(virNetServerPtr srv)
{
    int ret = -1;
    virNetTLSContextPtr ctxt = NULL;
    bool privileged = geteuid() == 0;

    ctxt = virNetServerGetTLSContext(srv);
    if (!ctxt) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("no tls service found, unable to update tls files"));
        return -1;
    }

    virObjectLock(srv);
    virObjectLock(ctxt);

    if (virNetTLSContextReloadForServer(ctxt, !privileged)) {
        VIR_DEBUG("failed to reload server's tls context");
        goto cleanup;
    }

    VIR_DEBUG("update tls files success");
    ret = 0;

 cleanup:
    virObjectUnlock(ctxt);
    virObjectUnlock(srv);
    return ret;
}
