rt-thread/components/lwp/lwp_args.c

782 lines
20 KiB
C

/*
* 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);
}
}