allow selecting stdout and stderr with inpipestream
[libi2ncommon] / src / pipestream.cpp
index cd9347a..e026851 100644 (file)
@@ -47,7 +47,8 @@ on this file might be covered by the GNU General Public License.
  *  @returns the output (stdout) of the called program
  */
 template <typename CmdT>
-std::string capture_exec(CmdT command, ExecResult &rescode)
+std::string capture_exec(CmdT command, ExecResult &rescode,
+                         const bool out, const bool err)
 {
     std::string output;
 
@@ -61,7 +62,7 @@ std::string capture_exec(CmdT command, ExecResult &rescode)
     try
     {
         {
-            inpipestream ips(command);
+            inpipestream ips(command, out, err);
 
             ips.store_exit_status(&exit_set, &exit_status_waitpid);
 
@@ -94,15 +95,61 @@ std::string capture_exec(CmdT command, ExecResult &rescode)
     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); }
-
-std::string capture_exec (const char *const *command, ExecResult &res)
-{ return capture_exec<const char *const *>(command, res); }
-
-std::string capture_exec (const std::vector<std::string> &command, ExecResult &res)
+{ return capture_exec<const std::string &>(command, res, true, 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.
+ *
+ *  @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); }
+
+/** @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.
+ *
+ *  @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)
 {
-    return capture_exec<const std::vector<std::string> &>(command, res);
+    return capture_exec<const std::vector<std::string> &>
+        (command, res, out, err);
 }
 
 #define PIPE_CTOR_FAIL(where) \
@@ -113,6 +160,18 @@ std::string capture_exec (const std::vector<std::string> &command, ExecResult &r
                          + " (" + 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)
 {
@@ -139,12 +198,28 @@ mk_argv (const std::vector<std::string> &command)
 }
 
 
+/** @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.
+ */
 FILE *
-inpipebuf::init_without_shell (const char *const *argv) const
+inpipebuf::init_without_shell (const char *const *argv,
+                               const bool out,
+                               const bool err) 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");
@@ -160,12 +235,16 @@ inpipebuf::init_without_shell (const char *const *argv) const
         case 0: {
             close (pipefd [0]);
 
-            if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
-                PIPE_CTOR_FAIL("dup2");
+            if (!out) {
+                close (STDOUT_FILENO);
+            } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
+                PIPE_CTOR_FAIL("dup2/stdout");
             }
 
-            if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
-                PIPE_CTOR_FAIL("dup2");
+            if (!err) {
+                close (STDERR_FILENO);
+            } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
+                PIPE_CTOR_FAIL("dup2/stderr");
             }
 
             errno = 0;
@@ -188,7 +267,9 @@ inpipebuf::init_without_shell (const char *const *argv) const
     return pipeobj;
 }
 
-inpipebuf::inpipebuf(const char *const *command)
+inpipebuf::inpipebuf(const char *const *command,
+                     const bool out,
+                     const bool err)
     : pipe (NULL) /* brr: shadowing global ident */
     , status_set (NULL)
     , exit_status (NULL)
@@ -197,12 +278,14 @@ inpipebuf::inpipebuf(const char *const *command)
         PIPE_CTOR_FAIL("command");
     }
 
-    this->pipe = this->init_without_shell (command);
+    this->pipe = this->init_without_shell (command, out, err);
 
     setg (&buffer, &buffer, &buffer);
 }
 
-inpipebuf::inpipebuf(const std::vector<std::string> &command)
+inpipebuf::inpipebuf(const std::vector<std::string> &command,
+                     const bool out,
+                     const bool err)
     : pipe (NULL) /* brr: shadowing global ident */
     , status_set (NULL)
     , exit_status (NULL)
@@ -216,12 +299,14 @@ inpipebuf::inpipebuf(const std::vector<std::string> &command)
         PIPE_CTOR_FAIL("malloc");
     }
 
-    this->pipe = this->init_without_shell (argv.get ());
+    this->pipe = this->init_without_shell (argv.get (), out, err);
 
     setg (&buffer, &buffer, &buffer);
 }
 
-inpipebuf::inpipebuf(const std::string& command)
+inpipebuf::inpipebuf(const std::string& command,
+                     const bool _ignored_out,
+                     const bool _ignored_err)
 {
     status_set = NULL;
     exit_status = NULL;