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 c57ba6827cfea5b73f54b8e063b96b4b4e418253
Author: cuiziwei <[email protected]>
AuthorDate: Wed May 6 17:37:17 2026 +0800

    binfmt: add script (#!) binary format handler
    
    Add CONFIG_BINFMT_SCRIPT, enabling posix_spawn / exec on files
    starting with "#!" to auto-launch the interpreter with the script
    path preserved at argv[1].
    
    Two pieces:
    
    1. Generic binfmt mechanism
       struct binary_s gains a bool filename field.  When it is set,
       exec_module() uses the caller's filename as the task name and
       forwards the full argv, instead of consuming argv[0] as the
       task name.
    
    2. Script handler
       script_loadbinary() parses the shebang line, sets
       binp->filename, and delegates to load_module() with
       the interpreter path so the builtin / ELF loader picks it up.
    
    For example, executing /tmp/foo.sh with "#!/bin/sh" produces:
      task argv = { "/tmp/foo.sh", "/tmp/foo.sh", orig_args... }
    which lets sh auto-execute the script from argv[1].
    
    The handler is registered last in the binfmt chain so builtin
    and ELF formats are always tried first.  An optional shebang
    interpreter argument (e.g. "#!/usr/bin/env lua") is not
    supported in this simple flag-based scheme; use a direct
    interpreter path instead.
    
    Signed-off-by: cuiziwei <[email protected]>
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 binfmt/CMakeLists.txt         |   4 +
 binfmt/Kconfig                |  13 +++
 binfmt/Makefile               |   6 ++
 binfmt/binfmt.h               |  13 +++
 binfmt/binfmt_execmodule.c    |   8 +-
 binfmt/binfmt_initialize.c    |   8 ++
 binfmt/script.c               | 202 ++++++++++++++++++++++++++++++++++++++++++
 include/nuttx/binfmt/binfmt.h |   5 ++
 8 files changed, 258 insertions(+), 1 deletion(-)

diff --git a/binfmt/CMakeLists.txt b/binfmt/CMakeLists.txt
index 9a3917c4bb7..8852722f1b0 100644
--- a/binfmt/CMakeLists.txt
+++ b/binfmt/CMakeLists.txt
@@ -67,6 +67,10 @@ if(CONFIG_BUILTIN)
   list(APPEND SRCS builtin.c)
 endif()
 
+if(CONFIG_BINFMT_SCRIPT)
+  list(APPEND SRCS script.c)
+endif()
+
 target_sources(binfmt PRIVATE ${SRCS})
 target_include_directories(binfmt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
 target_include_directories(binfmt PRIVATE ${NUTTX_DIR}/sched)
diff --git a/binfmt/Kconfig b/binfmt/Kconfig
index dd0678af577..69b11466b71 100644
--- a/binfmt/Kconfig
+++ b/binfmt/Kconfig
@@ -46,6 +46,19 @@ if NXFLAT
 source "binfmt/libnxflat/Kconfig"
 endif
 
+config BINFMT_SCRIPT
+       bool "Enable script (#!) binary format"
+       default n
+       ---help---
+               Enable support for executing files that start with a "#!"
+               (shebang) line.  When posix_spawn / exec targets such a file,
+               the shebang interpreter is loaded via the normal binfmt chain
+               and the original script path is forwarded to it at argv[1],
+               so the interpreter (sh, lua, python, ...) auto-executes it.
+
+               The interpreter itself must be loadable by another registered
+               binfmt handler (builtin, ELF, etc.).
+
 config ELF
        bool "Enable the ELF Binary Format"
        default n
diff --git a/binfmt/Makefile b/binfmt/Makefile
index 6ebd09632f3..4b9a3048d54 100644
--- a/binfmt/Makefile
+++ b/binfmt/Makefile
@@ -46,6 +46,12 @@ ifeq ($(CONFIG_BUILTIN),y)
 CSRCS += builtin.c
 endif
 
+# Script (#!) interpreter support
+
+ifeq ($(CONFIG_BINFMT_SCRIPT),y)
+CSRCS += script.c
+endif
+
 # ELF application interfaces
 
 ifeq ($(CONFIG_ELF),y)
diff --git a/binfmt/binfmt.h b/binfmt/binfmt.h
index c362bf041c6..84000211c44 100644
--- a/binfmt/binfmt.h
+++ b/binfmt/binfmt.h
@@ -264,6 +264,19 @@ int builtin_initialize(void);
 void builtin_uninitialize(void);
 #endif
 
+#ifdef CONFIG_BINFMT_SCRIPT
+/****************************************************************************
+ * Name: script_initialize / script_uninitialize
+ *
+ * Description:
+ *   Register/unregister the script (#!) binary format handler.
+ *
+ ****************************************************************************/
+
+int script_initialize(void);
+void script_uninitialize(void);
+#endif
+
 #ifdef CONFIG_ELF
 /****************************************************************************
  * Name: elf_initialize
diff --git a/binfmt/binfmt_execmodule.c b/binfmt/binfmt_execmodule.c
index d27218a9233..63513eb3928 100644
--- a/binfmt/binfmt_execmodule.c
+++ b/binfmt/binfmt_execmodule.c
@@ -279,7 +279,13 @@ int exec_module(FAR struct binary_s *binp,
   stackaddr = binp->stackaddr;
 #endif
 
-  if (argv && argv[0])
+  /* 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)
     {
       ret = nxtask_init(tcb, argv[0], binp->priority, stackaddr,
                         binp->stacksize, binp->entrypt, &argv[1],
diff --git a/binfmt/binfmt_initialize.c b/binfmt/binfmt_initialize.c
index ef110e898ba..9151c259708 100644
--- a/binfmt/binfmt_initialize.c
+++ b/binfmt/binfmt_initialize.c
@@ -74,6 +74,14 @@ void binfmt_initialize(void)
     }
 #endif
 
+#ifdef CONFIG_BINFMT_SCRIPT
+  ret = script_initialize();
+  if (ret < 0)
+    {
+      berr("ERROR: script_initialize failed: %d\n", ret);
+    }
+#endif
+
   UNUSED(ret);
 }
 
diff --git a/binfmt/script.c b/binfmt/script.c
new file mode 100644
index 00000000000..dfedcb9d55d
--- /dev/null
+++ b/binfmt/script.c
@@ -0,0 +1,202 @@
+/****************************************************************************
+ * binfmt/script.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/binfmt/binfmt.h>
+#include <nuttx/fs/fs.h>
+
+#include "binfmt.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define SCRIPT_HEADER_MAX 128
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int script_loadbinary(FAR struct binary_s *binp,
+                             FAR const char *filename,
+                             FAR const struct symtab_s *exports,
+                             int nexports);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static struct binfmt_s g_script_binfmt =
+{
+  NULL,                /* next */
+  script_loadbinary,   /* load */
+  NULL,                /* unload */
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * 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.
+ *
+ ****************************************************************************/
+
+static int script_loadbinary(FAR struct binary_s *binp,
+                             FAR const char *filename,
+                             FAR const struct symtab_s *exports,
+                             int nexports)
+{
+  char header[SCRIPT_HEADER_MAX];
+  FAR char *interp;
+  FAR char *end;
+  struct file file;
+  int ret;
+
+  binfo("Attempting script load: %s\n", filename);
+
+  ret = file_open(&file, filename, O_RDONLY | O_CLOEXEC);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  ret = file_read(&file, header, sizeof(header) - 1);
+  file_close(&file);
+
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  header[ret] = '\0';
+
+  /* Check for "#!" magic */
+
+  if (header[0] != '#' || header[1] != '!')
+    {
+      return -ENOEXEC;
+    }
+
+  /* Terminate at newline */
+
+  end = strchr(header + 2, '\n');
+  if (end != NULL)
+    {
+      *end = '\0';
+    }
+
+  /* Skip whitespace after "#!" to find interpreter path */
+
+  interp = header + 2;
+  while (isspace(*interp))
+    {
+      interp++;
+    }
+
+  if (*interp == '\0')
+    {
+      berr("ERROR: Empty interpreter in %s\n", filename);
+      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.
+   */
+
+  end = interp;
+  while (*end != '\0' && !isspace(*end))
+    {
+      end++;
+    }
+
+  *end = '\0';
+
+  binfo("Script: interp='%s' script='%s'\n", interp, 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].
+   */
+
+  binp->filename = true;
+
+  return load_module(binp, interp, exports, nexports);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: script_initialize
+ *
+ * Description:
+ *   Register the script binary format handler.  Called from
+ *   binfmt_initialize() AFTER builtin and ELF handlers so those
+ *   formats are tried first.
+ *
+ ****************************************************************************/
+
+int script_initialize(void)
+{
+  int ret;
+
+  binfo("Registering Script Loader\n");
+
+  ret = register_binfmt(&g_script_binfmt);
+  if (ret < 0)
+    {
+      berr("ERROR: Failed to register script binfmt: %d\n", ret);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: script_uninitialize
+ ****************************************************************************/
+
+void script_uninitialize(void)
+{
+  unregister_binfmt(&g_script_binfmt);
+}
diff --git a/include/nuttx/binfmt/binfmt.h b/include/nuttx/binfmt/binfmt.h
index bff5e008aae..e3789330f18 100644
--- a/include/nuttx/binfmt/binfmt.h
+++ b/include/nuttx/binfmt/binfmt.h
@@ -30,6 +30,7 @@
 #include <nuttx/config.h>
 
 #include <spawn.h>
+#include <stdbool.h>
 #include <sys/types.h>
 
 #include <nuttx/sched.h>
@@ -111,6 +112,10 @@ struct binary_s
   FAR void *stackaddr;                 /* Task stack address */
 #endif
 
+  bool filename;                       /* Use filename as task name instead
+                                        * of argv[0]; preserve full argv.
+                                        */
+
   /* Unload module callback */
 
   CODE int (*unload)(FAR struct binary_s *bin);

Reply via email to