/*
 * Copyright (c) 2006-2024, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-01-12     Shell        separate argv, envp, aux processing to lwp_args.c
 *                             Bugs fix for script arguments processing.
 *                             support args larger than 4k
 */
#include "lwp_args.h"
#include "lwp_internal.h"
#include "mm_page.h"

static void _strvec_init(struct lwp_string_vector *sv)
{
    #define DEFAUTL_ARGV_BUFLEN 4
    sv->strvec = rt_malloc(DEFAUTL_ARGV_BUFLEN * sizeof(char *));
    sv->strvec_buflen = DEFAUTL_ARGV_BUFLEN;
    sv->string_count = 0;
}

static void _strvec_detach(struct lwp_string_vector *sv)
{
    if (sv->strvec)
    {
        rt_free(sv->strvec);
    }
}

static rt_err_t _strvec_append(struct lwp_string_vector *sv, const char *string)
{
    if (sv->string_count == sv->strvec_buflen)
    {
        void *newptr;
        newptr = rt_realloc(sv->strvec, sv->strvec_buflen * 2 * sizeof(char *));
        if (!newptr)
            return -RT_ENOMEM;
        sv->strvec = newptr;
        sv->strvec_buflen *= 2;
    }

    sv->strvec[sv->string_count++] = string;
    return RT_EOK;
}

static rt_err_t args_append(struct lwp_args_info *ai, const char *str_addr,
                            size_t str_len, enum lwp_args_type atype)
{
    rt_err_t error;
    char *str_bufaddr;

    if (ai->strings_length + str_len + 1 > ai->str_buf_size)
    {
        /* reallocate buffer for this */
        void *newptr;
        newptr = rt_realloc(ai->str_buf, ai->str_buf_size * 2);
        if (!newptr)
            return -RT_ENOMEM;
        ai->str_buf = newptr;
        ai->str_buf_size *= 2;
    }

    /* append new string to string buffer and update strings_length */
    str_bufaddr = &ai->str_buf[ai->strings_length];
    if (atype == LWP_ARGS_TYPE_KARG || atype == LWP_ARGS_TYPE_KENVP)
    {
        strcpy(str_bufaddr, str_addr);
        ai->strings_length += str_len + 1;
    }
    else
    {
        lwp_get_from_user(str_bufaddr, (void *)str_addr, str_len);
        ai->strings_length += str_len;
        ai->str_buf[ai->strings_length++] = '\0';
    }

    /* append new argument or environment */
    switch (atype)
    {
        case LWP_ARGS_TYPE_ARG:
        case LWP_ARGS_TYPE_KARG:
            error = _strvec_append(&ai->argv, str_bufaddr);
            if (!error && ai->argv.string_count == 1)
            {
                ai->argv0_strlen = str_len;
            }
            break;
        case LWP_ARGS_TYPE_ENVP:
        case LWP_ARGS_TYPE_KENVP:
            error = _strvec_append(&ai->envp, str_bufaddr);
            break;
        default:
            break;
    }
    return error;
}


/**
 * @brief Override arguments 0 for script interpreter.
 *
 * Manual: interpreter will be invoked with the following arguments:
 *      {interpreter [optional-arg] pathname arg...}
 * where pathname is the pathname of the file specified as the first
 * argument of execve(), and arg...  is the series of words pointed
 * to by the argv argument of execve(), starting at argv[1].  Note
 * that there is no way to get the argv[0] that was passed to the
 * execve() call.
 */
static rt_err_t _args_override_argv0(struct lwp_args_info *ai, struct lwp_args_info *ow_ai)
{
    rt_err_t error = 0;
    int i, new_argc, new_strbuf_size, ai_bytes_tobe_copied;
    char **new_argv, *new_strbuf, *base;
    rt_base_t off;

    if (ow_ai == 0 || ow_ai->argv.string_count == 0)
    {
        return -RT_EINVAL;
    }

    /* for new argument vector */
    new_argc = ai->argv.string_count - 1 + ow_ai->argv.string_count;
    new_argv = rt_malloc(new_argc * sizeof(char *));
    if (!new_argv)
    {
        return -RT_ENOMEM;
    }

    /* for new string buffer */
    ai_bytes_tobe_copied = ai->strings_length - (ai->argv0_strlen + 1);
    new_strbuf_size = ai_bytes_tobe_copied + ow_ai->strings_length;
    new_strbuf = rt_malloc(new_strbuf_size);
    if (!new_argv)
    {
        rt_free(new_argv);
        return -RT_ENOMEM;
    }

    base = new_strbuf;
    off = base - ow_ai->str_buf;
    /* copy overriding argument strings and argv */
    memcpy(base, ow_ai->str_buf, ow_ai->strings_length);
    for (i = 0; i < ow_ai->argv.string_count; i++)
    {
        /* base + ow_ai->argv.strvec[i] - ow_ai->str_buf */
        new_argv[i] = (char *)ow_ai->argv.strvec[i] + off;
    }

    base += ow_ai->strings_length;
    off = base - (ai->str_buf + ai->argv0_strlen + 1);
    /* copy old argument strings starting from argv[1] and setup new_argv */
    memcpy(base, ai->str_buf + ai->argv0_strlen + 1, ai_bytes_tobe_copied);
    for (size_t j = 1; j < ai->argv.string_count; i++, j++)
    {
        /* base + ai->argv->strvec[j] - ai->str_buf */
        new_argv[i] = (char *)ai->argv.strvec[j] + off;
    }

    /* setup envp for ai */
    for (i = 0; i < ai->envp.string_count; i++)
    {
        /* base + ai->envp->strvec[i] - ai->str_buf */
        ai->envp.strvec[i] += off;
    }

    /* replace strings buffer and argv buffer */
    ai->str_buf = new_strbuf;
    ai->strings_length = new_strbuf_size;
    ai->str_buf_size = new_strbuf_size;
    ai->argv.string_count = new_argc;
    ai->argv.strvec = (void *)new_argv;
    ai->argv.strvec_buflen = new_argc;

    ai->argv0_strlen = ow_ai->argv0_strlen;
    return error;
}

const char *lwp_args_get_argv_0(struct lwp_args_info *ai)
{
    return ai->str_buf;
}

static rt_err_t args_init(struct lwp_args_info *ai, size_t str_buf_size)
{
    void *str_buf;
    str_buf = rt_malloc(str_buf_size);
    if (!str_buf)
        return -RT_ENOMEM;

    memset(ai, 0, sizeof(*ai));

    _strvec_init(&ai->argv);
    if (!ai->argv.strvec)
    {
        rt_free(str_buf);
        return -RT_ENOMEM;
    }
    _strvec_init(&ai->envp);
    if (!ai->envp.strvec)
    {
        rt_free(str_buf);
        _strvec_detach(&ai->argv);
        return -RT_ENOMEM;
    }

    ai->str_buf_size = str_buf_size;
    ai->str_buf = str_buf;
    return RT_EOK;
}

#define STR_BUF_DEFAULT_SIZE 2048
rt_err_t lwp_args_init(struct lwp_args_info *ai)
{
    return args_init(ai, STR_BUF_DEFAULT_SIZE);
}

void lwp_args_detach(struct lwp_args_info *ai)
{
    _strvec_detach(&ai->argv);
    _strvec_detach(&ai->envp);
    rt_free(ai->str_buf);
}

#ifdef ARCH_MM_MMU
struct process_aux *lwp_argscopy(struct rt_lwp *lwp, struct lwp_args_info *ai)
{
    int size = sizeof(rt_base_t) * 4; /* store argc, argv_NULL, envp_NULL, aux_NULL */
    char *str_ua;
    const char **args_ua;
    const char **iter;
    rt_base_t off;
    struct process_aux_item pa_item;
    struct process_aux *aux_ua;
    size_t prot = PROT_READ | PROT_WRITE;
    size_t flags = MAP_FIXED | MAP_PRIVATE;
    rt_base_t argc = ai->argv.string_count;
    rt_base_t envc = ai->envp.string_count;

    /**
     * counts the bytes to storage the args
     */
    size += argc * sizeof(char *) + envc * sizeof(char *)
        + ai->strings_length + sizeof(struct process_aux);

    args_ua = lwp_mmap2(lwp, (void *)(USER_STACK_VEND), size, prot, flags, -1, 0);
    if (args_ua == RT_NULL)
    {
        return RT_NULL;
    }

    /**
     * @brief Put data from args info to user space
     * argc, argv[], NULL, envp[], NULL, aux[], NULL, strings
     */
    iter = args_ua;

    /* argc */
    lwp_data_put(lwp, iter++, &argc, sizeof(char *));

    /* strings */
    str_ua = (char *)((rt_ubase_t)args_ua +
        (1 + argc + 1 + envc + 1 + AUX_ARRAY_ITEMS_NR * 2 + 1) * sizeof(rt_base_t));
    lwp_data_put(lwp, str_ua, ai->str_buf, ai->strings_length);

    /* argv */
    off = str_ua - ai->str_buf;
    for (size_t i = 0; i < argc; i++)
    {
        /* str_ua + ai->argv.strvec[i] - ai->str_buf */
        ai->argv.strvec[i] += off;
    }
    lwp_data_put(lwp, iter, ai->argv.strvec, sizeof(char *) * ai->argv.string_count);
    iter += ai->argv.string_count;

    /* NULL */
    lwp_data_set(lwp, iter++, 0, sizeof(char *));

    /* envp */
    for (size_t i = 0; i < envc; i++)
    {
        /* str_ua + ai->envp.strvec[i] - ai->str_buf */
        ai->envp.strvec[i] += off;
    }
    lwp_data_put(lwp, iter, ai->envp.strvec, sizeof(char *) * ai->envp.string_count);
    iter += ai->envp.string_count;

    /* NULL */
    lwp_data_set(lwp, iter++, 0, sizeof(char *));

    /* aux */
    aux_ua = (struct process_aux *)iter;
    pa_item.key = AT_EXECFN;
    pa_item.value = (size_t)str_ua;
    lwp_data_put(lwp, iter, &pa_item, sizeof(pa_item));
    iter += AUX_ARRAY_ITEMS_NR * 2;

    /* NULL */
    lwp_data_set(lwp, iter++, 0, sizeof(char *));

    lwp->args = args_ua;

    return aux_ua;
}
#else
static struct process_aux *lwp_argscopy(struct rt_lwp *lwp, int argc, char **argv, char **envp)
{
#ifdef ARCH_MM_MMU
    int size = sizeof(int) * 5; /* store argc, argv, envp, aux, NULL */
    struct process_aux *aux;
#else
    int size = sizeof(int) * 4; /* store argc, argv, envp, NULL */
#endif /* ARCH_MM_MMU */
    int *args;
    char *str;
    char **new_argve;
    int i;
    int len;

    for (i = 0; i < argc; i++)
    {
        size += (rt_strlen(argv[i]) + 1);
    }
    size += (sizeof(int) * argc);

    i = 0;
    if (envp)
    {
        while (envp[i] != 0)
        {
            size += (rt_strlen(envp[i]) + 1);
            size += sizeof(int);
            i++;
        }
    }

#ifdef ARCH_MM_MMU
    /* for aux */
    size += sizeof(struct process_aux);

    args = (int *)rt_malloc(size);
    if (args == RT_NULL)
    {
        return RT_NULL;
    }

    /* argc, argv[], 0, envp[], 0 */
    str = (char *)((size_t)args + (argc + 2 + i + 1 + AUX_ARRAY_ITEMS_NR * 2 + 1) * sizeof(int));
#else
    args = (int *)rt_malloc(size);
    if (args == RT_NULL)
    {
        return RT_NULL;
    }
    str = (char*)((int)args + (argc + 2 + i + 1) * sizeof(int));
#endif /* ARCH_MM_MMU */

    new_argve = (char **)&args[1];
    args[0] = argc;

    for (i = 0; i < argc; i++)
    {
        len = rt_strlen(argv[i]) + 1;
        new_argve[i] = str;
        lwp_memcpy(str, argv[i], len);
        str += len;
    }
    new_argve[i] = 0;
    i++;

    new_argve[i] = 0;
    if (envp)
    {
        int j;
        for (j = 0; envp[j] != 0; j++)
        {
            len = rt_strlen(envp[j]) + 1;
            new_argve[i] = str;
            lwp_memcpy(str, envp[j], len);
            str += len;
            i++;
        }
        new_argve[i] = 0;
    }
#ifdef ARCH_MM_MMU
    /* aux */
    aux = (struct process_aux *)(new_argve + i);
    aux->item[0].key = AT_EXECFN;
    aux->item[0].value = (uint32_t)(size_t)new_argve[0];
    i += AUX_ARRAY_ITEMS_NR * 2;
    new_argve[i] = 0;

    lwp->args = args;

    return aux;
#else
    lwp->args = args;
    lwp->args_length = size;

    return (struct process_aux *)(new_argve + i);
#endif /* ARCH_MM_MMU */
}
#endif

rt_err_t lwp_args_put(struct lwp_args_info *args, const char **strv_addr, enum lwp_args_type atype)
{
    rt_err_t error;
    int iter = 0;
    int len;
    const char *arg_ptr;

    while (1)
    {
        if (atype == LWP_ARGS_TYPE_ARG || atype == LWP_ARGS_TYPE_ENVP)
        {
            len = lwp_get_from_user(&arg_ptr, strv_addr + iter++, sizeof(char *));
            if (len != sizeof(char *))
            {
                return -EFAULT;
            }
            if (arg_ptr == NULL)
            {
                break;
            }
            len = lwp_user_strlen(arg_ptr);
            if (len < 0)
            {
                return -EFAULT;
            }
        }
        else
        {
            arg_ptr = strv_addr[iter++];
            if (arg_ptr == NULL)
            {
                break;
            }
            len = strlen(arg_ptr);
        }

        error = args_append(args, arg_ptr, len, atype);
        if (error)
        {
            return error;
        }
    }
    return 0;
}

/**
 * @brief Put argument vector to args object
 */
rt_err_t lwp_args_put_argv(struct lwp_args_info *args, const char **argv_uaddr)
{
    return lwp_args_put(args, argv_uaddr, LWP_ARGS_TYPE_ARG);
}

/**
 * @brief Put argument vector to args object
 */
rt_err_t lwp_args_put_envp(struct lwp_args_info *args, const char **envp_uaddr)
{
    return lwp_args_put(args, envp_uaddr, LWP_ARGS_TYPE_ENVP);
}

/**
 * read words until reach nextline or EOF.
 * words copied into buffer is never truncated.
 */
#define READFILE_STAT_EOF_REACHED           0
#define READFILE_STAT_NEXTLINE_REACHED      0
#define READFILE_STAT_TRUNCATED             1
#define READFILE_STAT_CAN_READMORE(stat)    (stat)
static int _readfile(int fd, size_t maxbytes, char *buffer, int *p_readlen)
{
    int readlen;
    int stat;
    char *nlp;

    readlen = read(fd, buffer, maxbytes - 1);
    if (readlen <= 0)
    {
        /* eof, failed */
        stat = READFILE_STAT_EOF_REACHED;
        buffer[0] = '\0';
    }
    else
    {
        if ((nlp = strchr(buffer, '\n')) == NULL)
        {
            if (readlen == maxbytes - 1)
            {
                int tailing_wordlen = 0;
                char *cp = buffer + readlen - 1;
                for (; *cp && *cp != ' ' && *cp != '\t'; cp--, tailing_wordlen++)
                    ;
                if (tailing_wordlen)
                {
                    lseek(fd, -tailing_wordlen, SEEK_CUR);
                    readlen -= tailing_wordlen;
                    stat = READFILE_STAT_TRUNCATED;
                }
                else
                {
                    stat = READFILE_STAT_EOF_REACHED;
                }
            }
            else
            {
                stat = READFILE_STAT_EOF_REACHED;
            }
        }
        else
        {
            stat = READFILE_STAT_NEXTLINE_REACHED;
            readlen = nlp - buffer;
        }
        buffer[readlen] = '\0';
    }

    if (p_readlen)
        *p_readlen = readlen;
    return stat;
}

static char *_find_word(char *cp)
{
    for (; (*cp == ' ') || (*cp == '\t'); cp++)
        ;
    return cp;
}

static char *_seperate_and_get_nextword(char *cp)
{
    /* find next whitespace */
    for (; *cp && (*cp != ' ') && (*cp != '\t'); cp++)
        ;
    /* seperate words */
    while ((*cp == ' ') || (*cp == '\t'))
    {
        *cp++ = '\0';
    }
    return cp;
}

#define INTERP_BUF_SIZE 128
rt_err_t lwp_args_load_script(struct lwp_args_info *ai, const char *filename)
{
    rt_err_t error = -1;
    int fd = -RT_ERROR;
    int len;
    int rf_stat;
    char interp[INTERP_BUF_SIZE];
    char *cp, *nextword;
    char script_magic[2];
    struct lwp_args_info ow_ai = {0};

    fd = open(filename, O_BINARY | O_RDONLY, 0);
    if (fd < 0)
    {
        goto quit;
    }

    /**
     * verify an interpreter script by matching script file magic
     * eg: #!/bin/sh
     */
    len = read(fd, script_magic, sizeof(script_magic));
    if (len != 2 || memcmp(script_magic, "#!", sizeof(script_magic)))
    {
        goto quit;
    }

    /* setup a new args struct to save script arguments */
    if (args_init(&ow_ai, INTERP_BUF_SIZE))
    {
        goto quit;
    }

    while (1)
    {
        /* read file to buffer (avoid any truncated words in buffer) */
        rf_stat = _readfile(fd, INTERP_BUF_SIZE, interp, &len);
        if (len <= 0)
        {
            goto quit;
        }

        /* find first word until reaching nil */
        cp = _find_word(interp);
        if (*cp == '\0')
        {
            if (READFILE_STAT_CAN_READMORE(rf_stat))
                continue;
            else
                break;
        }

        do
        {
            nextword = _seperate_and_get_nextword(cp);
            args_append(&ow_ai, cp, strlen(cp), LWP_ARGS_TYPE_KARG);
            cp = nextword;
        }
        while (*cp);

        if (READFILE_STAT_CAN_READMORE(rf_stat))
            continue;
        else
            break;
    }

    if (ow_ai.argv.string_count == 0)
    {
        goto quit; /* No interpreter name found */
    }

    args_append(&ow_ai, filename, strlen(filename), LWP_ARGS_TYPE_KARG);
    error = _args_override_argv0(ai, &ow_ai);
    if (error)
    {
        goto quit;
    }

quit:
    lwp_args_detach(&ow_ai);
    if (fd >= 0)
    {
        close(fd);
    }
    return error;
}

char** lwp_get_command_line_args(struct rt_lwp *lwp)
{
    size_t argc = 0;
    char** argv = NULL;
    int ret;
    size_t i;
    size_t len;

    if (lwp)
    {
        ret = lwp_data_get(lwp, &argc, lwp->args, sizeof(argc));
        if (ret == 0)
        {
            return RT_NULL;
        }
        argv = (char**)rt_malloc((argc + 1) * sizeof(char*));

        if (argv)
        {
            for (i = 0; i < argc; i++)
            {
                char *argvp = NULL;
                ret = lwp_data_get(lwp, &argvp, &((char **)lwp->args)[1 + i], sizeof(argvp));
                if (ret == 0)
                {
                    lwp_free_command_line_args(argv);
                    return RT_NULL;
                }
                len = lwp_user_strlen_ext(lwp, argvp);

                if (len > 0)
                {
                    argv[i] = (char*)rt_malloc(len + 1);
                    ret = lwp_data_get(lwp, argv[i], argvp, len);
                    if (ret == 0)
                    {
                        lwp_free_command_line_args(argv);
                        return RT_NULL;
                    }
                    argv[i][len] = '\0';
                }
                else
                {
                    argv[i] = NULL;
                }
            }
            argv[argc] = NULL;
        }
    }

    return argv;
}

void lwp_print_envp(struct rt_lwp *lwp)
{
    rt_size_t envp_counts;
    char **kenvp_array = lwp_get_envp(lwp, &envp_counts);
    if (kenvp_array)
    {
        rt_kprintf("envp_counts: %d\n", envp_counts);
        for (size_t i = 0; i < envp_counts; i++)
        {
            rt_kprintf("envp[%d]: %s\n", i, kenvp_array[i]);
        }
    }
    lwp_free_command_line_args(kenvp_array);
    return ;
}

char** lwp_get_envp(struct rt_lwp *lwp, rt_size_t *penvp_counts)
{
    int ret, len;
    rt_base_t argc;
    char **p_kenvp = RT_NULL;
    char *envp, **p_envp;
    size_t envp_counts = 0;

    if (lwp)
    {
        ret = lwp_data_get(lwp, &argc, lwp->args, sizeof(argc));
        if (ret == 0)
        {
            return RT_NULL;
        }
        p_envp = (char **)lwp->args + 1 + argc + 1;

        /* counts envp */
        while (lwp_data_get(lwp, &envp, p_envp, sizeof(void *)) == sizeof(void *)
               && envp != NULL)
        {
            p_envp++;
            envp_counts++;
        }

        p_kenvp = (char **)rt_malloc((envp_counts + 1) * sizeof(char *));
        if (p_kenvp)
        {
            /* copy env from envp array */
            p_envp =  (char **)lwp->args + 1 + argc + 1;
            for (size_t i = 0; i < envp_counts; i++)
            {
                ret = lwp_data_get(lwp, &envp, &p_envp[i], sizeof(char *));
                if (ret != sizeof(char **))
                {
                    lwp_free_command_line_args(p_kenvp);
                    return RT_NULL;
                }

                len = lwp_user_strlen_ext(lwp, envp);
                if (len > 0)
                {
                    p_kenvp[i] = (char*)rt_malloc(len + 1);
                    ret = lwp_data_get(lwp, p_kenvp[i], envp, len + 1);
                    if (ret != len + 1)
                    {
                        lwp_free_command_line_args(p_kenvp);
                        return RT_NULL;
                    }
                }
                else
                {
                    p_kenvp[i] = NULL;
                }
            }
            if (penvp_counts)
                *penvp_counts = envp_counts;
            p_kenvp[envp_counts] = NULL;
        }
    }

    return p_kenvp;
}

void lwp_free_command_line_args(char** argv)
{
    size_t i;

    if (argv)
    {
        for (i = 0; argv[i]; i++)
        {
            rt_free(argv[i]);
        }
        rt_free(argv);
    }
}
