#include "confmodule.h"
#include "commands.h"
#include "frontend.h"
#include "database.h"
#include "question.h"
#include "template.h"
#include "strutl.h"
#include "debconfclient.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>

#include <debian-installer.h>

#ifdef SELINUX_ENABLED
#include <selinux/selinux.h>
#else
#define setexecfilecon(filename, fallback) do {} while(0)
#endif

static commands_t commands[] = {
#include "commands-list.h"
    { 0, 0 }
};

volatile sig_atomic_t signal_received = 0;
volatile sig_atomic_t sigchld_status = 0;

/* private functions */
/*
 * @brief helper function to process incoming commands
 * @param struct confmodule *mod - confmodule object
 * @param char *in - input command
 * @param char *out - reply buffer
 * @param size_t outsize - reply buffer length
 * @return int - DC_OK, DC_NOTOK, DC_NOTIMPL
 */
static char *_confmodule_process(struct confmodule *mod, char *in)
{
    int i;
    char *retval = NULL;
    char *argv[2] = { "", "" };

    char *inp = strstrip(in);
	
    INFO(INFO_DEBUG, "--> %s", inp);

    if (*inp == '#') return NULL;

    if (mod->frontend->capability & DCF_CAPB_ESCAPE)
        strunescape(inp, inp, strlen(inp)+1, STRESCAPE_CAPB);

    strcmdsplit(inp, argv, DIM(argv));

    for (i = 0; commands[i].command != 0; i++)
    {
        if (strcasecmp(argv[0], commands[i].command) == 0) {
            retval = (*commands[i].handler)(mod, argv[1]);
            break;
        }
    }

    if (retval == NULL)
        fprintf(stderr, "E: Unimplemented function\n");
		
    return retval;
}

/*
 * @brief mark a file descriptor close-on-exec
 * @param int fd - file descriptor
 * @return int - 0 on success, -1 with errno set on error
 */
static int _confmodule_cloexec(int fd)
{
    int flags = fcntl (fd, F_GETFD);
    if (flags < 0)
        return flags;
    if (fcntl (fd, F_SETFD, flags | FD_CLOEXEC) < 0)
        return -1;
    return 0;
}

/* public functions */
static int confmodule_communicate(struct confmodule *mod)
{
    char buf[1023];
    char *in;
    size_t insize = 1024;
    char *out;
    //size_t outsize = 4096;
    int ret = 0;

    in = malloc(insize);
    if (!in)
        return DC_NOTOK;
    memset(in, 0, insize);

    //out = malloc(outsize);
    //if (!out)
    //return DC_NOTOK;
    //memset(out, 0, outsize);

    while (1) {
        buf[0] = 0;
        in[0] = 0;
        while (strchr(buf, '\n') == NULL) {
            if (signal_received) {
                free(in);
                return DC_OK;
            }

            ret = read(mod->infd, buf, sizeof(buf) - 1);
            if (ret < 0) {
                if (errno == EINTR)
                    continue;
                free(in);
                return DC_NOTOK;
            }
            if (ret == 0) {
                free(in);
                return DC_OK;
            }
            buf[ret] = 0;
            if (strlen(in) + ret + 1 > insize) {
                insize += sizeof(buf);
                in = realloc(in, insize);
            }
            strcat(in, buf);
        }

        if (signal_received) {
            free(in);
            return DC_OK;
        }

        out = _confmodule_process(mod, in);
        if (out == NULL) {
            continue;
        }
        if (out[0] == 0) /* STOP called */
        {
            ret = DC_OK;
            break;
        }
        INFO(INFO_DEBUG, "<-- %s", out);
        write(mod->outfd, out, strlen(out));
        write(mod->outfd, "\n", 1);
        free(out);
    }
    free(in);
    return ret;
}

static char *confmodule_process_command(struct confmodule *mod, char *cmd)
{
    char *out;

    out = _confmodule_process(mod, cmd);
    if (out == NULL) {
        asprintf(&out, "%u Not implemented", DC_NOTOK);
    }
    INFO(INFO_DEBUG, "<-- %s", out);

    return out;
}

static int confmodule_shutdown(struct confmodule *mod)
{
    int status;

    while (waitpid(mod->pid, &status, WNOHANG) > 0)
        sigchld_status = status;

    mod->exitcode = di_exec_mangle_status(sigchld_status);

    return mod->exitcode;
}

static inline void check_fd(int fd, int newfd, bool old[])
{
    if (fd <= 2)
        old[fd] = false;
    dup2(fd, newfd);
    close(fd);
}

static pid_t confmodule_run(struct confmodule *mod, int argc, char **argv)
{
    pid_t pid;
    int i;
    char **args;
    bool old[3] = { true, true, true };
    int config[5]; /* 0=read/to, 1=write/to, 2=read/from, 3=write/from, 4=null */
    pipe(&config[0]);
    pipe(&config[2]);
    switch ((pid = fork()))
    {
        case -1:
            mod->frontend->methods.shutdown(mod->frontend);
            DIE("Cannot execute client config script");
            break;
        case 0:
            /* 50=read/to, 51=write/to, 52=read/from, 53=write/from, 54=null */
            config[4] = open("/dev/null", O_RDWR);
            for (i = 0; i < 5; i++)
                check_fd(config[i], 50 + i, old);
            for (i = 0; i <= 2; i++)
                dup2(old[i] ? i : 54, DEBCONF_OLD_FD_BASE + i);
            dup2(50, 0); dup2(53, 1); dup2(53, 3);
            for (i = 0; i < 5; i++)
                close(50 + i);

            args = (char **)malloc(sizeof(char *) * argc);
            for (i = 1; i < argc; i++)
                args[i-1] = argv[i];
            args[argc-1] = NULL;
            setexecfilecon(argv[1],"dpkg_script_t");
            if (execv(argv[1], args) != 0)
                perror("execv");
            /* should never reach here, otherwise execv failed :( */
            exit (127);
        default:
            close(config[0]); close(config[3]);
            mod->infd = config[2];
            mod->outfd = config[1];
            _confmodule_cloexec(mod->infd);
            _confmodule_cloexec(mod->outfd);
    }
    mod->pid = pid;

    return pid;
}

static int confmodule_update_seen_questions(struct confmodule *mod, enum seen_action action)
{
    struct question *q;
    struct question *qlast = NULL;
    int i, narg;

    switch (action)
    {
        case STACK_SEEN_ADD:
            if (mod->seen_questions == NULL)
                narg = 0;
            else
                narg = mod->number_seen_questions;

            i = narg;
            for (q = mod->frontend->questions; q != NULL; q = q->next)
                narg++;
            if (narg == 0)
                return DC_OK;

            mod->seen_questions = (char **) realloc(mod->seen_questions, sizeof(char *) * narg);
            for (q = mod->frontend->questions; q != NULL; q = q->next)
            {
                *(mod->seen_questions+i) = strdup(q->tag);
                i++;
            }
            mod->number_seen_questions = i;
            break;
        case STACK_SEEN_REMOVE:
            if (mod->seen_questions == NULL)
                return DC_OK;

            for (q = mod->frontend->questions; q != NULL; q = q->next)
                qlast = q;

            for (q = qlast; q != NULL; q = q->prev)
            {
                if (strcmp(*(mod->seen_questions + mod->number_seen_questions - 1), q->tag) != 0)
                    return DC_OK;
                DELETE(*(mod->seen_questions + mod->number_seen_questions - 1));
                (mod->number_seen_questions) --;
                if (mod->number_seen_questions == 0)
                {
                    DELETE(mod->seen_questions);
                    break;
                }
            }
            break;
        case STACK_SEEN_SAVE:
            if (mod->seen_questions == NULL)
                return DC_OK;

            narg = mod->number_seen_questions;
            for (i = 0; i < narg; i++)
            {
                q = mod->questions->methods.get(mod->questions, *(mod->seen_questions+i));
                if (q == NULL)
                    return DC_NOTOK;
#ifndef DI_UDEB
                question_set_flag(q, DC_QFLAG_SEEN);
#endif
                DELETE(*(mod->seen_questions+i));
            }
            DELETE(mod->seen_questions);
            mod->number_seen_questions = 0;
            break;
        default:
            /* should never happen */
            DIE("Mismatch argument in confmodule_update_seen_questions");
    }

    return DC_OK;
}

static int confmodule_save(struct confmodule *mod)
{
    int ret = DC_OK;

    if (!load_all_translations())
    {
        /* This isn't entirely accurate; really it should be done in
         * rfc822db's implementation of mod->templates->methods.save().
         * However, that doesn't have convenient access to the questions
         * database, so we do it here instead for the time being.
         */
        struct question *q = mod->questions->methods.get(
            mod->questions, "debconf/translations-dropped");
        if (q != NULL)
        {
            if (strcmp(question_getvalue(q, ""), "true") != 0)
            {
                question_setvalue(q, "true");
                mod->questions->methods.set(mod->questions, q);
            }
            question_deref(q);
        }
    }

    ret |= mod->update_seen_questions(mod, STACK_SEEN_SAVE);
    if (mod->questions)
        ret |= mod->questions->methods.save(mod->questions);
    if (mod->templates)
        ret |= mod->templates->methods.save(mod->templates);
    return ret != DC_OK ? DC_NOTOK : DC_OK;
}

struct confmodule *confmodule_new(struct configuration *config,
        struct template_db *templates, struct question_db *questions, 
        struct frontend *frontend)
{
    struct confmodule *mod = NEW(struct confmodule);
    memset(mod, 0, sizeof(struct confmodule));

    mod->exitcode = 126;
    mod->config = config;
    mod->templates = templates;
    mod->questions = questions;
    mod->frontend = frontend;
    mod->seen_questions = NULL;
    mod->backed_up = 0;
    mod->run = confmodule_run;
    mod->communicate = confmodule_communicate;
    mod->process_command = confmodule_process_command;
    mod->shutdown = confmodule_shutdown;
    mod->update_seen_questions = confmodule_update_seen_questions;
    mod->save = confmodule_save;

    /* TODO: I wish we don't need gross hacks like this.... */
    setenv("DEBIAN_HAS_FRONTEND", "1", 1);

    return mod;
}

void confmodule_delete(struct confmodule *mod)
{
    DELETE(mod);
}

/* vim: expandtab sw=4
*/
