This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 7c4ad4b4ee38eb0e08da0e9e96bee7683f96eb1e Author: Marco Casaroli <[email protected]> AuthorDate: Mon Oct 12 05:31:41 2026 +0200 binfmt/script: Give the interpreter the script path and its #! argument. The interpreter of a script got the script's own argv as its arguments, with the file name as argv[0]. The script path was only the caller's argv[0], which is often not a path: a script found through PATH ran as "sh name" and the shell did not find the file. The argument after the interpreter on the "#!" line was dropped, so with "#!/usr/bin/env sh" env got the caller's argv[0], the script itself, as the program to run. In a kernel build the task name was read from the caller's memory after the switch to the new address environment, and the kernel faulted. Let a loader give, in struct binary_s, an argument list that takes the place of the caller's argv[0]. The script handler puts there the interpreter, the optional argument and the full script path. binfmt_copyargv() builds the list that Linux uses, interp [arg] script argv[1] ...: in a kernel build in the copy that it already makes, in other builds in a new list of pointers, only when a loader gave one. exec_module() frees the loader's list when the task has its arguments, and unload_module() when the program does not start. For a script whose interpreter is a script, the entries of the inner script come first. The task takes its name from argv[0], the interpreter, as for any other program. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <[email protected]> --- binfmt/binfmt.h | 28 ++--- binfmt/binfmt_copyargv.c | 247 +++++++++++++++++++++++++++++++++--------- binfmt/binfmt_execmodule.c | 19 ++-- binfmt/binfmt_unloadmodule.c | 5 + binfmt/script.c | 132 +++++++++++++++++++--- include/nuttx/binfmt/binfmt.h | 9 +- 6 files changed, 344 insertions(+), 96 deletions(-) diff --git a/binfmt/binfmt.h b/binfmt/binfmt.h index 84000211c44..c8103990503 100644 --- a/binfmt/binfmt.h +++ b/binfmt/binfmt.h @@ -84,44 +84,44 @@ int binfmt_dumpmodule(FAR const struct binary_s *bin); * Name: binfmt_copyargv * * Description: + * Get the argument list of the new program. If ldargv is not NULL, it + * takes the place of argv[0]: a loader gives the program to run and its + * first arguments (for example, the interpreter of a script). + * * In the kernel build, the argv list will likely lie in the caller's * address environment and, hence, be inaccessible when we switch to the * address environment of the new process address environment. So we * do not have any real option other than to copy the callers argv[] list. * * Input Parameters: + * copy - The argument list to use + * ldargv - The argument list of the loader, or NULL * argv - Argument list * * Returned Value: - * A non-zero copy is returned on success. + * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ -#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_BUILD_KERNEL) -int binfmt_copyargv(FAR char * const **copy, FAR char * const *argv); -#else -# define binfmt_copyargv(copy, argv) (*(copy) = (argv), 0) -#endif +int binfmt_copyargv(FAR char * const **copy, FAR char * const *ldargv, + FAR char * const *argv); /**************************************************************************** * Name: binfmt_freeargv * * Description: - * Release the copied argv[] list. + * Release the list that binfmt_copyargv() returned, if it allocated it. * * Input Parameters: * argv - Argument list + * ldargv - The argument list of the loader that binfmt_copyargv() got * * Returned Value: * None * ****************************************************************************/ -#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_BUILD_KERNEL) -void binfmt_freeargv(FAR char * const *argv); -#else -# define binfmt_freeargv(argv) -#endif +void binfmt_freeargv(FAR char * const *argv, FAR char * const *ldargv); /**************************************************************************** * Name: binfmt_copyenv @@ -142,7 +142,7 @@ void binfmt_freeargv(FAR char * const *argv); ****************************************************************************/ #ifndef CONFIG_DISABLE_ENVIRON -# define binfmt_copyenv(copy, envp) binfmt_copyargv(copy, envp) +# define binfmt_copyenv(copy, envp) binfmt_copyargv(copy, NULL, envp) #else # define binfmt_copyenv(copy, envp) (*(copy) = (envp), 0) #endif @@ -162,7 +162,7 @@ void binfmt_freeargv(FAR char * const *argv); ****************************************************************************/ #ifndef CONFIG_DISABLE_ENVIRON -# define binfmt_freeenv(envp) binfmt_freeargv(envp) +# define binfmt_freeenv(envp) binfmt_freeargv(envp, NULL) #else # define binfmt_freeenv(envp) #endif diff --git a/binfmt/binfmt_copyargv.c b/binfmt/binfmt_copyargv.c index d59aa62e3bf..018fcc63d6b 100644 --- a/binfmt/binfmt_copyargv.c +++ b/binfmt/binfmt_copyargv.c @@ -35,12 +35,75 @@ #include "binfmt.h" -#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_BUILD_KERNEL) && !defined(CONFIG_BINFMT_DISABLE) +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: binfmt_nargs + * + * Description: + * Count the arguments of the list that binfmt_copyargv() builds: all of + * ldargv, then argv without argv[0] if ldargv is not NULL, or all of argv + * if it is. + * + ****************************************************************************/ + +static int binfmt_nargs(FAR char * const *ldargv, FAR char * const *argv, + FAR int *nld) +{ + int nargs = 0; + int i; + + *nld = 0; + if (ldargv != NULL) + { + while (ldargv[*nld] != NULL) + { + (*nld)++; + } + } + + if (argv != NULL) + { + for (i = ldargv != NULL && argv[0] != NULL; argv[i] != NULL; i++) + { + if (++nargs > CONFIG_TASK_MAX_ARGS) + { + berr("ERROR: Too many arguments\n"); + return -E2BIG; + } + } + } + + return *nld + nargs; +} + +/**************************************************************************** + * Name: binfmt_arg + * + * Description: + * Return argument i of the list that binfmt_copyargv() builds. + * + ****************************************************************************/ + +static FAR char *binfmt_arg(FAR char * const *ldargv, + FAR char * const *argv, int nld, int i) +{ + if (i < nld) + { + return ldargv[i]; + } + + return argv[i - nld + (ldargv != NULL)]; +} /**************************************************************************** * Public Functions ****************************************************************************/ +#if defined(CONFIG_ARCH_ADDRENV) && defined(CONFIG_BUILD_KERNEL) + /**************************************************************************** * Name: binfmt_copyargv * @@ -50,15 +113,22 @@ * address environment of the new process address environment. So we * do not have any real option other than to copy the callers argv[] list. * + * If ldargv is not NULL, it takes the place of argv[0]: a loader gives + * the program to run and its first arguments (for example, the + * interpreter of a script). + * * Input Parameters: + * copy - The copy of the argument list + * ldargv - The argument list of the loader, or NULL * argv - Argument list * * Returned Value: - * A non-zero copy is returned on success. + * Zero (OK) on success; a negated errno value on failure. * ****************************************************************************/ -int binfmt_copyargv(FAR char * const **copy, FAR char * const *argv) +int binfmt_copyargv(FAR char * const **copy, FAR char * const *ldargv, + FAR char * const *argv) { FAR char **argvbuf = NULL; FAR const char *arg; @@ -67,73 +137,60 @@ int binfmt_copyargv(FAR char * const **copy, FAR char * const *argv) size_t argvsize; size_t argsize = 0; size_t len; - int nargs = 0; + int nargs; + int nld; int i; /* Get the number of arguments and the size of the argument list */ - if (argv) + nargs = binfmt_nargs(ldargv, argv, &nld); + if (nargs < 0) { - for (i = 0; (arg = argv[i]) != NULL; i++) - { - /* Increment the size of the allocation with the size of the next - * string - */ + return nargs; + } - argsize += strlen(arg) + 1; - nargs++; + for (i = 0; i < nargs; i++) + { + argsize += strlen(binfmt_arg(ldargv, argv, nld, i)) + 1; + } - /* This is a sanity check to prevent running away with an - * unterminated argv[] list. - * CONFIG_TASK_MAX_ARGS should be sufficiently large that this - * never happens in normal usage. - */ + binfo("args=%d argsize=%zu\n", nargs, argsize); - if (nargs > CONFIG_TASK_MAX_ARGS) - { - berr("ERROR: Too many arguments: %zu\n", argsize); - return -E2BIG; - } - } + /* Allocate the argv array and an argument buffer */ - binfo("args=%d argsize=%zu\n", nargs, argsize); + if (argsize > 0) + { + argvsize = (nargs + 1) * sizeof(FAR char *); + ptr = kmm_malloc(argvsize + argsize); + if (!ptr) + { + berr("ERROR: Failed to allocate the argument buffer\n"); + return -ENOMEM; + } - /* Allocate the argv array and an argument buffer */ + /* Copy the argv list */ - if (argsize > 0) + argvbuf = (FAR char **)ptr; + ptr += argvsize; + end = ptr + argsize; + for (i = 0; i < nargs; i++) { - argvsize = (nargs + 1) * sizeof(FAR char *); - ptr = kmm_malloc(argvsize + argsize); - if (!ptr) + arg = binfmt_arg(ldargv, argv, nld, i); + if (ptr == end || arg == NULL) { - berr("ERROR: Failed to allocate the argument buffer\n"); - return -ENOMEM; + kmm_free(argvbuf); + return -EFAULT; } - /* Copy the argv list */ - - argvbuf = (FAR char **)ptr; - ptr += argvsize; - end = ptr + argsize; - for (i = 0; i < nargs; i++) - { - arg = argv[i]; - if (ptr == end || arg == NULL) - { - kmm_free(argvbuf); - return -EFAULT; - } - - len = strnlen(arg, end - ptr - 1); - argvbuf[i] = memcpy(ptr, arg, len); - ptr[len] = '\0'; - ptr += len + 1; - } + len = strnlen(arg, end - ptr - 1); + argvbuf[i] = memcpy(ptr, arg, len); + ptr[len] = '\0'; + ptr += len + 1; + } - /* Terminate the argv[] list */ + /* Terminate the argv[] list */ - argvbuf[i] = NULL; - } + argvbuf[i] = NULL; } *copy = argvbuf; @@ -148,16 +205,19 @@ int binfmt_copyargv(FAR char * const **copy, FAR char * const *argv) * * Input Parameters: * argv - Argument list + * ldargv - The argument list of the loader that binfmt_copyargv() got * * Returned Value: * None * ****************************************************************************/ -void binfmt_freeargv(FAR char * const *argv) +void binfmt_freeargv(FAR char * const *argv, FAR char * const *ldargv) { /* Is there an allocated argument buffer */ + UNUSED(ldargv); + if (argv) { /* Free the argument buffer */ @@ -166,4 +226,83 @@ void binfmt_freeargv(FAR char * const *argv) } } +#else + +/**************************************************************************** + * Name: binfmt_copyargv + * + * Description: + * All tasks share one address space, so the caller's argv[] list is used + * as it is. If ldargv is not NULL, it takes the place of argv[0]: the + * new list of pointers is allocated, the strings are not copied. + * + * Input Parameters: + * copy - The argument list to use + * ldargv - The argument list of the loader, or NULL + * argv - Argument list + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int binfmt_copyargv(FAR char * const **copy, FAR char * const *ldargv, + FAR char * const *argv) +{ + FAR char **argvbuf; + int nargs; + int nld; + int i; + + if (ldargv == NULL) + { + *copy = argv; + return OK; + } + + nargs = binfmt_nargs(ldargv, argv, &nld); + if (nargs < 0) + { + return nargs; + } + + argvbuf = kmm_malloc((nargs + 1) * sizeof(FAR char *)); + if (argvbuf == NULL) + { + return -ENOMEM; + } + + for (i = 0; i < nargs; i++) + { + argvbuf[i] = binfmt_arg(ldargv, argv, nld, i); + } + + argvbuf[nargs] = NULL; + *copy = argvbuf; + return OK; +} + +/**************************************************************************** + * Name: binfmt_freeargv + * + * Description: + * Release the list that binfmt_copyargv() allocated, if it did. + * + * Input Parameters: + * argv - Argument list + * ldargv - The argument list of the loader that binfmt_copyargv() got + * + * Returned Value: + * None + * + ****************************************************************************/ + +void binfmt_freeargv(FAR char * const *argv, FAR char * const *ldargv) +{ + if (ldargv != NULL) + { + kmm_free((FAR char **)argv); + } +} + #endif diff --git a/binfmt/binfmt_execmodule.c b/binfmt/binfmt_execmodule.c index 63513eb3928..dc33d9baee1 100644 --- a/binfmt/binfmt_execmodule.c +++ b/binfmt/binfmt_execmodule.c @@ -212,7 +212,7 @@ int exec_module(FAR struct binary_s *binp, return -ENOMEM; } - ret = binfmt_copyargv(&argv, argv); + ret = binfmt_copyargv(&argv, binp->argv, argv); if (ret < 0) { goto errout_with_tcb; @@ -279,13 +279,7 @@ int exec_module(FAR struct binary_s *binp, stackaddr = binp->stackaddr; #endif - /* If the binfmt handler requested argv preservation (e.g. script - * interpreter rewrites), use filename as the task name and forward the - * full argv. Otherwise the conventional path consumes argv[0] as the - * task name and forwards argv[1..] as arguments. - */ - - if (argv && argv[0] && !binp->filename) + if (argv && argv[0]) { ret = nxtask_init(tcb, argv[0], binp->priority, stackaddr, binp->stacksize, binp->entrypt, &argv[1], @@ -307,9 +301,14 @@ int exec_module(FAR struct binary_s *binp, /* The copied argv and envp can now be released */ binfmt_freeactions(actions); - binfmt_freeargv(argv); + binfmt_freeargv(argv, binp->argv); binfmt_freeenv(envp); + /* The task has its arguments: the list of the loader is not needed */ + + kmm_free(binp->argv); + binp->argv = NULL; + #ifdef CONFIG_PIC /* Add the D-Space address as the PIC base address. By convention, this * must be the first allocated address space. @@ -420,7 +419,7 @@ errout_with_actions: errout_with_envp: binfmt_freeenv(envp); errout_with_args: - binfmt_freeargv(argv); + binfmt_freeargv(argv, binp->argv); errout_with_tcb: kmm_free(tcb); return ret; diff --git a/binfmt/binfmt_unloadmodule.c b/binfmt/binfmt_unloadmodule.c index c4710936571..5a26e1b93f0 100644 --- a/binfmt/binfmt_unloadmodule.c +++ b/binfmt/binfmt_unloadmodule.c @@ -86,6 +86,11 @@ int unload_module(FAR struct binary_s *binp) } } + /* Free the argument list of the loader */ + + kmm_free(binp->argv); + binp->argv = NULL; + /* Unmap mapped address spaces */ if (binp->mapped) diff --git a/binfmt/script.c b/binfmt/script.c index dfedcb9d55d..4e14af5fe0d 100644 --- a/binfmt/script.c +++ b/binfmt/script.c @@ -34,6 +34,7 @@ #include <nuttx/binfmt/binfmt.h> #include <nuttx/fs/fs.h> +#include <nuttx/kmalloc.h> #include "binfmt.h" @@ -67,15 +68,85 @@ static struct binfmt_s g_script_binfmt = * Private Functions ****************************************************************************/ +/**************************************************************************** + * Name: script_setargv + * + * Description: + * Set binp->argv to: interp, arg (if not NULL), filename, then the + * entries after the first of the old list (a script whose interpreter is + * a script). All strings are copied into one allocation. + * + ****************************************************************************/ + +static int script_setargv(FAR struct binary_s *binp, FAR const char *interp, + FAR const char *arg, FAR const char *filename) +{ + FAR char **old = binp->argv; + FAR const char *src[3]; + FAR char **list; + FAR char *str; + size_t size = 0; + int nsrc = 0; + int nold = 0; + int nargs; + int i; + + src[nsrc++] = interp; + if (arg != NULL) + { + src[nsrc++] = arg; + } + + src[nsrc++] = filename; + + if (old != NULL) + { + while (old[nold] != NULL) + { + nold++; + } + } + + nargs = nsrc + (nold > 1 ? nold - 1 : 0); + for (i = 0; i < nsrc; i++) + { + size += strlen(src[i]) + 1; + } + + for (i = 1; i < nold; i++) + { + size += strlen(old[i]) + 1; + } + + list = kmm_malloc((nargs + 1) * sizeof(FAR char *) + size); + if (list == NULL) + { + return -ENOMEM; + } + + str = (FAR char *)&list[nargs + 1]; + for (i = 0; i < nargs; i++) + { + FAR const char *s = i < nsrc ? src[i] : old[i - nsrc + 1]; + + list[i] = strcpy(str, s); + str += strlen(s) + 1; + } + + list[nargs] = NULL; + binp->argv = list; + return OK; +} + /**************************************************************************** * Name: script_loadbinary * * Description: * Verify that the file starts with "#!" and parse the shebang line to * extract the interpreter path (and optional argument), then delegate - * loading to the interpreter via load_module(). Sets binp->filename - * so exec_module() launches the interpreter with the script path - * preserved in the forwarded argv. + * loading to the interpreter via load_module(). Sets binp->argv + * so exec_module() runs the interpreter with the optional argument and + * the script path before the caller's arguments. * ****************************************************************************/ @@ -85,7 +156,9 @@ static int script_loadbinary(FAR struct binary_s *binp, int nexports) { char header[SCRIPT_HEADER_MAX]; + FAR char **oldargv = binp->argv; FAR char *interp; + FAR char *arg = NULL; FAR char *end; struct file file; int ret; @@ -137,10 +210,9 @@ static int script_loadbinary(FAR struct binary_s *binp, return -ENOEXEC; } - /* Terminate the interpreter token at the first whitespace. Any - * optional argument after the interpreter is ignored -- the - * interpreter is relaunched with the script path as argv[1], which - * is enough for sh / lua / python to auto-execute the script. + /* The interpreter ends at the first whitespace. The rest of the line, + * without the whitespace around it, is one optional argument, as in + * Linux. */ end = interp; @@ -149,18 +221,50 @@ static int script_loadbinary(FAR struct binary_s *binp, end++; } - *end = '\0'; + if (*end != '\0') + { + *end++ = '\0'; + while (isspace(*end)) + { + end++; + } + + if (*end != '\0') + { + arg = end; + end = arg + strlen(arg); + while (isspace(*(end - 1))) + { + end--; + } + + *end = '\0'; + } + } - binfo("Script: interp='%s' script='%s'\n", interp, filename); + binfo("Script: interp='%s' arg='%s' script='%s'\n", + interp, arg ? arg : "", filename); - /* Ask exec_module() to keep the original filename as the task name and - * forward the unchanged argv, so the interpreter receives the script - * path (the original filename) at argv[1]. + /* The interpreter runs with: interp [arg] filename argv[1] ... + * filename is the path that was loaded, after the PATH search. */ - binp->filename = true; + ret = script_setargv(binp, interp, arg, filename); + if (ret < 0) + { + return ret; + } + + ret = load_module(binp, interp, exports, nexports); + if (ret < 0) + { + kmm_free(binp->argv); + binp->argv = oldargv; + return ret; + } - return load_module(binp, interp, exports, nexports); + kmm_free(oldargv); + return ret; } /**************************************************************************** diff --git a/include/nuttx/binfmt/binfmt.h b/include/nuttx/binfmt/binfmt.h index e3789330f18..669357557a7 100644 --- a/include/nuttx/binfmt/binfmt.h +++ b/include/nuttx/binfmt/binfmt.h @@ -30,7 +30,6 @@ #include <nuttx/config.h> #include <spawn.h> -#include <stdbool.h> #include <sys/types.h> #include <nuttx/sched.h> @@ -112,9 +111,11 @@ struct binary_s FAR void *stackaddr; /* Task stack address */ #endif - bool filename; /* Use filename as task name instead - * of argv[0]; preserve full argv. - */ + FAR char **argv; /* Arguments in place of argv[0], + * from the loader, or NULL: for + * example the interpreter of a + * script, its argument and the + * script. Freed by unload_module() */ /* Unload module callback */
