copyright : (C) 2001 by Intra2net AG
***************************************************************************/
+#include <errno.h>
#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
-#include <string>
#include <streambuf>
#include <istream>
#include <ostream>
#include <cstdio>
+#include <boost/foreach.hpp>
+#include <boost/shared_array.hpp>
#include "exception.hxx"
+#include "stringfunc.hxx"
#include "pipestream.hxx"
-inpipebuf::inpipebuf(const std::string& command)
+/** @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 path)
{
- status_set = NULL;
- exit_status = NULL;
+ std::string output;
+
+ bool exit_set = false;
+ int exit_status_waitpid;
+
+ // set the results to false until we are sure we have proper values
+ rescode.normal_exit = false;
+ rescode.terminated_by_signal = false;
+
+ try
+ {
+ {
+ inpipestream ips(command, out, err, path);
+
+ ips.store_exit_status(&exit_set, &exit_status_waitpid);
+
+ char buffer[2048];
+ while (ips.good())
+ {
+ ips.read(buffer, sizeof(buffer));
+ output.append(buffer, ips.gcount());
+ }
+ }
+
+ // exit_status_waitpid only valid after destruction of the inpipestream
+
+ if (exit_set)
+ {
+ rescode.normal_exit = WIFEXITED(exit_status_waitpid);
+ if (rescode.normal_exit)
+ rescode.return_code = WEXITSTATUS(exit_status_waitpid);
+
+ rescode.terminated_by_signal = WIFSIGNALED(exit_status_waitpid);
+ if (rescode.terminated_by_signal)
+ rescode.signal = WTERMSIG(exit_status_waitpid);
+ }
+ }
+ catch (pipestream_error &e)
+ {
+ rescode.error_message = e.what();
+ }
+
+ return output;
+}
+
+/** @brief Instantiation of \c capture_exec for STL string arguments.
+ * Caveat emptor: this will cause the backing stream to use \c
+ * popen(3). To avoid shelling out, please refer to one of the
+ * variants that allow passing an argument list.
+ *
+ * @param command String specifying the shell expression to be executed.
+ * @param res (Out parameter) Store information about the termination
+ * state in this struct.
+ *
+ * @returns Result of \c stdout. Note that due to the use of \c
+ * popen, the correct way to collect stderr output as
+ * well is to use shell redirection inside the expression
+ * passed.
+ */
+std::string capture_exec (const std::string &command, ExecResult &res)
+{ return capture_exec<const std::string &>(command, res, true, false, false); }
+
+/** @brief Instantiation of \c capture_exec for argument lists. The
+ * pipestream used to run the command will not shell out.
+ * One of \c out or \c err must be set.
+ *
+ * @param command List of \c char* specifying the \c argv array of the
+ * command to run. Note that the binary to executed is
+ * assumed to be present at index 0 and that the input
+ * is properly \c NULL terminated.
+ * @param res (Out parameter) Store information about the termination
+ * 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, const bool path)
+{ return capture_exec<const char *const *>(command, res, out, err, path); }
+
+/** @brief Instantiation of \c capture_exec for argument lists. The
+ * pipestream used to run the command will not shell out.
+ * One of \c out or \c err must be set.
+ *
+ * @param command String vector specifying the \c argv array of the
+ * command to run. Note that the binary to executed is
+ * assumed to be present at index 0.
+ * @param res (Out parameter) Store information about the termination
+ * 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 path)
+{
+ return capture_exec<const std::vector<std::string> &>
+ (command, res, out, err, path);
+}
+
+#define PIPE_CTOR_FAIL(where) \
+ do { \
+ throw EXCEPTION (pipestream_error, \
+ std::string (where) + ": error " \
+ + I2n::to_string (errno) \
+ + " (" + std::string (strerror (errno)) + ")"); \
+ } while (0)
+
+/** @brief Convert a string vector to a refcounted \c char**
+ * that is \c NULL terminated for use with e. g. \c execve(2).
+ *
+ * @param command List of arguments including the binary at index 0.
+ *
+ * @returns A \c boost::shared_array of pointers to the
+ * arguments plus a trailing \c NULL. Note that
+ * while the array itself is refcounted, the
+ * pointees are assumed owned by the caller and
+ * *not copyied*. I. e. they lose validity if the
+ * original strings are freed.
+ */
+static boost::shared_array <char *>
+mk_argv (const std::vector<std::string> &command)
+{
+ char **ret = NULL;
+
+ try {
+ ret = new char *[command.size () * sizeof (ret[0]) + 1];
+ } catch (std::bad_alloc &) {
+ return boost::shared_array<char *> ();
+ }
+
+ size_t cur = 0;
+ BOOST_FOREACH(const std::string &arg, command) {
+ /*
+ * Casting away constness is safe since the data is always
+ * kept alive until after exec().
+ */
+ ret [cur++] = const_cast<char *> (arg.c_str ());
+ }
+
+ ret [cur] = NULL;
+
+ return boost::shared_array<char *> (ret);
+}
+
+/** @brief Helper aggregating common code for the shell-free ctors.
+ *
+ * @param argv Argument list prepared for \c execve(2).
+ * @param out Whether to capture \c stdout.
+ * @param err Whether to capture \c stderr.
+ *
+ * @returns A \c FILE* handle for streaming if successful, \c NULL
+ * otherwise.
+ */
+std::pair <pid_t, FILE *>
+inpipebuf::init_without_shell (const char *const *argv,
+ const bool out,
+ const bool err,
+ const bool path) const
+{
+ FILE *pipeobj = NULL;
+ int pipefd [2];
+
+ if (!out && !err) {
+ errno = EINVAL;
+ PIPE_CTOR_FAIL("ctor");
+ }
+
+ errno = 0;
+ if (::pipe (pipefd) == -1) {
+ PIPE_CTOR_FAIL("pipe");
+ }
+
+ errno = 0;
+ pid_t childpid = fork ();
+ switch (childpid) {
+ case -1: {
+ PIPE_CTOR_FAIL("fork");
+ break;
+ }
+ case 0: {
+ close (pipefd [0]);
+
+ if (!out) {
+ close (STDOUT_FILENO);
+ } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
+ fprintf(stderr, "dup2/stdout: %m\n");
+ exit(EXIT_FAILURE);
+ }
+ if (!err) {
+ close (STDERR_FILENO);
+ } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
+ fprintf(stderr, "dup2/stderr: %m\n");
+ exit(EXIT_FAILURE);
+ }
+
+ close (pipefd [1]);
+
+ errno = 0;
+ if (path) {
+ execvpe (argv [0], const_cast <char *const *>(argv), environ);
+ } else {
+ execve (argv [0], const_cast <char *const *>(argv), NULL);
+ }
+ exit(EXIT_FAILURE);
+ break;
+ }
+ default: {
+ close (pipefd [1]);
+
+ errno = 0;
+ if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
+ PIPE_CTOR_FAIL("fdopen");
+ }
+ break;
+ }
+ }
+
+ return std::make_pair (childpid, pipeobj);
+}
+
+inpipebuf::inpipebuf(const char *const *command,
+ const bool out,
+ const bool err,
+ const bool path)
+ : pipe (NULL) /* brr: shadowing global ident */
+ , pid (-1)
+ , status_set (NULL)
+ , exit_status (NULL)
+{
+ if (command == NULL || command [0] == NULL) {
+ PIPE_CTOR_FAIL("command");
+ }
+
+ std::pair <pid_t, FILE *> tmp =
+ this->init_without_shell (command, out, err, path);
+ 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 path)
+ : pipe (NULL) /* brr: shadowing global ident */
+ , pid (-1)
+ , status_set (NULL)
+ , exit_status (NULL)
+{
+ if (command.empty ()) {
+ PIPE_CTOR_FAIL("command");
+ }
+
+ const boost::shared_array <char *> argv = mk_argv (command);
+ if (!argv) {
+ PIPE_CTOR_FAIL("malloc");
+ }
+
+ std::pair <pid_t, FILE *> tmp =
+ this->init_without_shell (argv.get (), out, err, path);
+ 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_path)
+ : pid (-1)
+ , 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;
return traits_type::to_int_type(*gptr());
}
-/** @brief runs command and returns it's output as string
- * @param command the full command with all parameters
- * @param exit_status the full exit status, use WEXITSTATUS to get the "regular" return code
- * @returns the output (stderr) of the called program
- */
-std::string pipe_to_string(const std::string& command, std::string *error, int *_exit_status)
-{
- std::string result;
- bool exit_set;
-
- try
- {
- inpipestream ips(command);
-
- ips.store_exit_status(&exit_set, _exit_status);
-
- char buffer[2048];
- while (ips.good())
- {
- ips.read(buffer, sizeof(buffer));
- result.append(buffer, ips.gcount());
- }
- }
- catch (pipestream_error &e)
- {
- if (error)
- *error=e.what();
- return "";
- }
- catch(...)
- {
- throw;
- }
-
- return result;
-}
-
outpipebuf::outpipebuf(const std::string& command)
{
status_set = NULL;