#include <errno.h>
#include <stdio.h>
#include <string.h>
+#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
/** @brief runs command and returns it's output as string
* @param command the full command with all parameters
* @param rescode struct containing the return code, if the program exited normally and so on
+ * @param out Whether to collect \c stdout.
+ * @param err Whether to collect \c stderr; combines with \c out.
+ * @param path Wether to look up the executable in \c $PATH.
* @returns the output (stdout) of the called program
*/
template <typename CmdT>
std::string capture_exec(CmdT command, ExecResult &rescode,
- const bool out, const bool err)
+ const bool out, const bool err,
+ const bool path, const bool env)
{
std::string output;
try
{
{
- inpipestream ips(command, out, err);
+ inpipestream ips(command, out, err, path, env);
ips.store_exit_status(&exit_set, &exit_status_waitpid);
* passed.
*/
std::string capture_exec (const std::string &command, ExecResult &res)
-{ return capture_exec<const std::string &>(command, res, true, false); }
+{ return capture_exec<const std::string &>(command, res, true, false, false, false); }
/** @brief Instantiation of \c capture_exec for argument lists. The
* pipestream used to run the command will not shell out.
* state in this struct.
* @param out Whether to collect \c stdout.
* @param err Whether to collect \c stderr; combines with \c out.
+ * @param path Wether to look up the executable in \c $PATH.
*
* @returns Captured output, combined into one string.
*/
std::string capture_exec (const char *const *command, ExecResult &res,
- const bool out, const bool err)
-{ return capture_exec<const char *const *>(command, res, out, err); }
+ const bool out, const bool err, const bool path,
+ const bool env)
+{ return capture_exec<const char *const *>(command, res, out, err, path, env); }
/** @brief Instantiation of \c capture_exec for argument lists. The
* pipestream used to run the command will not shell out.
* state in this struct.
* @param out Whether to collect \c stdout.
* @param err Whether to collect \c stderr; combines with \c out.
+ * @param path Wether to look up the executable in \c $PATH.
*
* @returns Captured output, combined into one string.
*/
std::string capture_exec (const std::vector<std::string> &command, ExecResult &res,
- const bool out, const bool err)
+ const bool out, const bool err, const bool path,
+ const bool env)
{
return capture_exec<const std::vector<std::string> &>
- (command, res, out, err);
+ (command, res, out, err, path, env);
}
#define PIPE_CTOR_FAIL(where) \
return boost::shared_array<char *> (ret);
}
+/** @brief Helper for redirecting a file descriptor to \c /dev/null.
+ * This will only acquire an fd the first time it is called
+ * or if it is called after unsuccessfully attempting to
+ * acquire one.
+ *
+ * @param fd The open file descriptor to operate on.
+ * @param save_errno Out parameter: stores errno here after a syscall failure.
+ *
+ * @returns \c true on success, \c false otherwise (the call to
+ * either \c open(2) or \c dup2(2) failed), with errno
+ * communicated through saved_errno.
+ */
+static bool
+redirect_devnull (const int fd, int &save_errno)
+{
+ static int nullfd = -1;
+
+ errno = 0;
+ if (nullfd == -1 && (nullfd = open ("/dev/null", O_RDWR)) == -1) {
+ save_errno = errno;
+ return false;
+ }
+
+ errno = 0;
+ if (dup2 (nullfd, fd) == -1) {
+ save_errno = errno;
+ return false;
+ }
+
+ return true;
+}
/** @brief Helper aggregating common code for the shell-free ctors.
*
* @returns A \c FILE* handle for streaming if successful, \c NULL
* otherwise.
*/
-FILE *
+std::pair <pid_t, FILE *>
inpipebuf::init_without_shell (const char *const *argv,
const bool out,
- const bool err) const
+ const bool err,
+ const bool path,
+ const bool env) const
{
FILE *pipeobj = NULL;
- int pipefd [2];
+ int pipefd [2]; /* for reading output from the child */
+ int errfd [2]; /* for determining a successful exec() */
+ sigset_t oldmask, newmask;
+ char *const *envp = env ? environ : NULL;
if (!out && !err) {
errno = EINVAL;
}
errno = 0;
- if (::pipe (pipefd) == -1) {
- PIPE_CTOR_FAIL("pipe");
+ if ( ::pipe2 (pipefd, O_CLOEXEC) == -1
+ || ::pipe2 (errfd , O_CLOEXEC) == -1) {
+ PIPE_CTOR_FAIL("pipe2");
}
+ sigfillset (&newmask);
+ sigprocmask (SIG_SETMASK, &newmask, &oldmask);
+
errno = 0;
pid_t childpid = fork ();
switch (childpid) {
case -1: {
+ sigprocmask (SIG_SETMASK, &oldmask, NULL);
PIPE_CTOR_FAIL("fork");
break;
}
case 0: {
close (pipefd [0]);
+ close (errfd [0]);
+
+ fcntl (pipefd [1], F_SETFD, 0);
+ int save_errno = 0;
if (!out) {
- close (STDOUT_FILENO);
+ if (!redirect_devnull (STDOUT_FILENO, save_errno)) {
+ (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno));
+ exit (EXIT_FAILURE);
+ }
} else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
- PIPE_CTOR_FAIL("dup2/stdout");
+ (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno));
+ exit (EXIT_FAILURE);
}
if (!err) {
- close (STDERR_FILENO);
+ if (!redirect_devnull (STDERR_FILENO, save_errno)) {
+ (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno));
+ exit (EXIT_FAILURE);
+ }
} else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
- PIPE_CTOR_FAIL("dup2/stderr");
+ (void)write (errfd [1], (char *)&save_errno, sizeof(save_errno));
+ exit (EXIT_FAILURE);
}
+ close (pipefd [1]);
+
+ sigprocmask (SIG_SETMASK, &oldmask, NULL);
+
errno = 0;
- if (execve (argv [0], const_cast <char *const *>(argv), NULL) == -1) {
- PIPE_CTOR_FAIL("exec");
+ if (path) {
+ execvpe (argv [0], const_cast <char *const *>(argv), envp);
+ } else {
+ execve (argv [0], const_cast <char *const *>(argv), envp);
}
+
+ (void)write (errfd [1], (char *)&errno, sizeof(errno));
+ exit (EXIT_FAILURE);
break;
}
default: {
- close (pipefd [1]);
-
- errno = 0;
- if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
- PIPE_CTOR_FAIL("fdopen");
- }
break;
}
}
- return pipeobj;
+ close (pipefd [1]);
+ close (errfd [1]);
+
+ /*
+ * Check whether the child exec()’ed by reading from the error pipe.
+ * The call to read(2) will block, uninterruptible due to signals being
+ * blocked. If all went well, the read(2) will return zero bytes and we can
+ * ditch the error channel.
+ *
+ * Otherwise either the read(2) failed or we actually received something
+ * through the error pipe. Both cases are treated as errors and cause an
+ * exit from the ctor.
+ */
+ char buf [sizeof (errno)];
+ int ret;
+ memset (buf, 0, sizeof (buf));
+ errno = 0;
+ if ((ret = read (errfd [0], buf, sizeof (buf))) != 0) {
+ close (pipefd [0]);
+ close (errfd [0]);
+ sigprocmask (SIG_SETMASK, &oldmask, NULL);
+ if (ret == - 1) {
+ /* read(2) failed */
+ PIPE_CTOR_FAIL("read");
+ } else {
+ /*
+ * We received data on the error channel indicating the child
+ * process never successfully exec()’ed. We grab the error code
+ * from the buffer and bail.
+ */
+ errno = *((int *)&buf[0]);
+ PIPE_CTOR_FAIL("child failed to exec()");
+ }
+ }
+
+ /*
+ * read(2) yielded zero bytes; it’s safe to use the pipe so close our end
+ * and continue.
+ */
+ close (errfd [0]);
+
+ sigprocmask (SIG_SETMASK, &oldmask, NULL);
+
+ errno = 0;
+ if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
+ close (pipefd [0]);
+ PIPE_CTOR_FAIL("fdopen");
+ }
+
+ return std::make_pair (childpid, pipeobj);
}
inpipebuf::inpipebuf(const char *const *command,
const bool out,
- const bool err)
+ const bool err,
+ const bool path,
+ const bool env)
: pipe (NULL) /* brr: shadowing global ident */
+ , pid (-1)
, status_set (NULL)
, exit_status (NULL)
{
PIPE_CTOR_FAIL("command");
}
- this->pipe = this->init_without_shell (command, out, err);
+ std::pair <pid_t, FILE *> tmp =
+ this->init_without_shell (command, out, err, path, env);
+ this->pid = tmp.first; /* no std::tie :/ */
+ this->pipe = tmp.second;
setg (&buffer, &buffer, &buffer);
}
inpipebuf::inpipebuf(const std::vector<std::string> &command,
const bool out,
- const bool err)
+ const bool err,
+ const bool path,
+ const bool env)
: pipe (NULL) /* brr: shadowing global ident */
+ , pid (-1)
, status_set (NULL)
, exit_status (NULL)
{
PIPE_CTOR_FAIL("malloc");
}
- this->pipe = this->init_without_shell (argv.get (), out, err);
+ std::pair <pid_t, FILE *> tmp =
+ this->init_without_shell (argv.get (), out, err, path, env);
+ this->pid = tmp.first;
+ this->pipe = tmp.second;
setg (&buffer, &buffer, &buffer);
}
inpipebuf::inpipebuf(const std::string& command,
const bool _ignored_out,
- const bool _ignored_err)
+ const bool _ignored_err,
+ const bool _ignored_path,
+ const bool _ignored_env)
+ : pid (-1)
+ , status_set (NULL)
+ , exit_status (NULL)
{
- status_set = NULL;
- exit_status = NULL;
-
pipe = popen (command.c_str(), "r");
if (pipe == NULL)
throw EXCEPTION (pipestream_error, "can't open program or permission denied");
inpipebuf::~inpipebuf()
{
if (pipe != NULL) {
- int pclose_exit = pclose (pipe);
+ int status;
- if (exit_status && pclose_exit != -1)
+ if (this->pid == -1)
{
- if (status_set)
- *status_set = true;
- *exit_status = pclose_exit;
+ errno = 0;
+ status = pclose (pipe);
+ if (status != -1) {
+ if (exit_status != NULL) {
+ *exit_status = status;
+ if (status_set != NULL) {
+ *status_set = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ errno = 0;
+ status = fclose (pipe);
+ if (status != EOF) {
+ if (exit_status != NULL) {
+ *exit_status = status; /* might be overwritten below */
+ if (status_set != NULL) {
+ *status_set = true;
+ }
+ }
+ }
+
+ errno = 0;
+ while (waitpid (this->pid, &status, 0) == -1) {
+ if (errno != EINTR) {
+ status = -1;
+ break;
+ }
+ }
+ if (status != 0 && exit_status != NULL) {
+ *exit_status = status; /* might overwrite pipe status above */
+ if (status_set != NULL) {
+ *status_set = true;
+ }
+ }
}
pipe = NULL;