/* Copyright (C) 2011,2015 the GSS-PROXY contributors, see COPYING for license */

#include "config.h"
#include <stdlib.h>
#include "popt.h"
#include "gp_proxy.h"
#include <signal.h>
#include <string.h>

const int vflags =
    VERTO_EV_FLAG_PERSIST |
    VERTO_EV_FLAG_IO_READ |
    VERTO_EV_FLAG_IO_CLOSE_FD;

char *opt_config_file = NULL;
char *opt_config_dir = NULL;
char *opt_config_socket = NULL;
int opt_daemon = 0;

struct gssproxy_ctx *gpctx;

static struct gp_service *
find_service_by_name(struct gp_config *cfg, const char *name)
{
    int i;
    struct gp_service *ret = NULL;

    for (i = 0; i < cfg->num_svcs; i++) {
        if (strcmp(cfg->svcs[i]->name, name) == 0) {
            ret = cfg->svcs[i];
            break;
        }
    }
    return ret;
}

static verto_ev *setup_socket(char *sock_name, verto_ctx *vctx)
{
    struct gp_sock_ctx *sock_ctx;
    verto_ev *ev;

    sock_ctx = init_unix_socket(gpctx, sock_name);
    if (!sock_ctx) {
        return NULL;
    }

    ev = verto_add_io(vctx, vflags, accept_sock_conn, sock_ctx->fd);
    if (!ev) {
        return NULL;
    }

    verto_set_private(ev, sock_ctx, free_unix_socket);
    return ev;
}

static int init_sockets(verto_ctx *vctx, struct gp_config *old_config)
{
    int i;
    struct gp_sock_ctx *sock_ctx;
    verto_ev *ev;
    struct gp_service *svc;

    /* init main socket */
    if (!old_config) {
        ev = setup_socket(gpctx->config->socket_name, vctx);
        if (!ev) {
            return 1;
        }

        gpctx->sock_ev = ev;
    } else if (strcmp(old_config->socket_name,
                      gpctx->config->socket_name) != 0) {
        ev = setup_socket(gpctx->config->socket_name, vctx);
        if (!ev) {
            return 1;
        }

        gpctx->sock_ev = ev;
        verto_del(gpctx->sock_ev);
    } else {
        /* free_config will erase the socket name; update it accordingly */
        sock_ctx = verto_get_private(gpctx->sock_ev);
        sock_ctx->socket = gpctx->config->socket_name;
    }

    /* propagate any sockets that shouldn't change */
    if (old_config) {
        for (i = 0; i < old_config->num_svcs; i++) {
            if (old_config->svcs[i]->ev) {
                svc = find_service_by_name(gpctx->config,
                                           old_config->svcs[i]->name);
                if (svc &&
                    ((svc->socket == old_config->svcs[i]->socket) ||
                     ((svc->socket != NULL) &&
                      (old_config->svcs[i]->socket != NULL) &&
                      strcmp(svc->socket,
                             old_config->svcs[i]->socket) == 0))) {
                    svc->ev = old_config->svcs[i]->ev;
                    sock_ctx = verto_get_private(svc->ev);
                    sock_ctx->socket = svc->socket;
                } else {
                    verto_del(old_config->svcs[i]->ev);
                }
            }
        }
    }

    /* init all other sockets */
    for (i = 0; i < gpctx->config->num_svcs; i++) {
        svc = gpctx->config->svcs[i];
        if (svc->socket != NULL && svc->ev == NULL) {
            ev = setup_socket(svc->socket, vctx);
            if (!ev) {
                return 1;
            }
            svc->ev = ev;
        }
    }
    return 0;
}

static void hup_handler(verto_ctx *vctx, verto_ev *ev UNUSED)
{
    int ret;
    struct gp_config *new_config, *old_config;

    GPDEBUG("Received SIGHUP; re-reading config.\n");
    new_config = read_config(opt_config_file, opt_config_dir,
                             opt_config_socket, opt_daemon);
    if (!new_config) {
        GPERROR("Error reading new configuration on SIGHUP; keeping old "
                "configuration instead!\n");
        return;
    }
    old_config = gpctx->config;
    gpctx->config = new_config;

    ret = init_sockets(vctx, old_config);
    if (ret != 0) {
        exit(ret);
    }

    /* conditionally reload kernel interface */
    init_proc_nfsd(gpctx->config);

    free_config(&old_config);

    GPDEBUG("New config loaded successfully.\n");
    return;
}

int main(int argc, const char *argv[])
{
    int opt;
    poptContext pc;
    int opt_interactive = 0;
    int opt_version = 0;
    int opt_debug = 0;
    int opt_debug_level = 0;
    verto_ctx *vctx;
    verto_ev *ev;
    int wait_fd;
    int ret = -1;

    /* initialize debug client id to 0 in the main thread */
    /* we do this early, before any code starts using debug statements */
    gp_debug_set_conn_id(0);

    struct poptOption long_options[] = {
        POPT_AUTOHELP
        {"daemon", 'D', POPT_ARG_NONE, &opt_daemon, 0, \
         _("Become a daemon (default)"), NULL }, \
        {"interactive", 'i', POPT_ARG_NONE, &opt_interactive, 0, \
         _("Run interactive (not a daemon)"), NULL}, \
        {"config", 'c', POPT_ARG_STRING, &opt_config_file, 0, \
         _("Specify a non-default config file"), NULL}, \
        {"configdir", 'C', POPT_ARG_STRING, &opt_config_dir, 0, \
         _("Specify a non-default config directory"), NULL}, \
        {"socket", 's', POPT_ARG_STRING, &opt_config_socket, 0, \
         _("Specify a custom default socket"), NULL}, \
        {"debug", 'd', POPT_ARG_NONE, &opt_debug, 0, \
         _("Enable debugging"), NULL}, \
        {"debug-level", '\0', POPT_ARG_INT, &opt_debug_level, 0, \
         _("Set debugging level"), NULL}, \
        {"version", '\0', POPT_ARG_NONE, &opt_version, 0, \
         _("Print version number and exit"), NULL }, \
        POPT_TABLEEND
    };

    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
    while((opt = poptGetNextOpt(pc)) != -1) {
        switch(opt) {
        default:
            fprintf(stderr, "\nInvalid option %s: %s\n\n",
                    poptBadOption(pc, 0), poptStrerror(opt));
            poptPrintUsage(pc, stderr, 0);

            ret = 1;
            goto cleanup;
        }
    }

    if (opt_version) {
        puts(VERSION""DISTRO_VERSION""PRERELEASE_VERSION);
        ret = 0;
        goto cleanup;
    }

    if (opt_debug || opt_debug_level > 0) {
        if (opt_debug_level == 0) opt_debug_level = 1;
        gp_debug_args(opt_debug_level);
    }

    if (opt_daemon && opt_interactive) {
        fprintf(stderr, "Option -i|--interactive is not allowed together with -D|--daemon\n");
        poptPrintUsage(pc, stderr, 0);
        ret = 0;
        goto cleanup;
    }

    if (opt_interactive) {
        opt_daemon = 2;
    }

    gpctx = calloc(1, sizeof(struct gssproxy_ctx));

    gpctx->config = read_config(opt_config_file,
                                opt_config_dir,
                                opt_config_socket,
                                opt_daemon);
    if (!gpctx->config) {
        ret = EXIT_FAILURE;
        goto cleanup;
    }

    init_server(gpctx->config->daemonize, &wait_fd);

    write_pid();

    vctx = init_event_loop();
    if (!vctx) {
        fprintf(stderr, "Failed to initialize event loop. "
                        "Is there at least one libverto backend installed?\n");
        ret = 1;
        goto cleanup;
    }
    gpctx->vctx = vctx;

    /* Add SIGHUP here so that gpctx is in scope for the handler */
    ev = verto_add_signal(vctx, VERTO_EV_FLAG_PERSIST, hup_handler, SIGHUP);
    if (!ev) {
        fprintf(stderr, "Failed to register SIGHUP handler with verto!\n");
        ret = 1;
        goto cleanup;
    }

    ret = init_sockets(vctx, NULL);
    if (ret != 0) {
        goto cleanup;
    }

    /* We need to tell nfsd that GSS-Proxy is available before it starts,
     * as nfsd needs to know GSS-Proxy is in use before the first time it
     * needs to call accept_sec_context. */
    init_proc_nfsd(gpctx->config);

    /* Now it is safe to tell the init system that we're done starting up,
     * so it can continue with dependencies and start nfsd */
    init_done(wait_fd);

    ret = drop_privs(gpctx->config);
    if (ret) {
        ret = EXIT_FAILURE;
        goto cleanup;
    }

    ret = gp_workers_init(gpctx);
    if (ret) {
        ret = EXIT_FAILURE;
        goto cleanup;
    }

    verto_run(vctx);
    verto_free(vctx);

    gp_workers_free(gpctx->workers);

    fini_server();


    free_config(&gpctx->config);
    free(gpctx);

    ret = 0;

cleanup:
    poptFreeContext(pc);
    free(opt_config_file);
    free(opt_config_dir);
    free(opt_config_socket);

#ifdef HAVE_VERTO_CLEANUP
    verto_cleanup();
#endif

    return ret;
}
