#include "upgrade.h"
#include <elf.h>

static int check_selfextract_upgrade(const char * pfile,
	char * upcmd, unsigned int uplen)
{
    int fd = -1;
    Elf32_Ehdr ehdr;

    fd = open(pfile, O_RDONLY);
    if (fd == -1)
        return -1;

    memset(&ehdr, 0, sizeof(ehdr));
    if (read(fd, &ehdr, sizeof(ehdr)) != (ssize_t) sizeof(ehdr)) {
        close(fd);
        return -1;
    }
    close(fd); fd = -1;

    /* only ELF files are self-extractable */
    if (ehdr.e_ident[0] != 0x7F || ehdr.e_ident[1] != 'E' ||
        ehdr.e_ident[2] != 'L' || ehdr.e_ident[3] != 'F') {
        return -1;
    }

    if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 && ehdr.e_ident[EI_CLASS] != ELFCLASS64)
        return -1;

    if (ehdr.e_ident[EI_VERSION] != EV_CURRENT ||
        (ehdr.e_ident[EI_OSABI] != ELFOSABI_SYSV &&
        ehdr.e_ident[EI_OSABI] != ELFOSABI_GNU &&
        ehdr.e_ident[EI_OSABI] != ELFOSABI_LINUX &&
        ehdr.e_ident[EI_OSABI] != ELFOSABI_ARM
#ifdef ELFOSABI_ARM_AEABI
		&& ehdr.e_ident[EI_OSABI] != ELFOSABI_ARM_AEABI
#endif
    )) {
        dy_syslog(LOG_ERR, "Error, invalid EI_VERSION: 0x%x or EI_OSABI: 0x%x",
            (unsigned int) ehdr.e_ident[EI_VERSION],
            (unsigned int) ehdr.e_ident[EI_OSABI]);
        return -1;
    }

    if (chmod(pfile, 0755) != 0) {
        dy_syslog(LOG_ERR, "Error, failed to chmod(%s)", pfile);
        return -1;
    }

    if (pfile[0] != '/') {
        snprintf(upcmd, (size_t) uplen, "NOTHUP_OUT=/root/uplog.txt nothup ./%s", pfile);
    } else {
        /* strncpy(upcmd, pfile, (size_t) uplen); */
        snprintf(upcmd, (size_t) uplen, "NOTHUP_OUT=/root/uplog.txt nothup %s", pfile);
    }

    dy_syslog(LOG_ERR, "UPGRADE CMD: %s", upcmd);
    return 0;
}

static int upgrade_execute(char *file, char *mode)
{
    char  exec_cmd[420] = {0};
    if (!file || !mode)
    {
        return -1;
    }
    if (strstr(mode, "ipk"))
    {
        dy_syslog(LOG_ERR, "Not support this interface any more");
        return UPGRADE_GW_NOT_SUPPORT;
    }
    else if (strstr(mode, "firmware"))
    {
        if (check_selfextract_upgrade(file,
            exec_cmd, (unsigned int) sizeof(exec_cmd) - 0x1) < 0) {
            sprintf(exec_cmd, "(cd /tmp;/sbin/sysupgrade %s)", file);
        }
        pid_t status = system(exec_cmd);
        if (WIFEXITED(status))
        {
            if (WEXITSTATUS(status) == 0)
            {
                dy_syslog(LOG_INFO, "upgrade successful: %s", exec_cmd);
                return 0;
            }
            else
            {
                dy_syslog(LOG_ERR, "upgrade fail: %s", file);
                return -1;
            }
        }
    }
    else
    {
        dy_syslog(LOG_ERR, "unknow mode:%s\n", mode);
    }
    dy_syslog(LOG_ERR, "execute fail: %s", file);
    return -1;
}

static error_code_msg_t upgrade_gw_code_tab[] =
{
    {UPGRADE_GW_DOWNLOAD_FAIL, "download failed"},
    {UPGRADE_GW_CHECK_FAIL, "check md5 failed"},
    {UPGRADE_GW_EXTRACT, "extract file failed"},
    {UPGRADE_GW_ALREADY_RUN, "another upgrade already run"},
    {UPGRADE_GW_NOT_SUPPORT, "not support this mode any more"},
    {UPGRADE_GW_INTERNAL_ERR, "upgrade internal error"},
    {UPGRADE_GW_FILENAME_INVALID, "file name invalid"},
};

static void upgrade_gw_json_result(upgrade_var_t *var, int mi, int code)
{
    char topic[192] = {0};
    cJSON *root = cJSON_CreateObject();
    if (root == NULL)
    {
        return;
    }
    cJSON_AddNumberToObject(root, "mi", mi);
    cJSON_AddNumberToObject(root, "timestamp", time(NULL));
    cJSON_AddNumberToObject(root, "error_code", code);
    char *err_msg = NULL;
    int i;
    for (i = 0; i < sizeof(upgrade_gw_code_tab) / sizeof(upgrade_gw_code_tab[0]); i++)
    {
        if ((int)upgrade_gw_code_tab[i].code == code)
        {
            err_msg = upgrade_gw_code_tab[i].msg;
            break;
        }
    }
    cJSON_AddStringToObject(root, "error_msg", err_msg);
    char *str = (cJSON_Print(root));
    cJSON_Delete(root);

    snprintf(topic, sizeof(topic), "G/%s/%s/device/%s/service/%s", var->sn_str, TOPIC_GATEWAY_CONFIG, var->sn_str, TOPIC_RSP_GATEWAYUPGRADECMD);
    ipc_session_publish(var->session, topic, str, strlen(str));
    free(str);
}

static int upgrade_gw_json_parse(upgrade_gw_args_t *arg, const char *str)
{
    if (!str || !arg)
    {
        return -1;
    }
    cJSON *root = cJSON_Parse(str);
    if (!root)
    {
        return -1;
    }
    GET_JSON_VALUE_INT(root, "mi", arg->mi);
    GET_JSON_VALUE_INT(root, "timeout", arg->timeout);
    GET_JSON_VALUE_STRING(root, "URL", arg->URL);
    GET_JSON_VALUE_STRING(root, "md5sum", arg->md5sum);
    GET_JSON_VALUE_STRING(root, "mode", arg->mode);
    cJSON_Delete(root);
    return 0;
}

static int valid_file(char *filename)
{
    char cmd[320] = {0};

    sprintf(cmd, "ls %s", filename);
    pid_t status = system(cmd);
    if (WIFEXITED(status))
    {
        if (WEXITSTATUS(status) == 0)
        {
            dy_syslog(LOG_INFO, "file valid:%s", filename);
            return 1;
        }
        else
        {
            dy_syslog(LOG_INFO, "file invalid:%s", filename);
            return 0;
        }
    }
	return 0;
}

void msg_upgrade_gw(upgrade_var_t *var, ipc_msg_t *mqtt_msg)
{
    int ret;
    char buff[256];
    int err_code = 0;
    char *file = NULL;
    upgrade_gw_args_t upgrade_arg = {0};

    ret = upgrade_gw_json_parse(&upgrade_arg, mqtt_msg->payload);
    if (var->state != UPGRADE_IDLE)
    {
        err_code = UPGRADE_GW_ALREADY_RUN;
        dy_syslog(LOG_ERR, "another upgrade already run");
        goto err;
    }
    var->state = UPGRADING_GW;

    if (strstr(upgrade_arg.URL, "http") || strstr(upgrade_arg.URL, "HTTP") ||
        strncmp(upgrade_arg.URL, "ftp://", 6) == 0)
    {
        file = GetFileName(upgrade_arg.URL);
        sprintf(buff, "/tmp/%s", file);

        ret = download_file("/tmp", upgrade_arg.URL, upgrade_arg.timeout);
        if (ret != 0)
        {
            dy_syslog(LOG_ERR, "download failed");
            err_code = UPGRADE_GW_DOWNLOAD_FAIL;
            goto err;
        }
    }
    else
    {
        strcpy(buff, upgrade_arg.URL);
    }

    if (valid_file(buff) == 0)
    {
        err_code = UPGRADE_GW_FILENAME_INVALID;
        dy_syslog(LOG_ERR, "file name buff:%s invalid", buff);
        goto err;
    }
    ret = check_md5sum_file(buff, upgrade_arg.md5sum);
    if (ret != 0)
    {
        err_code = UPGRADE_GW_CHECK_FAIL;
        dy_syslog(LOG_ERR, "gateway upgrade failed as md5sum wrong");
        goto err;
    }
    upgrade_gw_json_result(var, upgrade_arg.mi, err_code);
    ret = upgrade_execute(buff, upgrade_arg.mode);
    if (ret != 0)
    {
        dy_syslog(LOG_ERR, "gateway upgrade failed");
        err_code = UPGRADE_GW_INTERNAL_ERR;
        goto err;
    }

err:
    if (err_code != 0)
    {
        // 如果失败了则清除upgrading flag，如果都成功，系统或进程会重启，flag不需要清除
        var->state = UPGRADE_IDLE;
        upgrade_gw_json_result(var, upgrade_arg.mi, err_code);
    }
    {
        int i = 0, j = 0;
        char cmd[128] = "rm ";
        char *p = buff;

        j = strlen(cmd);
        for (i = 0; i < strlen(buff); i++)
        {
            if (p[i] == ' ')
            {
                cmd[j++] = '\\';
                cmd[j++] = ' ';
            }
            else if (p[i] == '(')
            {
                cmd[j++] = '\\';
                cmd[j++] = '(';
            }
            else if (p[i] == ')')
            {
                cmd[j++] = '\\';
                cmd[j++] = ')';
            }
            else
            {
                cmd[j++] = p[i];
            }
        }
        cmd[j] = 0;

        dy_syslog(LOG_DEBUG, "run cmd:%s", cmd);

        system(cmd);
    }
}
