nuttx-apps/system/popen/popen.c

404 lines
12 KiB
C

/****************************************************************************
* apps/popen/popen/popen.c
*
* Copyright (C) 2018 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* 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 <nuttx/config.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sched.h>
#include <spawn.h>
#include <assert.h>
#include <debug.h>
#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() (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, &param);
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, newfd, oldfd);
if (errcode != 0)
{
goto errout_with_actions;
}
/* Call task_spawn() (or posix_spawn), re-directing stdin or stdout
* appropriately.
*/
argv[0] = (FAR char *)command;
argv[1] = NULL;
#ifdef CONFIG_BUILD_KERNEL
errcode = posix_spawn(&container->shell, CONFIG_SYSTEM_POPEN_SHPATH,
&file_actions, &attr, argv,
(FAR char * const *)NULL);
#else
errcode = task_spawn(&container->shell, "popen", nsh_system, &file_actions,
&attr, argv, (FAR char * const *)NULL);
#endif
if (errcode != 0)
{
serr("ERROR: Spawn failed: %d\n", result);
goto errout_with_actions;
}
/* We can close the 'newfd' now. It is no longer useful on this side of
* the interface.
*/
(void)close(newfd);
/* 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 OK;
#endif
}