add shell-free pipestream
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Fri, 9 Feb 2018 13:21:41 +0000 (14:21 +0100)
committerPhilipp Gesang <philipp.gesang@intra2net.com>
Mon, 13 Aug 2018 13:13:39 +0000 (15:13 +0200)
Overload the pipestream ctor with a variant that avoids shelling
out with *popen(3)* that is chosen by passing an argument list
in lieu of a command.

src/pipestream.cpp
src/pipestream.hxx

index afa6d56..ebcc6c2 100644 (file)
@@ -24,16 +24,21 @@ on this file might be covered by the GNU General Public License.
     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"
 
 /** @brief runs command and returns it's output as string
@@ -41,7 +46,8 @@ on this file might be covered by the GNU General Public License.
  *  @param rescode struct containing the return code, if the program exited normally and so on
  *  @returns the output (stdout) of the called program
  */
-std::string capture_exec(const std::string& command, ExecResult &rescode)
+template <typename CmdT>
+std::string capture_exec(CmdT command, ExecResult &rescode)
 {
     std::string output;
 
@@ -88,6 +94,109 @@ std::string capture_exec(const std::string& command, ExecResult &rescode)
     return output;
 }
 
+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); }
+
+#define PIPE_CTOR_FAIL(where) \
+    do { \
+        throw EXCEPTION (pipestream_error, \
+                         std::string (where) + ": error " \
+                         + I2n::to_string (errno) \
+                         + " (" + std::string (strerror (errno)) + ")"); \
+    } while (0)
+
+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);
+}
+
+
+FILE *
+inpipebuf::init_without_shell (const char *const *argv) const
+{
+    FILE *pipeobj = NULL;
+    int pipefd [2];
+
+    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 (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
+                PIPE_CTOR_FAIL("dup2");
+            }
+
+            if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
+                PIPE_CTOR_FAIL("dup2");
+            }
+
+            errno = 0;
+            if (execve (argv [0], const_cast <char *const *>(argv), NULL) == -1) {
+                PIPE_CTOR_FAIL("exec");
+            }
+            break;
+        }
+        default: {
+            close (pipefd [1]);
+
+            errno = 0;
+            if ((pipeobj = fdopen (pipefd [0], "r")) == NULL) {
+                PIPE_CTOR_FAIL("fdopen");
+            }
+            break;
+        }
+    }
+
+    return pipeobj;
+}
+
+inpipebuf::inpipebuf(const char *const *command)
+    : pipe (NULL) /* brr: shadowing global ident */
+    , status_set (NULL)
+    , exit_status (NULL)
+{
+    if (command == NULL || command [0] == NULL) {
+        PIPE_CTOR_FAIL("command");
+    }
+
+    this->pipe = this->init_without_shell (command);
+
+    setg (&buffer, &buffer, &buffer);
+}
+
 inpipebuf::inpipebuf(const std::string& command)
 {
     status_set = NULL;
index 9280859..6f83006 100644 (file)
@@ -54,8 +54,15 @@ 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);
 
-inline std::string capture_exec(const std::string& command)
+inline std::string capture_exec (const std::string &command)
+{
+    ExecResult r;
+    return capture_exec(command,r);
+}
+
+inline std::string capture_exec(const char *const *command)
 {
     ExecResult r;
     return capture_exec(command,r);
@@ -74,6 +81,7 @@ class inpipebuf : public std::streambuf
 {
 protected:
     char buffer;
+
     FILE *pipe;
 
     // "callback" variables for destructor to store exit status
@@ -82,6 +90,7 @@ protected:
 
 public:
     inpipebuf(const std::string& command);
+    inpipebuf(const char *const *command);
 
     ~inpipebuf();
 
@@ -89,6 +98,9 @@ public:
 
 protected:
     virtual int_type underflow();
+
+private:
+    FILE *init_without_shell (const char *const *argv) const;
 };
 
 /** @brief stream around inpipebuf -- see comment there */
@@ -101,6 +113,9 @@ public:
     inpipestream(const std::string& command)
             : std::istream(&buf), buf(command)
     {}
+    inpipestream(const char *const command[])
+            : std::istream(&buf), buf(command)
+    {}
 
     void store_exit_status(bool *_status_set, int *_exit_status)
     { buf.store_exit_status(_status_set, _exit_status); }