7503f7bf33
The fcntl interface sets the O_CLOEXEC property by calling the ioctl interface. Let's call the ioctl interface directly. Signed-off-by: zhangshoukui <zhangshoukui@xiaomi.com>
441 lines
13 KiB
C
441 lines
13 KiB
C
/****************************************************************************
|
|
* apps/system/popen/popen.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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sched.h>
|
|
#include <spawn.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
#include <fcntl.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, 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.
|
|
*
|
|
* 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[4];
|
|
int fd[2];
|
|
int oldfd[2];
|
|
int newfd[2];
|
|
int retfd;
|
|
int errcode;
|
|
int result = 0;
|
|
bool rw = false;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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 */
|
|
|
|
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)
|
|
{
|
|
/* 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 */
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
/* 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 */
|
|
}
|
|
#endif
|
|
else if (result < 0)
|
|
{
|
|
errcode = errno;
|
|
goto errout_with_container;
|
|
}
|
|
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;
|
|
}
|
|
|
|
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[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]);
|
|
|
|
if (rw)
|
|
{
|
|
close(newfd[1]);
|
|
}
|
|
|
|
/* Free attributes and file actions. Ignoring return values in the case
|
|
* of an error.
|
|
*/
|
|
|
|
posix_spawn_file_actions_destroy(&file_actions);
|
|
posix_spawnattr_destroy(&attr);
|
|
|
|
if (strchr(mode, 'e') == NULL)
|
|
{
|
|
ioctl(retfd, FIOCLEX, 0);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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)
|
|
*/
|
|
|
|
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
|
|
}
|