You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2026/10/14 13:27:16 UTC
(nuttx) 01/05: binfmt: add script (#!) binary format handler
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 <cu...@xiaomi.com>
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 <cu...@xiaomi.com>
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <ma...@gmail.com>
---
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);