diff --git a/builtin/exec_builtin.c b/builtin/exec_builtin.c index aadc0b8d0..ad0883653 100644 --- a/builtin/exec_builtin.c +++ b/builtin/exec_builtin.c @@ -192,11 +192,11 @@ int exec_builtin(FAR const char *appname, FAR char * const *argv, goto errout_with_actions; } - /* Free attibutes and file actions. Ignoring return values in the case + /* Free attributes and file actions. Ignoring return values in the case * of an error. */ - /* Return the task ID of the new task if the task was sucessfully + /* Return the task ID of the new task if the task was successfully * started. Otherwise, ret will be ERROR (and the errno value will * be set appropriately). */ diff --git a/system/popen/.gitignore b/system/popen/.gitignore new file mode 100644 index 000000000..83bd7b811 --- /dev/null +++ b/system/popen/.gitignore @@ -0,0 +1,11 @@ +/Make.dep +/.depend +/.built +/*.asm +/*.rel +/*.lst +/*.sym +/*.adb +/*.lib +/*.src +/*.obj diff --git a/system/popen/Kconfig b/system/popen/Kconfig new file mode 100644 index 000000000..a91117c51 --- /dev/null +++ b/system/popen/Kconfig @@ -0,0 +1,37 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config SYSTEM_POPEN + bool "popen()/pclose() Functions" + default n + select SCHED_WAITPID + depends on NSH_LIBRARY + ---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. + +if SYSTEM_POPEN + +config SYSTEM_POPEN_STACKSIZE + int "Shell stack size" + default 2048 if !ARCH_SIM + default 4096 if ARCH_SIM + ---help--- + The size of stack allocated for the shell. + + NOTE: I needed to set the stack size quite large to get this example + working on the simulated target (perhaps because of the 64-bit + stack? Or perhaps that is a sneak call into the host libc that I + have not caught). I assume that a smaller stack would be okay on + real hardware, but I have not yet verified that. + +config SYSTEM_POPEN_PRIORITY + int "Shell priority" + default 100 + ---help--- + The priority of the shell. + +endif diff --git a/system/popen/Make.defs b/system/popen/Make.defs new file mode 100644 index 000000000..f6a3e9822 --- /dev/null +++ b/system/popen/Make.defs @@ -0,0 +1,39 @@ +############################################################################ +# apps/system/popen/Make.defs +# Adds selected applications to apps/ build +# +# Copyright (C) 2018 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +ifeq ($(CONFIG_SYSTEM_POPEN),y) +CONFIGURED_APPS += system/popen +endif diff --git a/system/popen/Makefile b/system/popen/Makefile new file mode 100644 index 000000000..cbc7549ce --- /dev/null +++ b/system/popen/Makefile @@ -0,0 +1,45 @@ +############################################################################ +# apps/system/popen/Makefile +# +# Copyright (C) 2018 Gregory Nutt. All rights reserved. +# Author: Gregory Nutt +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. Neither the name NuttX nor the names of its contributors may be +# used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +############################################################################ + +-include $(TOPDIR)/.config +-include $(TOPDIR)/Make.defs +include $(APPDIR)/Make.defs + +# popen()/pclose functions + +ASRCS = +CSRCS = popen.c + +include $(APPDIR)/Application.mk diff --git a/system/popen/popen.c b/system/popen/popen.c new file mode 100644 index 000000000..4dd8cff16 --- /dev/null +++ b/system/popen/popen.c @@ -0,0 +1,388 @@ +/**************************************************************************** + * apps/popen/popen/popen.c + * + * Copyright (C) 2018 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nshlib/nshlib.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* struct popen_file_s is a cast compatible version of FILE that contains + * the additional PID of the shell processes needed by pclose(). + */ + +struct popen_file_s +{ + FILE copy; + FILE *original; + pid_t shell; +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: popen + * + * Description: + * The popen() function will execute the command specified by the string + * command. It will create a pipe between the calling program and the + * 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, (char *)0); + * + * 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. + * + * The mode argument to popen() is a string that specifies I/O mode: + * + * - If mode is r, when the child process is started, its file descriptor + * STDOUT_FILENO will be the writable end of the pipe, and the file + * descriptor fileno(stream) in the calling process, where stream is + * the stream pointer returned by popen(), will be the readable end of + * the pipe. + * + * - If mode is w, when the child process is started its file descriptor + * STDIN_FILENO will be the readable end of the pipe, and the file + * descriptor fileno(stream) in the calling process, where stream is + * 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 + * + * Returned Value: + * A non-NULLFILE stream connected to the shell instance is returned on + * success. NULL is returned on any failure with the errno variable set + * appropriately. + * + ****************************************************************************/ + +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[2]; + int fd[2]; + int oldfd; + int newfd; + int retfd; + int errcode; + int result; + + /* 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; + } + + /* Create a pipe. fd[0] refers to the read end of the pipe; fd[1] refers + * to the write end of the pipe. + */ + + result = pipe(fd); + if (result < 0) + { + errcode = errno; + goto errout_with_container; + } + + /* Is the pipe the input to the shell? Or the output? */ + + if (strcmp(mode, "r") == 0) + { + /* Pipe is the output from the shell */ + + oldfd = 1; /* Replace stdout with the write side of the pipe */ + newfd = fd[1]; + retfd = fd[0]; /* Use read side of the pipe to create the return stream */ + } + else if (strcmp(mode, "w") == 0) + { + /* Pipe is the input to the shell */ + + oldfd = 0; /* Replace stdin with the read side of the pipe */ + newfd = fd[0]; + retfd = fd[1]; /* Use write side of the pipe to create the return stream */ + } + 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(). */ + + 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 task size and priority */ + + param.sched_priority = CONFIG_SYSTEM_POPEN_PRIORITY; + errcode = posix_spawnattr_setschedparam(&attr, ¶m); + if (errcode != 0) + { + goto errout_with_actions; + } + + errcode = task_spawnattr_setstacksize(&attr, CONFIG_SYSTEM_POPEN_STACKSIZE); + if (errcode != 0) + { + goto errout_with_actions; + } + + /* 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 input or output as determined by the mode parameter */ + + errcode = posix_spawn_file_actions_adddup2(&file_actions, oldfd, newfd); + if (errcode != 0) + { + goto errout_with_actions; + } + + /* Call task_spawn(), re-directing stdin or stdout appropriately */ + /* Start the built-in */ + + argv[0] = (FAR char *)command; + argv[1] = NULL; + + errcode = task_spawn(&container->shell, "popen", nsh_system, &file_actions, + &attr, argv, (FAR char * const *)NULL); + if (errcode != 0) + { + serr("ERROR: task_spawn failed: %d\n", result); + goto errout_with_actions; + } + + /* Free attributes and file actions. Ignoring return values in the case + * of an error. + */ + + (void)posix_spawn_file_actions_destroy(&file_actions); + (void)posix_spawnattr_destroy(&attr); + + /* Finale and return input input/output stream */ + + memcpy(&container->copy, container->original, sizeof(FILE)); + return &container->copy; + +errout_with_actions: + (void)posix_spawn_file_actions_destroy(&file_actions); + +errout_with_attrs: + (void)posix_spawnattr_destroy(&attr); + +errout_with_stream: + (void)fclose(container->original); + +errout_with_pipe: + (void)close(fd[0]); + (void)close(fd[1]); + +errout_with_container: + free(container); + +errout: + set_errno(errcode); + return NULL; +} + +/**************************************************************************** + * Name: pclose + * + * Description: + * The pclose() function will close a stream that was opened by popen(), + * wait for the command to terminate, and return the termination status of + * the process that was running the command language interpreter. However, + * if a call caused the termination status to be unavailable to pclose(), + * then pclose() will return -1 with errno set to ECHILD to report this + * situation. This can happen if the application calls one of the following + * functions: + * + * wait() + * waitpid() with a pid argument less than or equal to 0 or equal to the + * process ID of the command line interpreter + * + * Any other function not defined in this volume of IEEE Std 1003.1-2001 that + * could do one of the above + * + * In any case, pclose() will not return before the child process created by + * popen() has terminated. + * + * If the command language interpreter cannot be executed, the child termination + * status returned by pclose() will be as if the command language interpreter + * terminated using exit(127) or _exit(127). + * + * The pclose() function will not affect the termination status of any child of + * the calling process other than the one created by popen() for the associated + * stream. + * + * If the argument stream to pclose() is not a pointer to a stream created by + * popen(), the result of pclose() is undefined. + * + * Description: + * 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. + * + ****************************************************************************/ + +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 + * working copy + */ + + memcpy(original, &container->copy, sizeof(FILE)); + + /* Then close the original and free the container (saving the PID of the shell + * process) + */ + + (void)fclose(original); + + 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 EXIT_SUCCESS; +#endif +} diff --git a/system/system/Kconfig b/system/system/Kconfig index 9d1d44034..691d0c41a 100644 --- a/system/system/Kconfig +++ b/system/system/Kconfig @@ -6,7 +6,8 @@ config SYSTEM_SYSTEM bool "System Command" default n - depends on NSH_LIBRARY && SCHED_WAITPID + select SCHED_WAITPID + depends on NSH_LIBRARY ---help--- Enable support for the system() interface. This will support execution of NSH commands from C code.