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 */
 

Reply via email to