/*
 * lock_daemon.c: lock management daemon
 *
 * Copyright (C) 2006-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
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <getopt.h>


#include "lock_daemon.h"
#include "lock_daemon_config.h"
#include "admin/admin_server_dispatch.h"
#include "virutil.h"
#include "virfile.h"
#include "virpidfile.h"
#include "virprocess.h"
#include "virerror.h"
#include "virlog.h"
#include "viralloc.h"
#include "virconf.h"
#include "rpc/virnetdaemon.h"
#include "rpc/virnetserver.h"
#include "virrandom.h"
#include "virhash.h"
#include "viruuid.h"
#include "virstring.h"
#include "virgettext.h"
#include "virdaemon.h"

#include "locking/lock_daemon_dispatch.h"
#include "locking/lock_protocol.h"

#include "configmake.h"

#define VIR_FROM_THIS VIR_FROM_LOCKING

VIR_LOG_INIT("locking.lock_daemon");

#define VIR_LOCK_DAEMON_NUM_LOCKSPACES 3

struct _virLockDaemon {
    virMutex lock;
    virNetDaemonPtr dmn;
    virHashTablePtr lockspaces;
    virLockSpacePtr defaultLockspace;
};

virLockDaemonPtr lockDaemon = NULL;

static bool execRestart;

static void *
virLockDaemonClientNew(virNetServerClientPtr client,
                       void *opaque);
static void
virLockDaemonClientFree(void *opaque);

static void *
virLockDaemonClientNewPostExecRestart(virNetServerClientPtr client,
                                      virJSONValuePtr object,
                                      void *opaque);
static virJSONValuePtr
virLockDaemonClientPreExecRestart(virNetServerClientPtr client,
                                  void *opaque);

static void
virLockDaemonFree(virLockDaemonPtr lockd)
{
    if (!lockd)
        return;

    virMutexDestroy(&lockd->lock);
    virObjectUnref(lockd->dmn);
    virHashFree(lockd->lockspaces);
    virLockSpaceFree(lockd->defaultLockspace);

    VIR_FREE(lockd);
}

static inline void
virLockDaemonLock(virLockDaemonPtr lockd)
{
    virMutexLock(&lockd->lock);
}

static inline void
virLockDaemonUnlock(virLockDaemonPtr lockd)
{
    virMutexUnlock(&lockd->lock);
}

static void virLockDaemonLockSpaceDataFree(void *data)
{
    virLockSpaceFree(data);
}

static virLockDaemonPtr
virLockDaemonNew(virLockDaemonConfigPtr config, bool privileged)
{
    virLockDaemonPtr lockd;
    virNetServerPtr srv = NULL;

    if (VIR_ALLOC(lockd) < 0)
        return NULL;

    if (virMutexInit(&lockd->lock) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Unable to initialize mutex"));
        VIR_FREE(lockd);
        return NULL;
    }

    if (!(lockd->dmn = virNetDaemonNew()))
        goto error;

    if (!(srv = virNetServerNew("virtlockd", 1,
                                0, 0, 0, config->max_clients,
                                config->max_clients, -1, 0,
                                virLockDaemonClientNew,
                                virLockDaemonClientPreExecRestart,
                                virLockDaemonClientFree,
                                (void*)(intptr_t)(privileged ? 0x1 : 0x0))))
        goto error;

    if (virNetDaemonAddServer(lockd->dmn, srv) < 0)
        goto error;
    virObjectUnref(srv);
    srv = NULL;

    if (!(srv = virNetServerNew("admin", 1,
                                0, 0, 0, config->admin_max_clients,
                                config->admin_max_clients, -1, 0,
                                remoteAdmClientNew,
                                remoteAdmClientPreExecRestart,
                                remoteAdmClientFree,
                                lockd->dmn)))
        goto error;

    if (virNetDaemonAddServer(lockd->dmn, srv) < 0)
         goto error;
    virObjectUnref(srv);
    srv = NULL;

    if (!(lockd->lockspaces = virHashCreate(VIR_LOCK_DAEMON_NUM_LOCKSPACES,
                                            virLockDaemonLockSpaceDataFree)))
        goto error;

    if (!(lockd->defaultLockspace = virLockSpaceNew(NULL)))
        goto error;

    return lockd;

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


static virNetServerPtr
virLockDaemonNewServerPostExecRestart(virNetDaemonPtr dmn G_GNUC_UNUSED,
                                      const char *name,
                                      virJSONValuePtr object,
                                      void *opaque)
{
    if (STREQ(name, "virtlockd")) {
        return virNetServerNewPostExecRestart(object,
                                              name,
                                              virLockDaemonClientNew,
                                              virLockDaemonClientNewPostExecRestart,
                                              virLockDaemonClientPreExecRestart,
                                              virLockDaemonClientFree,
                                              opaque);
    } else if (STREQ(name, "admin")) {
        return virNetServerNewPostExecRestart(object,
                                              name,
                                              remoteAdmClientNew,
                                              remoteAdmClientNewPostExecRestart,
                                              remoteAdmClientPreExecRestart,
                                              remoteAdmClientFree,
                                              dmn);
    } else {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Unexpected server name '%s' during restart"),
                       name);
        return NULL;
    }
}


static virLockDaemonPtr
virLockDaemonNewPostExecRestart(virJSONValuePtr object, bool privileged)
{
    virLockDaemonPtr lockd;
    virJSONValuePtr child;
    virJSONValuePtr lockspaces;
    size_t i;
    const char *serverNames[] = { "virtlockd" };

    if (VIR_ALLOC(lockd) < 0)
        return NULL;

    if (virMutexInit(&lockd->lock) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Unable to initialize mutex"));
        VIR_FREE(lockd);
        return NULL;
    }

    if (!(lockd->lockspaces = virHashCreate(VIR_LOCK_DAEMON_NUM_LOCKSPACES,
                                            virLockDaemonLockSpaceDataFree)))
        goto error;

    if (!(child = virJSONValueObjectGet(object, "defaultLockspace"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing defaultLockspace data from JSON file"));
        goto error;
    }

    if (!(lockd->defaultLockspace =
          virLockSpaceNewPostExecRestart(child)))
        goto error;

    if (!(lockspaces = virJSONValueObjectGet(object, "lockspaces"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing lockspaces data from JSON file"));
        goto error;
    }

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

    for (i = 0; i < virJSONValueArraySize(lockspaces); i++) {
        virLockSpacePtr lockspace;

        child = virJSONValueArrayGet(lockspaces, i);

        if (!(lockspace = virLockSpaceNewPostExecRestart(child)))
            goto error;

        if (virHashAddEntry(lockd->lockspaces,
                            virLockSpaceGetDirectory(lockspace),
                            lockspace) < 0) {
            virLockSpaceFree(lockspace);
        }
    }

    if (virJSONValueObjectHasKey(object, "daemon")) {
        if (!(child = virJSONValueObjectGet(object, "daemon"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Malformed daemon data from JSON file"));
            goto error;
        }
    } else {
        if (!(child = virJSONValueObjectGet(object, "server"))) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Missing server data from JSON file"));
            goto error;
        }
    }

    if (!(lockd->dmn = virNetDaemonNewPostExecRestart(child,
                                                      G_N_ELEMENTS(serverNames),
                                                      serverNames,
                                                      virLockDaemonNewServerPostExecRestart,
                                                      (void*)(intptr_t)(privileged ? 0x1 : 0x0))))
        goto error;

    return lockd;

 error:
    virLockDaemonFree(lockd);
    return NULL;
}


int virLockDaemonAddLockSpace(virLockDaemonPtr lockd,
                              const char *path,
                              virLockSpacePtr lockspace)
{
    int ret;
    virLockDaemonLock(lockd);
    ret = virHashAddEntry(lockd->lockspaces, path, lockspace);
    virLockDaemonUnlock(lockd);
    return ret;
}

virLockSpacePtr virLockDaemonFindLockSpace(virLockDaemonPtr lockd,
                                           const char *path)
{
    virLockSpacePtr lockspace;
    virLockDaemonLock(lockd);
    if (path && STRNEQ(path, ""))
        lockspace = virHashLookup(lockd->lockspaces, path);
    else
        lockspace = lockd->defaultLockspace;
    virLockDaemonUnlock(lockd);
    return lockspace;
}


static void
virLockDaemonErrorHandler(void *opaque G_GNUC_UNUSED,
                          virErrorPtr err G_GNUC_UNUSED)
{
    /* Don't do anything, since logging infrastructure already
     * took care of reporting the error */
}


/* Display version information. */
static void
virLockDaemonVersion(const char *argv0)
{
    printf("%s (%s) %s\n", argv0, PACKAGE_NAME, PACKAGE_VERSION);
}

static void
virLockDaemonShutdownHandler(virNetDaemonPtr dmn,
                             siginfo_t *sig G_GNUC_UNUSED,
                             void *opaque G_GNUC_UNUSED)
{
    virNetDaemonQuit(dmn);
}

static void
virLockDaemonExecRestartHandler(virNetDaemonPtr dmn,
                                siginfo_t *sig G_GNUC_UNUSED,
                                void *opaque G_GNUC_UNUSED)
{
    execRestart = true;
    virNetDaemonQuit(dmn);
}

static int
virLockDaemonSetupSignals(virNetDaemonPtr dmn)
{
    if (virNetDaemonAddSignalHandler(dmn, SIGINT, virLockDaemonShutdownHandler, NULL) < 0)
        return -1;
    if (virNetDaemonAddSignalHandler(dmn, SIGQUIT, virLockDaemonShutdownHandler, NULL) < 0)
        return -1;
    if (virNetDaemonAddSignalHandler(dmn, SIGTERM, virLockDaemonShutdownHandler, NULL) < 0)
        return -1;
    if (virNetDaemonAddSignalHandler(dmn, SIGUSR1, virLockDaemonExecRestartHandler, NULL) < 0)
        return -1;
    return 0;
}


struct virLockDaemonClientReleaseData {
    virLockDaemonClientPtr client;
    bool hadSomeLeases;
    bool gotError;
};

static int
virLockDaemonClientReleaseLockspace(void *payload,
                                    const void *name G_GNUC_UNUSED,
                                    void *opaque)
{
    virLockSpacePtr lockspace = payload;
    struct virLockDaemonClientReleaseData *data = opaque;
    int rc;

    rc = virLockSpaceReleaseResourcesForOwner(lockspace,
                                              data->client->clientPid);
    if (rc > 0)
        data->hadSomeLeases = true;
    else if (rc < 0)
        data->gotError = true;
    return 0;
}


static void
virLockDaemonClientFree(void *opaque)
{
    virLockDaemonClientPtr priv = opaque;

    if (!priv)
        return;

    VIR_DEBUG("priv=%p client=%lld owner=%lld",
              priv,
              (unsigned long long)priv->clientPid,
              (unsigned long long)priv->ownerPid);

    /* If client & owner match, this is the lock holder */
    if (priv->clientPid == priv->ownerPid) {
        size_t i;
        struct virLockDaemonClientReleaseData data = {
            .client = priv, .hadSomeLeases = false, .gotError = false
        };

        /* Release all locks associated with this
         * owner in all lockspaces */
        virLockDaemonLock(lockDaemon);
        virHashForEach(lockDaemon->lockspaces,
                       virLockDaemonClientReleaseLockspace,
                       &data);
        virLockDaemonClientReleaseLockspace(lockDaemon->defaultLockspace,
                                            "",
                                            &data);
        virLockDaemonUnlock(lockDaemon);

        /* If the client had some active leases when it
         * closed the connection, we must kill it off
         * to make sure it doesn't do nasty stuff */
        if (data.gotError || data.hadSomeLeases) {
            for (i = 0; i < 15; i++) {
                int signum;
                if (i == 0)
                    signum = SIGTERM;
                else if (i == 8)
                    signum = SIGKILL;
                else
                    signum = 0;
                if (priv->clientPid != 0 && virProcessKill(priv->clientPid, signum) < 0) {
                    if (errno == ESRCH)
                        break;

                    VIR_WARN("Failed to kill off pid %lld",
                             (unsigned long long)priv->clientPid);
                }
                g_usleep(200 * 1000);
            }
        }
    }

    virMutexDestroy(&priv->lock);
    VIR_FREE(priv->ownerName);
    VIR_FREE(priv);
}


static void *
virLockDaemonClientNew(virNetServerClientPtr client,
                       void *opaque)
{
    virLockDaemonClientPtr priv;
    uid_t clientuid;
    gid_t clientgid;
    unsigned long long timestamp;
    bool privileged = opaque != NULL;

    if (VIR_ALLOC(priv) < 0)
        return NULL;

    if (virMutexInit(&priv->lock) < 0) {
        VIR_FREE(priv);
        virReportSystemError(errno, "%s", _("unable to init mutex"));
        return NULL;
    }

    if (virNetServerClientGetUNIXIdentity(client,
                                          &clientuid,
                                          &clientgid,
                                          &priv->clientPid,
                                          &timestamp) < 0)
        goto error;

    VIR_DEBUG("New client pid %llu uid %llu",
              (unsigned long long)priv->clientPid,
              (unsigned long long)clientuid);

    if (!privileged) {
        if (geteuid() != clientuid) {
            virReportRestrictedError(_("Disallowing client %llu with uid %llu"),
                                     (unsigned long long)priv->clientPid,
                                     (unsigned long long)clientuid);
            goto error;
        }
    } else {
        if (clientuid != 0) {
            virReportRestrictedError(_("Disallowing client %llu with uid %llu"),
                                     (unsigned long long)priv->clientPid,
                                     (unsigned long long)clientuid);
            goto error;
        }
    }

    /* there's no closing handshake in the locking protocol */
    virNetServerClientSetQuietEOF(client);

    return priv;

 error:
    virMutexDestroy(&priv->lock);
    VIR_FREE(priv);
    return NULL;
}


static void *
virLockDaemonClientNewPostExecRestart(virNetServerClientPtr client,
                                      virJSONValuePtr object,
                                      void *opaque)
{
    virLockDaemonClientPtr priv = virLockDaemonClientNew(client, opaque);
    unsigned int ownerPid;
    const char *ownerUUID;
    const char *ownerName;

    if (!priv)
        return NULL;

    if (virJSONValueObjectGetBoolean(object, "restricted", &priv->restricted) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing restricted data in JSON document"));
        goto error;
    }
    if (virJSONValueObjectGetNumberUint(object, "ownerPid", &ownerPid) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing ownerPid data in JSON document"));
        goto error;
    }
    priv->ownerPid = (pid_t)ownerPid;
    if (virJSONValueObjectGetNumberUint(object, "ownerId", &priv->ownerId) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing ownerId data in JSON document"));
        goto error;
    }
    if (!(ownerName = virJSONValueObjectGetString(object, "ownerName"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing ownerName data in JSON document"));
        goto error;
    }
    priv->ownerName = g_strdup(ownerName);
    if (!(ownerUUID = virJSONValueObjectGetString(object, "ownerUUID"))) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing ownerUUID data in JSON document"));
        goto error;
    }
    if (virUUIDParse(ownerUUID, priv->ownerUUID) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing ownerUUID data in JSON document"));
        goto error;
    }
    return priv;

 error:
    virLockDaemonClientFree(priv);
    return NULL;
}


static virJSONValuePtr
virLockDaemonClientPreExecRestart(virNetServerClientPtr client G_GNUC_UNUSED,
                                  void *opaque)
{
    virLockDaemonClientPtr priv = opaque;
    virJSONValuePtr object = virJSONValueNewObject();
    char uuidstr[VIR_UUID_STRING_BUFLEN];

    if (virJSONValueObjectAppendBoolean(object, "restricted", priv->restricted) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Cannot set restricted data in JSON document"));
        goto error;
    }
    if (virJSONValueObjectAppendNumberUint(object, "ownerPid", priv->ownerPid) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Cannot set ownerPid data in JSON document"));
        goto error;
    }
    if (virJSONValueObjectAppendNumberUint(object, "ownerId", priv->ownerId) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Cannot set ownerId data in JSON document"));
        goto error;
    }
    if (virJSONValueObjectAppendString(object, "ownerName", priv->ownerName) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Cannot set ownerName data in JSON document"));
        goto error;
    }
    virUUIDFormat(priv->ownerUUID, uuidstr);
    if (virJSONValueObjectAppendString(object, "ownerUUID", uuidstr) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Cannot set ownerUUID data in JSON document"));
        goto error;
    }

    return object;

 error:
    virJSONValueFree(object);
    return NULL;
}


static int
virLockDaemonExecRestartStatePath(bool privileged,
                                  char **state_file)
{
    if (privileged) {
        *state_file = g_strdup(RUNSTATEDIR "/virtlockd-restart-exec.json");
    } else {
        g_autofree char *rundir = NULL;
        mode_t old_umask;

        rundir = virGetUserRuntimeDirectory();

        old_umask = umask(077);
        if (virFileMakePath(rundir) < 0) {
            umask(old_umask);
            return -1;
        }
        umask(old_umask);

        *state_file = g_strdup_printf("%s/virtlockd-restart-exec.json", rundir);
    }

    return 0;
}


static char *
virLockDaemonGetExecRestartMagic(void)
{
    char *ret;

    ret = g_strdup_printf("%lld", (long long int)getpid());
    return ret;
}


static int
virLockDaemonPostExecRestart(const char *state_file,
                             const char *pid_file,
                             int *pid_file_fd,
                             bool privileged)
{
    const char *gotmagic;
    char *wantmagic = NULL;
    int ret = -1;
    char *state = NULL;
    virJSONValuePtr object = NULL;

    VIR_DEBUG("Running post-restart exec");

    if (!virFileExists(state_file)) {
        VIR_DEBUG("No restart state file %s present",
                  state_file);
        ret = 0;
        goto cleanup;
    }

    if (virFileReadAll(state_file,
                       1024 * 1024 * 10, /* 10 MB */
                       &state) < 0)
        goto cleanup;

    VIR_DEBUG("Loading state %s", state);

    if (!(object = virJSONValueFromString(state)))
        goto cleanup;

    gotmagic = virJSONValueObjectGetString(object, "magic");
    if (!gotmagic) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Missing magic data in JSON document"));
        goto cleanup;
    }

    if (!(wantmagic = virLockDaemonGetExecRestartMagic()))
        goto cleanup;

    if (STRNEQ(gotmagic, wantmagic)) {
        VIR_WARN("Found restart exec file with old magic %s vs wanted %s",
                 gotmagic, wantmagic);
        ret = 0;
        goto cleanup;
    }

    /* Re-claim PID file now as we will not be daemonizing */
    if (pid_file &&
        (*pid_file_fd = virPidFileAcquirePath(pid_file, false, getpid())) < 0)
        goto cleanup;

    if (!(lockDaemon = virLockDaemonNewPostExecRestart(object, privileged)))
        goto cleanup;

    ret = 1;

 cleanup:
    unlink(state_file);
    VIR_FREE(wantmagic);
    VIR_FREE(state);
    virJSONValueFree(object);
    return ret;
}


static int
virLockDaemonPreExecRestart(const char *state_file,
                            virNetDaemonPtr dmn,
                            char **argv)
{
    virJSONValuePtr child;
    char *state = NULL;
    virJSONValuePtr object = virJSONValueNewObject();
    char *magic;
    virHashKeyValuePairPtr pairs = NULL, tmp;
    virJSONValuePtr lockspaces;

    VIR_DEBUG("Running pre-restart exec");

    if (!(child = virNetDaemonPreExecRestart(dmn)))
        goto cleanup;

    if (virJSONValueObjectAppend(object, "daemon", child) < 0) {
        virJSONValueFree(child);
        goto cleanup;
    }

    if (!(child = virLockSpacePreExecRestart(lockDaemon->defaultLockspace)))
        goto cleanup;

    if (virJSONValueObjectAppend(object, "defaultLockspace", child) < 0) {
        virJSONValueFree(child);
        goto cleanup;
    }

    lockspaces = virJSONValueNewArray();

    if (virJSONValueObjectAppend(object, "lockspaces", lockspaces) < 0) {
        virJSONValueFree(lockspaces);
        goto cleanup;
    }


    tmp = pairs = virHashGetItems(lockDaemon->lockspaces, NULL);
    while (tmp && tmp->key) {
        virLockSpacePtr lockspace = (virLockSpacePtr)tmp->value;

        if (!(child = virLockSpacePreExecRestart(lockspace)))
            goto cleanup;

        if (virJSONValueArrayAppend(lockspaces, child) < 0) {
            virJSONValueFree(child);
            goto cleanup;
        }

        tmp++;
    }

    if (!(magic = virLockDaemonGetExecRestartMagic()))
        goto cleanup;

    if (virJSONValueObjectAppendString(object, "magic", magic) < 0) {
        VIR_FREE(magic);
        goto cleanup;
    }

    if (!(state = virJSONValueToString(object, true)))
        goto cleanup;

    VIR_DEBUG("Saving state %s", state);

    if (virFileWriteStr(state_file,
                        state, 0700) < 0) {
        virReportSystemError(errno,
                             _("Unable to save state file %s"),
                             state_file);
        goto cleanup;
    }

    if (execvp(argv[0], argv) < 0) {
        virReportSystemError(errno, "%s",
                             _("Unable to restart self"));
        goto cleanup;
    }

    abort(); /* This should be impossible to reach */

 cleanup:
    VIR_FREE(pairs);
    VIR_FREE(state);
    virJSONValueFree(object);
    return -1;
}


static void
virLockDaemonUsage(const char *argv0, bool privileged)
{
    fprintf(stderr,
            _("\n"
              "Usage:\n"
              "  %s [options]\n"
              "\n"
              "Options:\n"
              "  -h | --help            Display program help:\n"
              "  -v | --verbose         Verbose messages.\n"
              "  -d | --daemon          Run as a daemon & write PID file.\n"
              "  -t | --timeout <secs>  Exit after timeout period.\n"
              "  -f | --config <file>   Configuration file.\n"
              "  -V | --version         Display version information.\n"
              "  -p | --pid-file <file> Change name of PID file.\n"
              "\n"
              "libvirt lock management daemon:\n"), argv0);

    if (privileged) {
        fprintf(stderr,
                _("\n"
                  "  Default paths:\n"
                  "\n"
                  "    Configuration file (unless overridden by -f):\n"
                  "      %s/libvirt/virtlockd.conf\n"
                  "\n"
                  "    Sockets:\n"
                  "      %s/libvirt/virtlockd-sock\n"
                  "\n"
                  "    PID file (unless overridden by -p):\n"
                  "      %s/virtlockd.pid\n"
                  "\n"),
                SYSCONFDIR,
                RUNSTATEDIR,
                RUNSTATEDIR);
    } else {
        fprintf(stderr, "%s",
                _("\n"
                  "  Default paths:\n"
                  "\n"
                  "    Configuration file (unless overridden by -f):\n"
                  "      $XDG_CONFIG_HOME/libvirt/virtlockd.conf\n"
                  "\n"
                  "    Sockets:\n"
                  "      $XDG_RUNTIME_DIR/libvirt/virtlockd-sock\n"
                  "\n"
                  "    PID file:\n"
                  "      $XDG_RUNTIME_DIR/libvirt/virtlockd.pid\n"
                  "\n"));
    }
}

int main(int argc, char **argv) {
    virNetServerPtr lockSrv = NULL;
    virNetServerPtr adminSrv = NULL;
    virNetServerProgramPtr lockProgram = NULL;
    virNetServerProgramPtr adminProgram = NULL;
    char *remote_config_file = NULL;
    int statuswrite = -1;
    int ret = 1;
    int verbose = 0;
    int godaemon = 0;
    char *run_dir = NULL;
    char *pid_file = NULL;
    int pid_file_fd = -1;
    char *sock_file = NULL;
    char *admin_sock_file = NULL;
    int timeout = 0;         /* -t: Shutdown timeout */
    char *state_file = NULL;
    bool implicit_conf = false;
    mode_t old_umask;
    bool privileged = false;
    virLockDaemonConfigPtr config = NULL;
    int rv;

    struct option opts[] = {
        { "verbose", no_argument, &verbose, 'v'},
        { "daemon", no_argument, &godaemon, 'd'},
        { "config", required_argument, NULL, 'f'},
        { "timeout", required_argument, NULL, 't'},
        { "pid-file", required_argument, NULL, 'p'},
        { "version", no_argument, NULL, 'V' },
        { "help", no_argument, NULL, 'h' },
        {0, 0, 0, 0}
    };

    privileged = geteuid() == 0;

    if (virGettextInitialize() < 0 ||
        virErrorInitialize() < 0) {
        fprintf(stderr, _("%s: initialization failed\n"), argv[0]);
        exit(EXIT_FAILURE);
    }

    while (1) {
        int optidx = 0;
        int c;
        char *tmp;

        c = getopt_long(argc, argv, "df:p:t:vVh", opts, &optidx);

        if (c == -1)
            break;

        switch (c) {
        case 0:
            /* Got one of the flags */
            break;
        case 'v':
            verbose = 1;
            break;
        case 'd':
            godaemon = 1;
            break;

        case 't':
            if (virStrToLong_i(optarg, &tmp, 10, &timeout) != 0
                || timeout < 0
                /* Ensure that we can multiply by 1000 without overflowing.  */
                || timeout > INT_MAX / 1000) {
                VIR_ERROR(_("Invalid value for timeout"));
                exit(EXIT_FAILURE);
            }
            break;

        case 'p':
            VIR_FREE(pid_file);
            pid_file = g_strdup(optarg);
            break;

        case 'f':
            VIR_FREE(remote_config_file);
            remote_config_file = g_strdup(optarg);
            break;

        case 'V':
            virLockDaemonVersion(argv[0]);
            exit(EXIT_SUCCESS);

        case 'h':
            virLockDaemonUsage(argv[0], privileged);
            exit(EXIT_SUCCESS);

        case '?':
        default:
            virLockDaemonUsage(argv[0], privileged);
            exit(EXIT_FAILURE);
        }
    }

    virFileActivateDirOverrideForProg(argv[0]);

    if (!(config = virLockDaemonConfigNew(privileged))) {
        VIR_ERROR(_("Can't create initial configuration"));
        exit(EXIT_FAILURE);
    }

    /* No explicit config, so try and find a default one */
    if (remote_config_file == NULL) {
        implicit_conf = true;
        if (virLockDaemonConfigFilePath(privileged,
                                        &remote_config_file) < 0) {
            VIR_ERROR(_("Can't determine config path"));
            exit(EXIT_FAILURE);
        }
    }

    /* Read the config file if it exists */
    if (remote_config_file &&
        virLockDaemonConfigLoadFile(config, remote_config_file, implicit_conf) < 0) {
        VIR_ERROR(_("Can't load config file: %s: %s"),
                  virGetLastErrorMessage(), remote_config_file);
        exit(EXIT_FAILURE);
    }
    VIR_FREE(remote_config_file);

    virDaemonSetupLogging("virtlockd",
                          config->log_level,
                          config->log_filters,
                          config->log_outputs,
                          privileged,
                          verbose,
                          godaemon);

    if (!pid_file &&
        virPidFileConstructPath(privileged,
                                RUNSTATEDIR,
                                "virtlockd",
                                &pid_file) < 0) {
        VIR_ERROR(_("Can't determine pid file path."));
        exit(EXIT_FAILURE);
    }
    VIR_DEBUG("Decided on pid file path '%s'", NULLSTR(pid_file));

    if (virDaemonUnixSocketPaths("virtlockd",
                                 privileged,
                                 NULL,
                                 &sock_file,
                                 NULL,
                                 &admin_sock_file) < 0) {
        VIR_ERROR(_("Can't determine socket paths"));
        exit(EXIT_FAILURE);
    }
    VIR_DEBUG("Decided on socket paths '%s' and '%s'",
              sock_file, admin_sock_file);

    if (virLockDaemonExecRestartStatePath(privileged,
                                          &state_file) < 0) {
        VIR_ERROR(_("Can't determine restart state file path"));
        exit(EXIT_FAILURE);
    }
    VIR_DEBUG("Decided on restart state file path '%s'",
              state_file);

    /* Ensure the rundir exists (on tmpfs on some systems) */
    if (privileged) {
        run_dir = g_strdup(RUNSTATEDIR "/libvirt");
    } else {
        run_dir = virGetUserRuntimeDirectory();
    }

    if (privileged)
        old_umask = umask(022);
    else
        old_umask = umask(077);
    VIR_DEBUG("Ensuring run dir '%s' exists", run_dir);
    if (virFileMakePath(run_dir) < 0) {
        VIR_ERROR(_("unable to create rundir %s: %s"), run_dir,
                  g_strerror(errno));
        ret = VIR_DAEMON_ERR_RUNDIR;
        umask(old_umask);
        goto cleanup;
    }
    umask(old_umask);

    if ((rv = virLockDaemonPostExecRestart(state_file,
                                           pid_file,
                                           &pid_file_fd,
                                           privileged)) < 0) {
        ret = VIR_DAEMON_ERR_INIT;
        goto cleanup;
    }

    /* rv == 1 means we successfully restored from the saved internal state
     * (but still need to add @lockProgram into @srv). rv == 0 means that no
     * saved state is present, therefore initialize from scratch here. */
    if (rv == 0) {
        g_autoptr(virSystemdActivation) act = NULL;
        virSystemdActivationMap actmap[] = {
            { .name = "virtlockd.socket", .family = AF_UNIX, .path = sock_file },
            { .name = "virtlockd-admin.socket", .family = AF_UNIX, .path = admin_sock_file },
        };

        if (godaemon) {
            if (chdir("/") < 0) {
                VIR_ERROR(_("cannot change to root directory: %s"),
                          g_strerror(errno));
                goto cleanup;
            }

            if ((statuswrite = virDaemonForkIntoBackground(argv[0])) < 0) {
                VIR_ERROR(_("Failed to fork as daemon: %s"),
                          g_strerror(errno));
                goto cleanup;
            }
        }

        /* If we have a pidfile set, claim it now, exiting if already taken */
        if ((pid_file_fd = virPidFileAcquirePath(pid_file, false, getpid())) < 0) {
            ret = VIR_DAEMON_ERR_PIDFILE;
            goto cleanup;
        }

        if (!(lockDaemon = virLockDaemonNew(config, privileged))) {
            ret = VIR_DAEMON_ERR_INIT;
            goto cleanup;
        }

        if (virSystemdGetActivation(actmap,
                                    G_N_ELEMENTS(actmap),
                                    &act) < 0) {
            ret = VIR_DAEMON_ERR_NETWORK;
            goto cleanup;
        }

        lockSrv = virNetDaemonGetServer(lockDaemon->dmn, "virtlockd");
        adminSrv = virNetDaemonGetServer(lockDaemon->dmn, "admin");

        if (virNetServerAddServiceUNIX(lockSrv,
                                       act, "virtlockd.socket",
                                       sock_file, 0700, 0, 0,
                                       NULL,
                                       false, 0, 1) < 0) {
            ret = VIR_DAEMON_ERR_NETWORK;
            goto cleanup;
        }
        if (virNetServerAddServiceUNIX(adminSrv,
                                       act, "virtlockd-admin.socket",
                                       admin_sock_file, 0700, 0, 0,
                                       NULL,
                                       false, 0, 1) < 0) {
            ret = VIR_DAEMON_ERR_NETWORK;
            goto cleanup;
        }

        if (act &&
            virSystemdActivationComplete(act) < 0) {
            ret = VIR_DAEMON_ERR_NETWORK;
            goto cleanup;
        }
    } else {
        lockSrv = virNetDaemonGetServer(lockDaemon->dmn, "virtlockd");
        /* If exec-restarting from old virtlockd, we won't have an
         * admin server present */
        if (virNetDaemonHasServer(lockDaemon->dmn, "admin"))
            adminSrv = virNetDaemonGetServer(lockDaemon->dmn, "admin");
    }

    if (timeout > 0) {
        VIR_DEBUG("Registering shutdown timeout %d", timeout);
        virNetDaemonAutoShutdown(lockDaemon->dmn,
                                 timeout);
    }

    if ((virLockDaemonSetupSignals(lockDaemon->dmn)) < 0) {
        ret = VIR_DAEMON_ERR_SIGNAL;
        goto cleanup;
    }

    if (!(lockProgram = virNetServerProgramNew(VIR_LOCK_SPACE_PROTOCOL_PROGRAM,
                                               VIR_LOCK_SPACE_PROTOCOL_PROGRAM_VERSION,
                                               virLockSpaceProtocolProcs,
                                               virLockSpaceProtocolNProcs))) {
        ret = VIR_DAEMON_ERR_INIT;
        goto cleanup;
    }

    if (virNetServerAddProgram(lockSrv, lockProgram) < 0) {
        ret = VIR_DAEMON_ERR_INIT;
        goto cleanup;
    }

    if (adminSrv != NULL) {
        if (!(adminProgram = virNetServerProgramNew(ADMIN_PROGRAM,
                                                    ADMIN_PROTOCOL_VERSION,
                                                    adminProcs,
                                                    adminNProcs))) {
            ret = VIR_DAEMON_ERR_INIT;
            goto cleanup;
        }
        if (virNetServerAddProgram(adminSrv, adminProgram) < 0) {
            ret = VIR_DAEMON_ERR_INIT;
            goto cleanup;
        }
    }

    /* Disable error func, now logging is setup */
    virSetErrorFunc(NULL, virLockDaemonErrorHandler);


    /* Tell parent of daemon that basic initialization is complete
     * In particular we're ready to accept net connections & have
     * written the pidfile
     */
    if (statuswrite != -1) {
        char status = 0;
        while (write(statuswrite, &status, 1) == -1 &&
               errno == EINTR)
            ;
        VIR_FORCE_CLOSE(statuswrite);
    }

    /* Start accepting new clients from network */

    virNetDaemonUpdateServices(lockDaemon->dmn, true);
    virNetDaemonRun(lockDaemon->dmn);

    if (execRestart &&
        virLockDaemonPreExecRestart(state_file,
                                    lockDaemon->dmn,
                                    argv) < 0)
        ret = VIR_DAEMON_ERR_REEXEC;
    else
        ret = 0;

 cleanup:
    virObjectUnref(lockProgram);
    virObjectUnref(adminProgram);
    virObjectUnref(lockSrv);
    virObjectUnref(adminSrv);
    virLockDaemonFree(lockDaemon);
    if (statuswrite != -1) {
        if (ret != 0) {
            /* Tell parent of daemon what failed */
            char status = ret;
            while (write(statuswrite, &status, 1) == -1 &&
                   errno == EINTR)
                ;
        }
        VIR_FORCE_CLOSE(statuswrite);
    }
    if (pid_file_fd != -1)
        virPidFileReleasePath(pid_file, pid_file_fd);
    VIR_FREE(pid_file);
    VIR_FREE(sock_file);
    VIR_FREE(admin_sock_file);
    VIR_FREE(state_file);
    VIR_FREE(run_dir);
    virLockDaemonConfigFree(config);
    return ret;
}
