allow path lookup for pipestream
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Wed, 3 Jan 2018 17:08:44 +0000 (18:08 +0100)
committerPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 14 Aug 2018 14:53:34 +0000 (16:53 +0200)
Add a flag ``path'' which, if set, causes the specified program
to be executed with execvpe(); also, the environment is passed
on so in this case (to propagate $PATH) so it is up to the caller
to sanitize envp beforehand.

src/pipestream.cpp
src/pipestream.hxx
test/test_pipestream.cpp

index eeca4f2..66ee39b 100644 (file)
@@ -44,11 +44,15 @@ on this file might be covered by the GNU General Public License.
 /** @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)
 {
     std::string output;
 
@@ -62,7 +66,7 @@ std::string capture_exec(CmdT command, ExecResult &rescode,
     try
     {
         {
-            inpipestream ips(command, out, err);
+            inpipestream ips(command, out, err, path);
 
             ips.store_exit_status(&exit_set, &exit_status_waitpid);
 
@@ -110,7 +114,7 @@ std::string capture_exec(CmdT command, ExecResult &rescode,
  *                    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); }
 
 /** @brief      Instantiation of \c capture_exec for argument lists. The
  *              pipestream used to run the command will not shell out.
@@ -124,12 +128,13 @@ std::string capture_exec (const std::string &command, ExecResult &res)
  *                    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)
+{ 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.
@@ -142,14 +147,15 @@ std::string capture_exec (const char *const *command, ExecResult &res,
  *                    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)
 {
     return capture_exec<const std::vector<std::string> &>
-        (command, res, out, err);
+        (command, res, out, err, path);
 }
 
 #define PIPE_CTOR_FAIL(where) \
@@ -197,7 +203,6 @@ mk_argv (const std::vector<std::string> &command)
     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).
@@ -210,7 +215,8 @@ mk_argv (const std::vector<std::string> &command)
 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
 {
     FILE *pipeobj = NULL;
     int pipefd [2];
@@ -238,21 +244,26 @@ inpipebuf::init_without_shell (const char *const *argv,
             if (!out) {
                 close (STDOUT_FILENO);
             } else if (dup2 (pipefd[1], STDOUT_FILENO) == -1) {
-                PIPE_CTOR_FAIL("dup2/stdout");
+                fprintf(stderr, "dup2/stdout: %m\n");
+                exit(EXIT_FAILURE);
             }
 
             if (!err) {
                 close (STDERR_FILENO);
             } else if (dup2 (pipefd[1], STDERR_FILENO) == -1) {
-                PIPE_CTOR_FAIL("dup2/stderr");
+                fprintf(stderr, "dup2/stderr: %m\n");
+                exit(EXIT_FAILURE);
             }
 
             close (pipefd [1]);
 
             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), environ);
+            } else {
+                execve (argv [0], const_cast <char *const *>(argv), NULL);
             }
+            exit(EXIT_FAILURE);
             break;
         }
         default: {
@@ -271,7 +282,8 @@ inpipebuf::init_without_shell (const char *const *argv,
 
 inpipebuf::inpipebuf(const char *const *command,
                      const bool out,
-                     const bool err)
+                     const bool err,
+                     const bool path)
     : pipe (NULL) /* brr: shadowing global ident */
     , pid (-1)
     , status_set (NULL)
@@ -281,7 +293,8 @@ inpipebuf::inpipebuf(const char *const *command,
         PIPE_CTOR_FAIL("command");
     }
 
-    std::pair <pid_t, FILE *> tmp = this->init_without_shell (command, out, err);
+    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;
 
@@ -290,7 +303,8 @@ inpipebuf::inpipebuf(const char *const *command,
 
 inpipebuf::inpipebuf(const std::vector<std::string> &command,
                      const bool out,
-                     const bool err)
+                     const bool err,
+                     const bool path)
     : pipe (NULL) /* brr: shadowing global ident */
     , pid (-1)
     , status_set (NULL)
@@ -305,7 +319,8 @@ inpipebuf::inpipebuf(const std::vector<std::string> &command,
         PIPE_CTOR_FAIL("malloc");
     }
 
-    std::pair <pid_t, FILE *> tmp = this->init_without_shell (argv.get (), out, err);
+    std::pair <pid_t, FILE *> tmp =
+        this->init_without_shell (argv.get (), out, err, path);
     this->pid  = tmp.first;
     this->pipe = tmp.second;
 
@@ -314,7 +329,8 @@ inpipebuf::inpipebuf(const std::vector<std::string> &command,
 
 inpipebuf::inpipebuf(const std::string& command,
                      const bool _ignored_out,
-                     const bool _ignored_err)
+                     const bool _ignored_err,
+                     const bool _ignored_path)
     : pid (-1)
     , status_set (NULL)
     , exit_status (NULL)
index 4b645ef..f53f474 100644 (file)
@@ -29,6 +29,7 @@ on this file might be covered by the GNU General Public License.
 
 #include <stdio.h>
 
+#include <cstring>
 #include <string>
 #include <streambuf>
 #include <istream>
@@ -69,9 +70,11 @@ typedef struct ExecResult ExecResult;
 
 std::string capture_exec(const std::string& command, ExecResult &rescode);
 std::string capture_exec(const char *const *command, ExecResult &rescode,
-                         const bool out=true, const bool err=false);
+                         const bool out=true, const bool err=false,
+                         const bool path=false);
 std::string capture_exec(const std::vector<std::string>& command, ExecResult &rescode,
-                         const bool out=true, const bool err=false);
+                         const bool out=true, const bool err=false,
+                         const bool path=false);
 
 inline std::string capture_exec (const std::string &command)
 {
@@ -114,11 +117,11 @@ protected:
 
 public:
     inpipebuf(const std::string& command,
-              const bool out, const bool err);
+              const bool out, const bool err, const bool path);
     inpipebuf(const char *const *command,
-              const bool out, const bool err);
+              const bool out, const bool err, const bool path);
     inpipebuf(const std::vector<std::string> &command,
-              const bool out, const bool err);
+              const bool out, const bool err, const bool path);
 
     ~inpipebuf();
 
@@ -130,7 +133,8 @@ protected:
 private:
     std::pair <pid_t, FILE *>
     init_without_shell (const char *const *argv,
-                        const bool out, const bool err) const;
+                        const bool out, const bool err,
+                        const bool path) const;
 };
 
 /** @brief stream around inpipebuf -- see comment there */
@@ -141,18 +145,21 @@ protected:
 
 public:
     inpipestream(const std::string& command,
-                 const bool out=true, const bool err=false)
-            : std::istream(&buf), buf(command, out, err)
+                 const bool out=true, const bool err=false,
+                 const bool path=false)
+            : std::istream(&buf), buf(command, out, err, path)
     {}
 
     inpipestream(const char *const command[],
-                 const bool out=true, const bool err=false)
-            : std::istream(&buf), buf(command, out, err)
+                 const bool out=true, const bool err=false,
+                 const bool path=false)
+            : std::istream(&buf), buf(command, out, err, path)
     {}
 
     inpipestream(const std::vector<std::string> &command,
-                 const bool out=true, const bool err=false)
-            : std::istream(&buf), buf(command, out, err)
+                 const bool out=true, const bool err=false,
+                 const bool path=false)
+            : std::istream(&buf), buf(command, out, err, path)
     {}
 
     void store_exit_status(bool *_status_set, int *_exit_status)
index b361906..bf7b068 100644 (file)
@@ -53,7 +53,7 @@ BOOST_AUTO_TEST_SUITE(pipestream)
 
         BOOST_AUTO_TEST_CASE(abspath_zeros_shell_ok_result)
         {
-            ExecResult exres;
+            ExecResult exres = ExecResult ();
             const std::string result =
                     capture_exec (I2n::join_string (zero_bytes_argv, " "),
                                   exres);
@@ -88,7 +88,7 @@ BOOST_AUTO_TEST_SUITE(pipestream)
 
         BOOST_AUTO_TEST_CASE(abspath_zeros_noshell_ok_result)
         {
-            ExecResult exres;
+            ExecResult exres = ExecResult ();
             const std::string result = capture_exec (zero_bytes_argv, exres);
 
             BOOST_CHECK(exres.normal_exit);
@@ -103,7 +103,12 @@ BOOST_AUTO_TEST_SUITE(pipestream)
         {
             assert (access(bad_command [0], X_OK) != 0);
 
-            ExecResult exres;
+            ExecResult exres = ExecResult ();
+            /*
+             * Note that the next line will make the unit test spew a message
+             * to stderr which cannot be prevented due to the limitations of
+             * popen(3).
+             */
             const std::string result =
                     capture_exec (I2n::join_string (bad_command, " "));
 
@@ -114,7 +119,7 @@ BOOST_AUTO_TEST_SUITE(pipestream)
         {
             assert (access(bad_command [0], X_OK) != 0);
 
-            ExecResult exres;
+            ExecResult exres = ExecResult ();
             const std::string result = capture_exec (bad_command, exres);
 
             BOOST_CHECK(!exres.terminated_by_signal);
@@ -125,18 +130,23 @@ BOOST_AUTO_TEST_SUITE(pipestream)
         {
             assert (access(bad_command [0], X_OK) != 0);
 
-            ExecResult exres;
+            ExecResult exres = ExecResult ();
             const std::string result = capture_exec (bad_command, exres, false, true);
 
-            BOOST_CHECK_NE(result.size (), 0);
+            BOOST_CHECK(!exres.terminated_by_signal);
+            BOOST_CHECK_EQUAL(result.size (), 0);
         }
 
-        const char *const false_argv [] = { "/bin/false", NULL };
+        const char *const false_argv_abs [] = { "/bin/false", NULL };
+        const char *const true_argv_abs  [] = { "/bin/true" , NULL };
+        const char *const false_argv_rel [] = { "false"     , NULL };
+        const char *const true_argv_rel  [] = { "true"      , NULL };
 
         BOOST_AUTO_TEST_CASE(abspath_false_noshell_fail_exit)
         {
-            ExecResult exres;
-            const std::string result = capture_exec (false_argv, exres, true, false);
+            ExecResult exres = ExecResult ();
+            const std::string result =
+                capture_exec (false_argv_abs, exres, true, false, false);
 
             BOOST_CHECK(exres.normal_exit);
             BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
@@ -145,15 +155,59 @@ BOOST_AUTO_TEST_SUITE(pipestream)
 
         BOOST_AUTO_TEST_CASE(abspath_false_shell_fail_exit)
         {
-            ExecResult exres;
+            ExecResult exres = ExecResult ();
             const std::string result =
-                    capture_exec (std::string (false_argv [0]), exres);
+                    capture_exec (std::string (false_argv_abs [0]), exres);
 
             BOOST_CHECK(exres.normal_exit);
             BOOST_CHECK_EQUAL(exres.return_code, EXIT_FAILURE);
             BOOST_CHECK_EQUAL(result.size (), 0);
         }
 
+        BOOST_AUTO_TEST_CASE(relpath_true_noshell_ok)
+        {
+            ExecResult exres = ExecResult ();
+            const std::string result =
+                capture_exec (true_argv_rel, exres, true, false, true);
+
+            BOOST_CHECK(exres.normal_exit);
+            BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+            BOOST_CHECK_EQUAL(result.size (), 0);
+        }
+
+        BOOST_AUTO_TEST_CASE(relpath_true_noshell_fail)
+        {
+            ExecResult exres = ExecResult ();
+            const std::string result =
+                capture_exec (true_argv_rel, exres, true, false, false);
+
+            BOOST_CHECK(exres.normal_exit);
+            /* no return code check since we couln't exit */
+            BOOST_CHECK_EQUAL(result.size (), 0);
+        }
+
+        BOOST_AUTO_TEST_CASE(abspath_true_noshell_ok)
+        {
+            ExecResult exres = ExecResult ();
+            const std::string result =
+                capture_exec (true_argv_abs, exres, true, false, true);
+
+            BOOST_CHECK(exres.normal_exit);
+            BOOST_CHECK_EQUAL(exres.return_code, EXIT_SUCCESS);
+            BOOST_CHECK_EQUAL(result.size (), 0);
+        }
+
+        BOOST_AUTO_TEST_CASE(relpath_false_noshell_fail)
+        {
+            ExecResult exres = ExecResult ();
+            const std::string result =
+                capture_exec (false_argv_rel, exres, true, false, true);
+
+            BOOST_CHECK(exres.normal_exit);
+            /* no return code check since we couln't exit */
+            BOOST_CHECK_EQUAL(result.size (), 0);
+        }
+
     BOOST_AUTO_TEST_SUITE_END() /* [pipestream->read] */
 
 BOOST_AUTO_TEST_SUITE_END() /* [pipestream] */