From: Philipp Gesang Date: Tue, 2 Jan 2018 16:55:28 +0000 (+0100) Subject: allow selecting stdout and stderr with inpipestream X-Git-Tag: v2.10~1^2~13 X-Git-Url: http://developer.intra2net.com/git/?a=commitdiff_plain;h=cc9178973e400af51877aaad5584f07ab63e5e8a;p=libi2ncommon allow selecting stdout and stderr with inpipestream Only handle the user-requested fd in the forked command and discard the other. --- diff --git a/src/pipestream.cpp b/src/pipestream.cpp index cd9347a..e026851 100644 --- a/src/pipestream.cpp +++ b/src/pipestream.cpp @@ -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 -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(command, res); } - -std::string capture_exec (const char *const *command, ExecResult &res) -{ return capture_exec(command, res); } - -std::string capture_exec (const std::vector &command, ExecResult &res) +{ return capture_exec(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(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 &command, ExecResult &res, + const bool out, const bool err) { - return capture_exec &>(command, res); + return capture_exec &> + (command, res, out, err); } #define PIPE_CTOR_FAIL(where) \ @@ -113,6 +160,18 @@ std::string capture_exec (const std::vector &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 mk_argv (const std::vector &command) { @@ -139,12 +198,28 @@ mk_argv (const std::vector &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 &command) +inpipebuf::inpipebuf(const std::vector &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 &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; diff --git a/src/pipestream.hxx b/src/pipestream.hxx index d13f312..fe9e8c0 100644 --- a/src/pipestream.hxx +++ b/src/pipestream.hxx @@ -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& 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& 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 &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 &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 &command) - : std::istream(&buf), buf(command) + inpipestream(const std::vector &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) diff --git a/test/test_pipestream.cpp b/test/test_pipestream.cpp index cf806c4..181fe23 100644 --- a/test/test_pipestream.cpp +++ b/test/test_pipestream.cpp @@ -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] */