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-apps.git
commit 0d35e2e0bb7fb4504f864cf445b7c0ae262795b1 Author: cuiziwei <[email protected]> AuthorDate: Tue Apr 28 17:00:56 2026 +0800 system/popen: add dpopen/dpclose fd-based interface Add dpopen()/dpclose() as the descriptor-based counterpart of popen()/pclose(), analogous to how dprintf() relates to fprintf(). dpopen() returns a raw file descriptor instead of a FILE stream, avoiding the stdio.h dependency for callers that only need an fd. Refactor popen() as a thin wrapper: dpopen() + fdopen() + FILE container. All pipe creation and process spawning logic now lives in dpopen.c. Also remove the hard dependency on NSH_LIBRARY from SYSTEM_POPEN. When NSH is available, commands are executed through sh -c with full shell syntax support. When NSH is not available, commands are split by whitespace and executed directly via posix_spawnp(). Add CONFIG_SYSTEM_POPEN_MAXARGUMENTS (default 7) to control the argv array size for the no-shell path. Signed-off-by: cuiziwei <[email protected]> --- system/popen/CMakeLists.txt | 2 +- system/popen/Kconfig | 19 ++- system/popen/Makefile | 4 +- system/popen/dpopen.c | 321 ++++++++++++++++++++++++++++++++++++++++++++ system/popen/popen.c | 298 ++++++---------------------------------- 5 files changed, 379 insertions(+), 265 deletions(-) diff --git a/system/popen/CMakeLists.txt b/system/popen/CMakeLists.txt index 78add77e6..353205d24 100644 --- a/system/popen/CMakeLists.txt +++ b/system/popen/CMakeLists.txt @@ -21,5 +21,5 @@ # ############################################################################## if(CONFIG_SYSTEM_POPEN) - target_sources(apps PRIVATE popen.c) + target_sources(apps PRIVATE dpopen.c popen.c) endif() diff --git a/system/popen/Kconfig b/system/popen/Kconfig index 94041b0e0..606efa032 100644 --- a/system/popen/Kconfig +++ b/system/popen/Kconfig @@ -4,14 +4,25 @@ # config SYSTEM_POPEN - bool "popen()/pclose() Functions" + bool "popen()/pclose()/dpopen()/dpclose() Functions" default n select SCHED_WAITPID depends on NSH_LIBRARY && PIPES ---help--- - Enable support for the popen() and pclose() interfaces. - This will support execution of NSH commands from C code with - pipe communications with the shell. + Enable support for the popen(), pclose(), dpopen(), and + dpclose() interfaces. + + dpopen()/dpclose() are the descriptor-based counterparts + of popen()/pclose(), analogous to how dprintf() relates + to fprintf(). They return a raw file descriptor instead + of a FILE stream, avoiding the stdio.h dependency. + + 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. if SYSTEM_POPEN diff --git a/system/popen/Makefile b/system/popen/Makefile index fbd958274..7e8887de6 100644 --- a/system/popen/Makefile +++ b/system/popen/Makefile @@ -22,8 +22,8 @@ include $(APPDIR)/Make.defs -# popen()/pclose functions +# dpopen()/dpclose() core and popen()/pclose() FILE wrappers -CSRCS = popen.c +CSRCS = dpopen.c popen.c include $(APPDIR)/Application.mk diff --git a/system/popen/dpopen.c b/system/popen/dpopen.c new file mode 100644 index 000000000..dd8bc9b7d --- /dev/null +++ b/system/popen/dpopen.c @@ -0,0 +1,321 @@ +/**************************************************************************** + * apps/system/popen/dpopen.c + * + * 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 <sys/wait.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <sched.h> +#include <spawn.h> +#include <debug.h> +#include <fcntl.h> +#include <errno.h> + +#if defined(CONFIG_NET_LOCAL) && defined(CONFIG_NET_LOCAL_STREAM) +# include <sys/socket.h> +#endif + +#include "nshlib/nshlib.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: dpopen + * + * Description: + * Execute a command and return a raw file descriptor connected to the + * child process via a pipe, along with the child PID. + * + * This is the descriptor-based counterpart of popen(), analogous to how + * dprintf() relates to fprintf(). It avoids the FILE stream layer, + * making it suitable for callers that only need a raw fd and want to + * avoid the stdio.h dependency. + * + * When NSH is available, commands are executed through the shell + * (sh -c command), supporting full shell syntax. + * + * Input Parameters: + * command - The command string to execute + * oflag - O_RDONLY to read child stdout, O_WRONLY to write child stdin, + * O_RDWR for bidirectional socket mode + * pid - Location to return the child process ID + * + * Returned Value: + * A valid file descriptor on success, or -1 on failure with errno set. + * + ****************************************************************************/ + +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; + FAR char *argv[4]; + int fd[2]; + int childfd; + int parentfd; + int errcode; + + if (command == NULL || pid == NULL) + { + errno = EINVAL; + return -1; + } + + /* Create a pipe or socketpair. fd[0] refers to the read end of the + * pipe; fd[1] refers to the write end of the pipe. + */ + + if ((oflag & O_RDWR) == O_RDWR) + { +#if defined(CONFIG_NET_LOCAL) && defined(CONFIG_NET_LOCAL_STREAM) + /* Create a socketpair for bidirectional communication */ + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fd) < 0) + { + return -1; + } + + childfd = fd[0]; + parentfd = fd[1]; +#else + errno = ENOTSUP; + return -1; +#endif + } + else if (pipe2(fd, O_CLOEXEC) < 0) + { + return -1; + } + else if ((oflag & O_ACCMODE) == O_RDONLY) + { + /* Pipe is the output from the child */ + + childfd = fd[1]; /* Child writes to pipe */ + parentfd = fd[0]; /* Parent reads from pipe */ + } + else if ((oflag & O_ACCMODE) == O_WRONLY) + { + /* Pipe is the input to the child */ + + childfd = fd[0]; /* Child reads from pipe */ + parentfd = fd[1]; /* Parent writes to pipe */ + } + else + { + errcode = EINVAL; + goto errout_with_pipe; + } + + /* Initialize attributes for task_spawn() (or posix_spawn()). */ + + errcode = posix_spawnattr_init(&attr); + if (errcode != 0) + { + goto errout_with_pipe; + } + + errcode = posix_spawn_file_actions_init(&file_actions); + if (errcode != 0) + { + goto errout_with_attr; + } + + /* Set the correct stack size and priority */ + + param.sched_priority = CONFIG_SYSTEM_POPEN_PRIORITY; + errcode = posix_spawnattr_setschedparam(&attr, ¶m); + if (errcode != 0) + { + goto errout_with_actions; + } + +#ifndef CONFIG_SYSTEM_POPEN_SHPATH + errcode = posix_spawnattr_setstacksize(&attr, + CONFIG_SYSTEM_POPEN_STACKSIZE); + if (errcode != 0) + { + goto errout_with_actions; + } +#endif + + /* If robin robin scheduling is enabled, then set the scheduling policy + * of the new task to SCHED_RR before it has a chance to run. + */ + +#if CONFIG_RR_INTERVAL > 0 + errcode = posix_spawnattr_setschedpolicy(&attr, SCHED_RR); + if (errcode != 0) + { + goto errout_with_actions; + } + + errcode = posix_spawnattr_setflags(&attr, + POSIX_SPAWN_SETSCHEDPARAM | + POSIX_SPAWN_SETSCHEDULER); + if (errcode != 0) + { + goto errout_with_actions; + } + +#else + errcode = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSCHEDPARAM); + if (errcode != 0) + { + goto errout_with_actions; + } + +#endif + + /* Redirect child's stdin or stdout to the pipe */ + + if ((oflag & O_RDWR) == O_RDWR) + { + errcode = posix_spawn_file_actions_adddup2(&file_actions, childfd, + STDIN_FILENO); + if (errcode != 0) + { + goto errout_with_actions; + } + + errcode = posix_spawn_file_actions_adddup2(&file_actions, childfd, + STDOUT_FILENO); + if (errcode != 0) + { + goto errout_with_actions; + } + } + else + { + errcode = posix_spawn_file_actions_adddup2(&file_actions, childfd, + (oflag & O_ACCMODE) == O_RDONLY ? + STDOUT_FILENO : STDIN_FILENO); + if (errcode != 0) + { + goto errout_with_actions; + } + } + + /* Call task_spawn() (or posix_spawn), re-directing stdin or stdout + * appropriately. + */ + + argv[1] = "-c"; + argv[2] = (FAR char *)command; + argv[3] = NULL; + +#ifdef CONFIG_SYSTEM_POPEN_SHPATH + argv[0] = CONFIG_SYSTEM_POPEN_SHPATH; + errcode = posix_spawn(pid, argv[0], &file_actions, + &attr, argv, NULL); +#else + *pid = task_spawn("dpopen", nsh_system, &file_actions, + &attr, argv + 1, NULL); + if (*pid < 0) + { + errcode = -*pid; + } +#endif + + if (errcode != 0) + { + serr("ERROR: dpopen spawn failed: %d\n", errcode); + goto errout_with_pipe; + } + + /* Free attributes and file actions */ + + posix_spawn_file_actions_destroy(&file_actions); + posix_spawnattr_destroy(&attr); + + /* Close the child's end in the parent */ + + if (!(oflag & O_CLOEXEC)) + { + ioctl(parentfd, FIONCLEX, 0); + } + + close(childfd); + + return parentfd; + +errout_with_actions: + posix_spawn_file_actions_destroy(&file_actions); + +errout_with_attr: + posix_spawnattr_destroy(&attr); + +errout_with_pipe: + close(fd[0]); + close(fd[1]); + errno = errcode; + return -1; +} + +/**************************************************************************** + * Name: dpclose + * + * Description: + * Close a file descriptor opened by dpopen() and wait for the child + * process to terminate. + * + * This is the descriptor-based counterpart of pclose(). + * + * Input Parameters: + * fd - The file descriptor returned by dpopen() + * pid - The child process ID returned by dpopen() + * + * Returned Value: + * The child termination status on success, or -1 on failure with + * errno set. + * + ****************************************************************************/ + +int dpclose(int fd, pid_t pid) +{ +#ifdef CONFIG_SCHED_WAITPID + int status; + int ret; +#endif + + if (fd >= 0) + { + close(fd); + } + +#ifdef CONFIG_SCHED_WAITPID + ret = waitpid(pid, &status, 0); + if (ret < 0) + { + return ERROR; + } + + return status; +#else + return 0; +#endif +} diff --git a/system/popen/popen.c b/system/popen/popen.c index b52fbaafe..4f46e6cd2 100644 --- a/system/popen/popen.c +++ b/system/popen/popen.c @@ -26,21 +26,13 @@ #include <nuttx/config.h> -#include <sys/wait.h> -#include <sys/ioctl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> -#include <sched.h> -#include <spawn.h> -#include <assert.h> -#include <nuttx/debug.h> #include <fcntl.h> #include <errno.h> -#include "nshlib/nshlib.h" - /**************************************************************************** * Private Types ****************************************************************************/ @@ -69,17 +61,9 @@ struct popen_file_s * executed command, and will return a pointer to a stream that can be * used to either read from or write to the pipe. * - * The environment of the executed command will be as if a child process - * were created within the popen() call using the fork() function, and the - * child invoked the sh utility using the call: - * - * execl(shell path, "sh", "-c", command, NULL); - * - * where shell path is an unspecified pathname for the sh utility. - * - * The popen() function will ensure that any streams from previous popen() - * calls that remain open in the parent process are closed in the new child - * process. + * This is a thin wrapper around dpopen() that wraps the returned file + * descriptor in a FILE stream, analogous to how fprintf() relates to + * dprintf(). * * The mode argument to popen() is a string that specifies I/O mode: * @@ -95,18 +79,12 @@ struct popen_file_s * the stream pointer returned by popen(), will be the writable end of * the pipe. * - * If mode is any other value, the result is undefined. - * - * After popen(), both the parent and the child process will be capable of - * executing independently before either terminates. - * - * Pipe streams are byte-oriented. - * * Input Parameters: - * command + * command - The command string to execute + * mode - "r" or "w" * * Returned Value: - * A non-NULLFILE stream connected to the shell instance is returned on + * A non-NULL FILE stream connected to the child process is returned on * success. NULL is returned on any failure with the errno variable set * appropriately. * @@ -115,244 +93,67 @@ struct popen_file_s FILE *popen(FAR const char *command, FAR const char *mode) { FAR struct popen_file_s *container; - struct sched_param param; - posix_spawnattr_t attr; - posix_spawn_file_actions_t file_actions; - FAR char *argv[4]; - int fd[2]; - int oldfd[2]; - int newfd[2]; - int retfd; - int errcode; - int result = 0; - bool rw = false; + int oflag; + int fd; /* Allocate a container for returned FILE stream */ container = (FAR struct popen_file_s *)malloc(sizeof(struct popen_file_s)); if (container == NULL) { - errcode = ENOMEM; - goto errout; + errno = ENOMEM; + return NULL; } - oldfd[1] = 0; - newfd[1] = 0; - - /* Create a pipe. fd[0] refers to the read end of the pipe; fd[1] refers - * to the write end of the pipe. - * Is the pipe the input to the shell? Or the output? - */ - - if (strcmp(mode, "r") == 0 && - (result = pipe2(fd, O_CLOEXEC)) >= 0) - { - /* Pipe is the output from the shell */ + /* Map mode string to open flags */ - oldfd[0] = 1; /* Replace stdout with the write side of the pipe */ - newfd[0] = fd[1]; - retfd = fd[0]; /* Use read side of the pipe to create the return stream */ - } - else if (strcmp(mode, "w") == 0 && - (result = pipe2(fd, O_CLOEXEC)) >= 0) + if (strstr(mode, "r+") || strstr(mode, "w+")) { - /* Pipe is the input to the shell */ - - oldfd[0] = 0; /* Replace stdin with the read side of the pipe */ - newfd[0] = fd[0]; - retfd = fd[1]; /* Use write side of the pipe to create the return stream */ + oflag = O_RDWR; } - - /* Create a socketpair. Using fd[0] as the input and output to the shell */ - -#if defined(CONFIG_NET_LOCAL) && defined(CONFIG_NET_LOCAL_STREAM) - else if ((strcmp(mode, "r+") == 0 || strcmp(mode, "w+") == 0) && - (result = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, - 0, fd)) >= 0) + else if (strstr(mode, "r")) { - /* Socketpair is the input/output to the shell */ - - rw = true; - oldfd[0] = 0; /* Replace stdin with the one side of a socket pair */ - newfd[0] = fd[0]; - oldfd[1] = 1; /* Replace stdout with the one side of a socket pair */ - newfd[1] = fd[0]; - retfd = fd[1]; /* Use other side of the socket pair to create the return stream */ + oflag = O_RDONLY; } -#endif - else if (result < 0) + else if (strstr(mode, "w")) { - errcode = errno; - goto errout_with_container; + oflag = O_WRONLY; } else { - errcode = EINVAL; - goto errout_with_pipe; - } - - /* Create the FILE stream return reference */ - - container->original = fdopen(retfd, mode); - if (container->original == NULL) - { - errcode = errno; - goto errout_with_pipe; - } - - /* Initialize attributes for task_spawn() (or posix_spawn()). */ - - errcode = posix_spawnattr_init(&attr); - if (errcode != 0) - { - goto errout_with_stream; - } - - errcode = posix_spawn_file_actions_init(&file_actions); - if (errcode != 0) - { - goto errout_with_attrs; - } - - /* Set the correct stack size and priority */ - - param.sched_priority = CONFIG_SYSTEM_POPEN_PRIORITY; - errcode = posix_spawnattr_setschedparam(&attr, ¶m); - if (errcode != 0) - { - goto errout_with_actions; - } - -#ifndef CONFIG_SYSTEM_POPEN_SHPATH - errcode = posix_spawnattr_setstacksize(&attr, - CONFIG_SYSTEM_POPEN_STACKSIZE); - if (errcode != 0) - { - goto errout_with_actions; - } -#endif - - /* If robin robin scheduling is enabled, then set the scheduling policy - * of the new task to SCHED_RR before it has a chance to run. - */ - -#if CONFIG_RR_INTERVAL > 0 - errcode = posix_spawnattr_setschedpolicy(&attr, SCHED_RR); - if (errcode != 0) - { - goto errout_with_actions; + free(container); + errno = EINVAL; + return NULL; } - errcode = posix_spawnattr_setflags(&attr, - POSIX_SPAWN_SETSCHEDPARAM | - POSIX_SPAWN_SETSCHEDULER); - if (errcode != 0) + if (strchr(mode, 'e') != NULL) { - goto errout_with_actions; + oflag |= O_CLOEXEC; } -#else - errcode = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSCHEDPARAM); - if (errcode != 0) - { - goto errout_with_actions; - } - -#endif - - /* Redirect input or output as determined by the mode parameter */ - - errcode = posix_spawn_file_actions_adddup2(&file_actions, - newfd[0], oldfd[0]); - if (errcode != 0) - { - goto errout_with_actions; - } - - if (rw) - { - errcode = posix_spawn_file_actions_adddup2(&file_actions, - newfd[1], oldfd[1]); - if (errcode != 0) - { - goto errout_with_actions; - } - } - - /* Call task_spawn() (or posix_spawn), re-directing stdin or stdout - * appropriately. - */ - - argv[1] = "-c"; - argv[2] = (FAR char *)command; - argv[3] = NULL; - -#ifdef CONFIG_SYSTEM_POPEN_SHPATH - argv[0] = CONFIG_SYSTEM_POPEN_SHPATH; - errcode = posix_spawn(&container->shell, argv[0], &file_actions, - &attr, argv, NULL); -#else - container->shell = task_spawn("popen", nsh_system, &file_actions, - &attr, argv + 1, NULL); - if (container->shell < 0) - { - errcode = -container->shell; - } -#endif - - if (errcode != 0) - { - serr("ERROR: Spawn failed: %d\n", errcode); - goto errout_with_actions; - } - - /* We can close the 'newfd' now. It is no longer useful on this side of - * the interface. - */ - - close(newfd[0]); + /* Use dpopen() to do the real work */ - if (rw && newfd[0] != newfd[1]) + fd = dpopen(command, oflag, &container->shell); + if (fd < 0) { - close(newfd[1]); + free(container); + return NULL; } - /* Free attributes and file actions. Ignoring return values in the case - * of an error. - */ + /* Wrap the raw fd in a FILE stream */ - posix_spawn_file_actions_destroy(&file_actions); - posix_spawnattr_destroy(&attr); - - if (strchr(mode, 'e') == NULL) + container->original = fdopen(fd, mode); + if (container->original == NULL) { - ioctl(retfd, FIOCLEX, 0); + int errcode = errno; + dpclose(fd, container->shell); + free(container); + errno = errcode; + return NULL; } - /* Finale and return input input/output stream */ - memcpy(&container->copy, container->original, sizeof(FILE)); return &container->copy; - -errout_with_actions: - posix_spawn_file_actions_destroy(&file_actions); - -errout_with_attrs: - posix_spawnattr_destroy(&attr); - -errout_with_stream: - fclose(container->original); - -errout_with_pipe: - close(fd[0]); - close(fd[1]); - -errout_with_container: - free(container); - -errout: - errno = errcode; - return NULL; } /**************************************************************************** @@ -388,12 +189,12 @@ errout: * If the argument stream to pclose() is not a pointer to a stream created * by popen(), the result of pclose() is undefined. * - * Description: + * Input Parameters: * stream - The stream reference returned by a previous call to popen() * * Returned Value: - * Zero (OK) is returned on success; otherwise -1 (ERROR) is returned and - * the errno variable is set appropriately. + * The child termination status on success, or -1 (ERROR) on failure + * with errno set. * ****************************************************************************/ @@ -402,12 +203,7 @@ int pclose(FILE *stream) FAR struct popen_file_s *container = (FAR struct popen_file_s *)stream; FILE *original; pid_t shell; -#ifdef CONFIG_SCHED_WAITPID - int status; - int result; -#endif - DEBUGASSERT(container != NULL && container->original != NULL); original = container->original; /* Set the state of the original file descriptor to the state of the @@ -417,7 +213,7 @@ int pclose(FILE *stream) memcpy(original, &container->copy, sizeof(FILE)); /* Then close the original and free the container (saving the PID of the - * shell process) + * shell process). Pass -1 to dpclose since fclose already closed the fd. */ fclose(original); @@ -425,19 +221,5 @@ int pclose(FILE *stream) shell = container->shell; free(container); -#ifdef CONFIG_SCHED_WAITPID - /* Wait for the shell to exit, retrieving the return value if available. */ - - result = waitpid(shell, &status, 0); - if (result < 0) - { - /* The errno has already been set */ - - return ERROR; - } - - return status; -#else - return OK; -#endif + return dpclose(-1, shell); }
