allow selecting stdout and stderr with inpipestream
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 2 Jan 2018 16:55:28 +0000 (17:55 +0100)
committerPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 14 Aug 2018 14:53:34 +0000 (16:53 +0200)
Only handle the user-requested fd in the forked command and
discard the other.

src/pipestream.cpp
src/pipestream.hxx
test/test_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;
index d13f312..fe9e8c0 100644 (file)
@@ -55,8 +55,10 @@ struct ExecResult
 typedef struct ExecResult ExecResult;
 
 std::string capture_exec(const std::string& command, ExecResult &rescode);
-std::string capture_exec(const char *const *command, ExecResult &rescode);
-std::string capture_exec(const std::vector<std::string>& command, ExecResult &rescode);
+std::string capture_exec(const char *const *command, ExecResult &rescode,
+                         const bool out=true, const bool err=false);
+std::string capture_exec(const std::vector<std::string>& command, ExecResult &rescode,
+                         const bool out=true, const bool err=false);
 
 inline std::string capture_exec (const std::string &command)
 {
@@ -97,9 +99,12 @@ protected:
     int *exit_status;
 
 public:
-    inpipebuf(const std::string& command);
-    inpipebuf(const char *const *command);
-    inpipebuf(const std::vector<std::string> &command);
+    inpipebuf(const std::string& command,
+              const bool out, const bool err);
+    inpipebuf(const char *const *command,
+              const bool out, const bool err);
+    inpipebuf(const std::vector<std::string> &command,
+              const bool out, const bool err);
 
     ~inpipebuf();
 
@@ -109,7 +114,8 @@ protected:
     virtual int_type underflow();
 
 private:
-    FILE *init_without_shell (const char *const *argv) const;
+    FILE *init_without_shell (const char *const *argv,
+                              const bool out, const bool err) const;
 };
 
 /** @brief stream around inpipebuf -- see comment there */
@@ -119,15 +125,19 @@ protected:
     inpipebuf buf;
 
 public:
-    inpipestream(const std::string& command)
-            : std::istream(&buf), buf(command)
+    inpipestream(const std::string& command,
+                 const bool out=true, const bool err=false)
+            : std::istream(&buf), buf(command, out, err)
     {}
-    inpipestream(const char *const command[])
-            : std::istream(&buf), buf(command)
+
+    inpipestream(const char *const command[],
+                 const bool out=true, const bool err=false)
+            : std::istream(&buf), buf(command, out, err)
     {}
 
-    inpipestream(const std::vector<std::string> &command)
-            : std::istream(&buf), buf(command)
+    inpipestream(const std::vector<std::string> &command,
+                 const bool out=true, const bool err=false)
+            : std::istream(&buf), buf(command, out, err)
     {}
 
     void store_exit_status(bool *_status_set, int *_exit_status)
index cf806c4..181fe23 100644 (file)
@@ -96,6 +96,41 @@ BOOST_AUTO_TEST_SUITE(pipestream)
             BOOST_CHECK_EQUAL(result.size (), ENOUGH_ZEROS);
         }
 
+        const char *const bad_command [] = { "/does_not_exist", NULL };
+
+        BOOST_AUTO_TEST_CASE(abspath_zeros_shell_fail)
+        {
+            assert (access(bad_command [0], X_OK) != 0);
+
+            ExecResult exres;
+            const std::string result =
+                    capture_exec (I2n::join_string (bad_command, " "));
+
+            BOOST_CHECK_EQUAL(result.size (), 0);
+        }
+
+        BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_fail)
+        {
+            assert (access(bad_command [0], X_OK) != 0);
+
+            ExecResult exres;
+            const std::string result = capture_exec (bad_command, exres);
+
+            BOOST_CHECK(!exres.normal_exit);
+            BOOST_CHECK(!exres.terminated_by_signal);
+            BOOST_CHECK_EQUAL(result.size (), 0);
+        }
+
+        BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_stderr)
+        {
+            assert (access(bad_command [0], X_OK) != 0);
+
+            ExecResult exres;
+            const std::string result = capture_exec (bad_command, exres, false, true);
+
+            BOOST_CHECK_NE(result.size (), 0);
+        }
+
     BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
 
 BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */