From ad4490f1cee6c48f81af20b691e2784b00e25286 Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Wed, 3 Jan 2018 18:08:44 +0100 Subject: [PATCH] allow path lookup for pipestream 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 | 52 ++++++++++++++++++++----------- src/pipestream.hxx | 31 +++++++++++------- test/test_pipestream.cpp | 76 +++++++++++++++++++++++++++++++++++++++------- 3 files changed, 118 insertions(+), 41 deletions(-) diff --git a/src/pipestream.cpp b/src/pipestream.cpp index eeca4f2..66ee39b 100644 --- a/src/pipestream.cpp +++ b/src/pipestream.cpp @@ -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 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(command, res, true, false); } +{ return capture_exec(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(command, res, out, err); } + const bool out, const bool err, const bool path) +{ return capture_exec(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 &command, ExecResult &res, - const bool out, const bool err) + const bool out, const bool err, const bool path) { return capture_exec &> - (command, res, out, err); + (command, res, out, err, path); } #define PIPE_CTOR_FAIL(where) \ @@ -197,7 +203,6 @@ mk_argv (const std::vector &command) return boost::shared_array (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 &command) std::pair 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 (argv), NULL) == -1) { - PIPE_CTOR_FAIL("exec"); + if (path) { + execvpe (argv [0], const_cast (argv), environ); + } else { + execve (argv [0], const_cast (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 tmp = this->init_without_shell (command, out, err); + std::pair 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 &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 &command, PIPE_CTOR_FAIL("malloc"); } - std::pair tmp = this->init_without_shell (argv.get (), out, err); + std::pair 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 &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) diff --git a/src/pipestream.hxx b/src/pipestream.hxx index 4b645ef..f53f474 100644 --- a/src/pipestream.hxx +++ b/src/pipestream.hxx @@ -29,6 +29,7 @@ on this file might be covered by the GNU General Public License. #include +#include #include #include #include @@ -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& 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 &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 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 &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) diff --git a/test/test_pipestream.cpp b/test/test_pipestream.cpp index b361906..bf7b068 100644 --- a/test/test_pipestream.cpp +++ b/test/test_pipestream.cpp @@ -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] */ -- 1.7.1