/*
   Copyright (c) 2010-2012 Red Hat, Inc. <http://www.redhat.com>
   This file is part of GlusterFS.

   This file is licensed to you under your choice of the GNU Lesser
   General Public License, version 3 or any later version (LGPLv3 or
   later), or the GNU General Public License, version 2 (GPLv2), in all
   cases as published by the Free Software Foundation.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>

#include "cli.h"
#include "cli-cmd.h"
#include "cli-mem-types.h"
#include "protocol-common.h"

int
cli_cmd_system_help_cbk(struct cli_state *state, struct cli_cmd_word *in_word,
                        const char **words, int wordcount);

int
cli_cmd_copy_file_cbk(struct cli_state *state, struct cli_cmd_word *word,
                      const char **words, int wordcount);

int
cli_cmd_sys_exec_cbk(struct cli_state *state, struct cli_cmd_word *word,
                     const char **words, int wordcount);

int
cli_cmd_getspec_cbk(struct cli_state *state, struct cli_cmd_word *word,
                    const char **words, int wordcount)
{
    int ret = -1;
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    dict_t *dict = NULL;

    if (wordcount != 3) {
        cli_usage_out(word->pattern);
        goto out;
    }

    dict = dict_new();
    if (!dict)
        goto out;

    ret = dict_set_str(dict, "volid", (char *)words[2]);
    if (ret)
        goto out;

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_GETSPEC];
    if (proc->fn) {
        frame = create_frame(THIS, THIS->ctx->pool);
        if (!frame) {
            ret = -1;
            goto out;
        }
        ret = proc->fn(frame, THIS, dict);
    }

out:
    if (!proc && ret) {
        if (wordcount > 1)
            cli_out("Fetching spec for volume %s failed", (char *)words[2]);
    }

    CLI_STACK_DESTROY(frame);
    if (dict)
        dict_unref(dict);

    return ret;
}

int
cli_cmd_pmap_b2p_cbk(struct cli_state *state, struct cli_cmd_word *word,
                     const char **words, int wordcount)
{
    int ret = -1;
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    dict_t *dict = NULL;

    if (wordcount != 4) {
        cli_usage_out(word->pattern);
        goto out;
    }

    dict = dict_new();
    if (!dict)
        goto out;

    ret = dict_set_str(dict, "brick", (char *)words[3]);
    if (ret)
        goto out;

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_PMAP_PORTBYBRICK];
    if (proc->fn) {
        frame = create_frame(THIS, THIS->ctx->pool);
        if (!frame) {
            ret = -1;
            goto out;
        }
        ret = proc->fn(frame, THIS, dict);
    }

out:
    if (!proc && ret) {
        if (wordcount > 1)
            cli_out("Fetching spec for volume %s failed", (char *)words[3]);
    }

    CLI_STACK_DESTROY(frame);
    if (dict)
        dict_unref(dict);

    return ret;
}

int
cli_cmd_fsm_log_cbk(struct cli_state *state, struct cli_cmd_word *word,
                    const char **words, int wordcount)
{
    int ret = -1;
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    char *name = "";

    if ((wordcount != 4) && (wordcount != 3)) {
        cli_usage_out(word->pattern);
        goto out;
    }

    if (wordcount == 4)
        name = (char *)words[3];
    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_FSM_LOG];
    if (proc && proc->fn) {
        frame = create_frame(THIS, THIS->ctx->pool);
        if (!frame)
            goto out;
        ret = proc->fn(frame, THIS, (void *)name);
    }
out:
    return ret;
}

int
cli_cmd_getwd_cbk(struct cli_state *state, struct cli_cmd_word *word,
                  const char **words, int wordcount)
{
    int ret = -1;
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;

    if (wordcount != 2) {
        cli_usage_out(word->pattern);
        goto out;
    }

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_GETWD];
    if (proc && proc->fn) {
        frame = create_frame(THIS, THIS->ctx->pool);
        if (!frame)
            goto out;
        ret = proc->fn(frame, THIS, NULL);
    }
out:
    return ret;
}

static dict_t *
make_seq_dict(int argc, char **argv)
{
    char index[] = "4294967296";  // 1<<32
    int i = 0;
    int len;
    int ret = 0;
    dict_t *dict = dict_new();

    if (!dict)
        return NULL;

    for (i = 0; i < argc; i++) {
        len = snprintf(index, sizeof(index), "%d", i);
        ret = dict_set_strn(dict, index, len, argv[i]);
        if (ret == -1)
            break;
    }

    if (ret) {
        dict_unref(dict);
        dict = NULL;
    }

    return dict;
}

int
cli_cmd_mount_cbk(struct cli_state *state, struct cli_cmd_word *word,
                  const char **words, int wordcount)
{
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    int ret = -1;
    dict_t *dict = NULL;
    void *dataa[] = {NULL, NULL};

    if (wordcount < 4) {
        cli_usage_out(word->pattern);
        goto out;
    }

    dict = make_seq_dict(wordcount - 3, (char **)words + 3);
    if (!dict)
        goto out;

    dataa[0] = (void *)words[2];
    dataa[1] = dict;

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_MOUNT];
    if (proc && proc->fn) {
        frame = create_frame(THIS, THIS->ctx->pool);
        if (!frame)
            goto out;
        ret = proc->fn(frame, THIS, dataa);
    }

out:
    if (dict)
        dict_unref(dict);

    if (!proc && ret)
        cli_out("Mount command failed");

    return ret;
}

int
cli_cmd_umount_cbk(struct cli_state *state, struct cli_cmd_word *word,
                   const char **words, int wordcount)
{
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    int ret = -1;
    dict_t *dict = NULL;

    if (!(wordcount == 3 ||
          (wordcount == 4 && strcmp(words[3], "lazy") == 0))) {
        cli_usage_out(word->pattern);
        goto out;
    }

    dict = dict_new();
    if (!dict)
        goto out;

    ret = dict_set_str(dict, "path", (char *)words[2]);
    if (ret != 0)
        goto out;
    ret = dict_set_int32(dict, "lazy", wordcount == 4);
    if (ret != 0)
        goto out;

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_UMOUNT];
    if (proc && proc->fn) {
        frame = create_frame(THIS, THIS->ctx->pool);
        if (!frame) {
            gf_log(THIS->name, GF_LOG_ERROR, "failed to create frame");
            ret = -1;
            goto out;
        }
        ret = proc->fn(frame, THIS, dict);
    }

out:
    if (dict)
        dict_unref(dict);

    if (!proc && ret)
        cli_out("Umount command failed");

    return ret;
}

int
cli_cmd_uuid_get_cbk(struct cli_state *state, struct cli_cmd_word *word,
                     const char **words, int wordcount)
{
    int ret = -1;
    int sent = 0;
    int parse_error = 0;
    dict_t *dict = NULL;
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    cli_local_t *local = NULL;
    xlator_t *this = NULL;

    this = THIS;
    if (wordcount != 3) {
        cli_usage_out(word->pattern);
        parse_error = 1;
        goto out;
    }

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_UUID_GET];
    frame = create_frame(this, this->ctx->pool);
    if (!frame)
        goto out;

    dict = dict_new();
    if (!dict)
        goto out;

    CLI_LOCAL_INIT(local, words, frame, dict);
    if (proc->fn)
        ret = proc->fn(frame, this, dict);

out:
    if (ret) {
        cli_cmd_sent_status_get(&sent);
        if ((sent == 0) && (parse_error == 0))
            cli_out("uuid get failed");
    }

    CLI_STACK_DESTROY(frame);
    if (dict)
        dict_unref(dict);

    return ret;
}

int
cli_cmd_uuid_reset_cbk(struct cli_state *state, struct cli_cmd_word *word,
                       const char **words, int wordcount)
{
    int ret = -1;
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    int sent = 0;
    int parse_error = 0;
    gf_answer_t answer = GF_ANSWER_NO;
    char *question = NULL;
    cli_local_t *local = NULL;
    dict_t *dict = NULL;
    xlator_t *this = NULL;

    question =
        "Resetting uuid changes the uuid of local glusterd. "
        "Do you want to continue?";

    if (wordcount != 3) {
        cli_usage_out(word->pattern);
        parse_error = 1;
        goto out;
    }

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_UUID_RESET];

    this = THIS;
    frame = create_frame(this, this->ctx->pool);
    if (!frame)
        goto out;

    dict = dict_new();
    if (!dict) {
        ret = -1;
        goto out;
    }
    CLI_LOCAL_INIT(local, words, frame, dict);
    answer = cli_cmd_get_confirmation(state, question);

    if (GF_ANSWER_NO == answer) {
        ret = 0;
        goto out;
    }

    // send NULL as argument since no dictionary is sent to glusterd
    if (proc->fn) {
        ret = proc->fn(frame, this, dict);
    }

out:
    if (ret) {
        cli_cmd_sent_status_get(&sent);
        if ((sent == 0) && (parse_error == 0))
            cli_out("uuid reset failed");
    }

    CLI_STACK_DESTROY(frame);
    if (dict)
        dict_unref(dict);

    return ret;
}

static struct cli_cmd cli_system_cmds[] = {
    {"system:: getspec <VOLNAME>", cli_cmd_getspec_cbk,
     "fetch the volume file for the volume <VOLNAME>"},

    {"system:: portmap brick2port <BRICK>", cli_cmd_pmap_b2p_cbk,
     "query which port <BRICK> listens on"},

    {"system:: fsm log [<peer-name>]", cli_cmd_fsm_log_cbk,
     "display fsm transitions"},

    {"system:: getwd", cli_cmd_getwd_cbk, "query glusterd work directory"},

    {"system:: mount <label> <args...>", cli_cmd_mount_cbk, "request a mount"},

    {"system:: umount <path> [lazy]", cli_cmd_umount_cbk, "request an umount"},

    {"system:: uuid get", cli_cmd_uuid_get_cbk, "get uuid of glusterd"},

    {"system:: uuid reset", cli_cmd_uuid_reset_cbk,
     "reset the uuid of glusterd"},

    {"system:: help", cli_cmd_system_help_cbk,
     "display help for system commands"},

    {"system:: copy file [<filename>]", cli_cmd_copy_file_cbk,
     "Copy file from current node's $working_dir to "
     "$working_dir of all cluster nodes"},

    {"system:: execute <command> <args>", cli_cmd_sys_exec_cbk,
     "Execute the command on all the nodes "
     "in the cluster and display their output."},

    {NULL, NULL, NULL}};

int
cli_cmd_sys_exec_cbk(struct cli_state *state, struct cli_cmd_word *word,
                     const char **words, int wordcount)
{
    char cmd_arg_name[PATH_MAX] = "";
    char *command = NULL;
    char *saveptr = NULL;
    char *tmp = NULL;
    int ret = -1;
    int i = -1;
    int len;
    int cmd_args_count = 0;
    int in_cmd_args_count = 0;
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    dict_t *dict = NULL;
    cli_local_t *local = NULL;

    if ((wordcount < 3) || (words[2] == NULL)) {
        cli_usage_out(word->pattern);
        goto out;
    }

    command = strtok_r((char *)words[2], " ", &saveptr);
    if (command == NULL) {
        gf_log("cli", GF_LOG_ERROR, "Failed to parse command");
        goto out;
    }

    dict = dict_new();
    if (!dict)
        goto out;

    do {
        tmp = strtok_r(NULL, " ", &saveptr);
        if (tmp) {
            in_cmd_args_count++;
            snprintf(cmd_arg_name, sizeof(cmd_arg_name), "cmd_arg_%d",
                     in_cmd_args_count);
            ret = dict_set_str(dict, cmd_arg_name, tmp);
            if (ret) {
                gf_log("", GF_LOG_ERROR,
                       "Unable to set "
                       "%s in dict",
                       cmd_arg_name);
                goto out;
            }
        }
    } while (tmp);

    cmd_args_count = wordcount - 3;

    ret = dict_set_str(dict, "command", command);
    if (ret) {
        gf_log("", GF_LOG_ERROR, "Unable to set command in dict");
        goto out;
    }

    for (i = 1; i <= cmd_args_count; i++) {
        in_cmd_args_count++;
        len = snprintf(cmd_arg_name, sizeof(cmd_arg_name), "cmd_arg_%d",
                       in_cmd_args_count);
        ret = dict_set_strn(dict, cmd_arg_name, len, (char *)words[2 + i]);
        if (ret) {
            gf_log("", GF_LOG_ERROR, "Unable to set %s in dict", cmd_arg_name);
            goto out;
        }
    }

    ret = dict_set_int32(dict, "cmd_args_count", in_cmd_args_count);
    if (ret) {
        gf_log("", GF_LOG_ERROR, "Unable to set cmd_args_count in dict");
        goto out;
    }

    ret = dict_set_str(dict, "volname", "N/A");
    if (ret) {
        gf_log("", GF_LOG_ERROR, "Unable to set volname in dict");
        goto out;
    }

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_SYS_EXEC];
    if (proc->fn) {
        frame = create_frame(THIS, THIS->ctx->pool);
        if (!frame) {
            gf_log(THIS->name, GF_LOG_ERROR, "failed to create frame");
            ret = -1;
            goto out;
        }
        CLI_LOCAL_INIT(local, words, frame, dict);
        ret = proc->fn(frame, THIS, (void *)dict);

        /* proc->fn is processed synchronously, which means that the
         * execution flow won't return here until the operation is
         * fully processed, including any related callback. For this
         * reason, it's safe to destroy the stack here, since no one
         * can still be using it. Additionally, it's not easy to move
         * the stack destroy to the callback executed after completion
         * of the operation because there are multiple things than can
         * fail even before having queued the callback, so we would
         * still need to destroy the stack if proc->fn returns an
         * error. */
        CLI_STACK_DESTROY(frame);
    }
out:
    if (dict)
        dict_unref(dict);

    return ret;
}

int
cli_cmd_copy_file_cbk(struct cli_state *state, struct cli_cmd_word *word,
                      const char **words, int wordcount)
{
    int ret = -1;
    rpc_clnt_procedure_t *proc = NULL;
    call_frame_t *frame = NULL;
    char *filename = "";
    dict_t *dict = NULL;
    cli_local_t *local = NULL;

    if (wordcount != 4) {
        cli_usage_out(word->pattern);
        goto out;
    }

    dict = dict_new();
    if (!dict)
        goto out;

    filename = (char *)words[3];
    ret = dict_set_str(dict, "source", filename);
    if (ret)
        gf_log("", GF_LOG_ERROR, "Unable to set filename in dict");

    ret = dict_set_str(dict, "volname", "N/A");
    if (ret)
        gf_log("", GF_LOG_ERROR, "Unable to set volname in dict");

    proc = &cli_rpc_prog->proctable[GLUSTER_CLI_COPY_FILE];
    if (proc && proc->fn) {
        frame = create_frame(THIS, THIS->ctx->pool);
        if (!frame) {
            gf_log(THIS->name, GF_LOG_ERROR, "failed to create frame");
            ret = -1;
            goto out;
        }
        CLI_LOCAL_INIT(local, words, frame, dict);
        ret = proc->fn(frame, THIS, (void *)dict);
    }
out:
    return ret;
}

int
cli_cmd_system_help_cbk(struct cli_state *state, struct cli_cmd_word *in_word,
                        const char **words, int wordcount)
{
    struct cli_cmd *cmd = NULL;
    struct cli_cmd *system_cmd = NULL;
    int count = 0;

    cmd = GF_MALLOC(sizeof(cli_system_cmds), cli_mt_cli_cmd);
    memcpy(cmd, cli_system_cmds, sizeof(cli_system_cmds));
    count = (sizeof(cli_system_cmds) / sizeof(struct cli_cmd));
    cli_cmd_sort(cmd, count);

    for (system_cmd = cmd; system_cmd->pattern; system_cmd++)
        cli_out("%s - %s", system_cmd->pattern, system_cmd->desc);

    GF_FREE(cmd);
    return 0;
}

int
cli_cmd_system_register(struct cli_state *state)
{
    int ret = 0;
    struct cli_cmd *cmd = NULL;

    for (cmd = cli_system_cmds; cmd->pattern; cmd++) {
        ret = cli_cmd_register(&state->tree, cmd);
        if (ret)
            goto out;
    }
out:
    return ret;
}
