system/popen: support no-shell mode via posix_spawnp

When NSH_LIBRARY is not available, dpopen()/popen() can still execute
commands by splitting the command string by whitespace and calling
posix_spawnp() directly.  Shell syntax (pipes, redirects, globbing)
is not supported in this mode.

Add CONFIG_SYSTEM_POPEN_MAXARGUMENTS (default 7) to control the
argv array size for the no-shell path.

Remove the hard dependency on NSH_LIBRARY from SYSTEM_POPEN so the
feature can be used in minimal configurations without a shell.

Signed-off-by: cuiziwei <cuiziwei@xiaomi.com>
This commit is contained in:
cuiziwei 2026-04-28 17:32:22 +08:00 committed by Xiang Xiao
parent 0d35e2e0bb
commit 740fda9630
2 changed files with 78 additions and 8 deletions

View file

@ -7,7 +7,7 @@ config SYSTEM_POPEN
bool "popen()/pclose()/dpopen()/dpclose() Functions"
default n
select SCHED_WAITPID
depends on NSH_LIBRARY && PIPES
depends on PIPES
---help---
Enable support for the popen(), pclose(), dpopen(), and
dpclose() interfaces.
@ -20,16 +20,20 @@ config SYSTEM_POPEN
popen()/pclose() are thin wrappers around dpopen()/dpclose()
that additionally wrap the fd in a FILE stream.
Commands are executed through the NSH shell (sh -c command),
supporting full shell syntax including pipes, redirects,
and globbing.
When NSH is available (NSH_LIBRARY), commands are executed
through the shell (sh -c command), supporting full shell
syntax including pipes, redirects, and globbing.
When NSH is not available, commands are executed directly
via posix_spawnp(). The command string must be a simple
"program arg1 arg2" form without shell syntax.
if SYSTEM_POPEN
config SYSTEM_POPEN_SHPATH
string "Path to shell command"
default "/bin/sh"
depends on SYSTEM_NSH=m || BUILD_KERNEL
depends on (SYSTEM_NSH=m || BUILD_KERNEL) && NSH_LIBRARY
---help---
This is the full path to the program in a mounted file system that
implements the system() command. That is, a program that starts the
@ -53,4 +57,17 @@ config SYSTEM_POPEN_PRIORITY
---help---
The priority of the shell.
config SYSTEM_POPEN_MAXARGUMENTS
int "Maximum number of command arguments (no-shell mode)"
default 7
depends on !NSH_LIBRARY
---help---
When NSH is not available, dpopen()/popen() splits the command
string by whitespace into an argv array. This sets the maximum
number of arguments (excluding the program name and the
terminating NULL).
This is analogous to CONFIG_NSH_MAXARGUMENTS but applies
only to the no-shell path.
endif

View file

@ -27,6 +27,7 @@
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <sched.h>
#include <spawn.h>
#include <debug.h>
@ -37,7 +38,17 @@
# include <sys/socket.h>
#endif
#include "nshlib/nshlib.h"
#ifdef CONFIG_NSH_LIBRARY
# include "nshlib/nshlib.h"
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifndef CONFIG_NSH_LIBRARY
# define DPOPEN_MAX_ARGV (CONFIG_SYSTEM_POPEN_MAXARGUMENTS + 1)
#endif
/****************************************************************************
* Public Functions
@ -58,6 +69,10 @@
* When NSH is available, commands are executed through the shell
* (sh -c command), supporting full shell syntax.
*
* When NSH is not available, the command is split by whitespace and
* executed directly via posix_spawnp(). Shell syntax (pipes, redirects,
* globbing) is not supported in this mode.
*
* Input Parameters:
* command - The command string to execute
* oflag - O_RDONLY to read child stdout, O_WRONLY to write child stdin,
@ -74,7 +89,14 @@ int dpopen(FAR const char *command, int oflag, FAR pid_t *pid)
struct sched_param param;
posix_spawnattr_t attr;
posix_spawn_file_actions_t file_actions;
#ifdef CONFIG_NSH_LIBRARY
FAR char *argv[4];
#else
char cmdbuf[PATH_MAX];
FAR char *argv[DPOPEN_MAX_ARGV];
FAR char *saveptr;
int argc = 0;
#endif
int fd[2];
int childfd;
int parentfd;
@ -224,21 +246,52 @@ int dpopen(FAR const char *command, int oflag, FAR pid_t *pid)
* appropriately.
*/
#ifdef CONFIG_NSH_LIBRARY
/* Shell mode: execute command through sh -c */
argv[1] = "-c";
argv[2] = (FAR char *)command;
argv[3] = NULL;
#ifdef CONFIG_SYSTEM_POPEN_SHPATH
# ifdef CONFIG_SYSTEM_POPEN_SHPATH
argv[0] = CONFIG_SYSTEM_POPEN_SHPATH;
errcode = posix_spawn(pid, argv[0], &file_actions,
&attr, argv, NULL);
#else
# else
*pid = task_spawn("dpopen", nsh_system, &file_actions,
&attr, argv + 1, NULL);
if (*pid < 0)
{
errcode = -*pid;
}
# endif
#else
/* No-shell mode: split command and execute directly via posix_spawnp.
* The command must be in "program arg1 arg2" form -- no shell syntax.
*/
if (strlcpy(cmdbuf, command, sizeof(cmdbuf)) >= sizeof(cmdbuf))
{
errcode = ENAMETOOLONG;
goto errout_with_actions;
}
do
{
argv[argc] = strtok_r(argc ? NULL : cmdbuf, " \t", &saveptr);
}
while (argv[argc] != NULL && ++argc < DPOPEN_MAX_ARGV - 1);
argv[argc] = NULL;
if (argc == 0)
{
errcode = EINVAL;
goto errout_with_actions;
}
errcode = posix_spawnp(pid, argv[0], &file_actions,
&attr, argv, NULL);
#endif
if (errcode != 0)