diff --git a/mrw/exec.cpp b/mrw/exec.cpp index f3fe3c5..7eec02a 100644 --- a/mrw/exec.cpp +++ b/mrw/exec.cpp @@ -9,6 +9,9 @@ @license LGPL, see file COPYING $Log$ + Revision 1.5 2004/12/14 20:30:09 marc + added possibility to pass string to stdin of child process + Revision 1.4 2004/08/28 16:21:25 marc mrw-c++-0.92 (mrw) - new file: version.cpp @@ -26,9 +29,11 @@ #include #include #include +#include // max #include // waitpid #include // fork, exec #include // memcpy +#include // assert mrw::ExecutionFailedExc::ExecutionFailedExc(const std::string& w, const std::string& c) @@ -73,8 +78,13 @@ mrw::Cmd::operator mrw::Exec() const throw(std::bad_exception) { return mrw::Exec(*this); } -mrw::Exec mrw::Cmd::execute(bool throwExc) const throw(std::exception) { - return mrw::Exec(*this).execute(throwExc); +mrw::Exec mrw::Cmd::execute(bool exc) const throw(std::exception) { + return mrw::Exec(*this).execute(exc); +} + +mrw::Exec mrw::Cmd::execute(const std::string& input, bool exc) const + throw(std::exception) { + return mrw::Exec(*this).execute(input, exc); } const char* mrw::Cmd::path() const throw(std::bad_exception) { @@ -109,10 +119,11 @@ mrw::Exec& mrw::Exec::operator=(const mrw::Exec& e) throw(std::bad_exception) { return *this; } -mrw::Exec& mrw::Exec::execute(bool throwExc) throw(std::exception) { +mrw::Exec& mrw::Exec::execute(bool exc) throw(std::exception) { /** This method calls @c fork, sets up a pipe connection to pass @c - stdout and @c stderr from the child process to the parent process - using mrw::Pipe and calls @c execvp to execute the program. */ + stdout and @c stderr from the child process to the parent + process using mrw::Pipe and calls @c execvp to execute the + program. */ _success = false; _res = _err = ""; mrw::Pipe stdOut, stdErr; @@ -128,17 +139,18 @@ mrw::Exec& mrw::Exec::execute(bool throwExc) throw(std::exception) { throw ExecutionFailedExc("cannot close pipe", *_cmd); int num1(0), num2(0); for (char buf1[4096], buf2[4096]; - (num1=read(stdOut.istream(), buf1, sizeof(buf1)))>0 || - num1==-1 && errno==EINTR || - (num2=read(stdErr.istream(), buf2, sizeof(buf2)))>0 || - num2==-1 && errno==EINTR; - _res += std::string(buf1, num1), _err += std::string(buf2, num2)); - if (num1==-1 || num2==-1) - throw ExecutionFailedExc("cannot_ read pipe", *_cmd); + (num1=read(stdOut.istream(), buf1, sizeof(buf1)))>0 + || num1==-1 && (errno==EINTR||errno==EAGAIN) + || (num2=read(stdErr.istream(), buf2, sizeof(buf2)))>0 + || num2==-1 && (errno==EINTR||errno==EAGAIN);) { + if (num1>0) _res += std::string(buf1, num1); + if (num2>0) _err += std::string(buf2, num2); + } // wait for child to get return code int s(0); - if (waitpid(pid, &s, 0)!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) { - if (throwExc) { + if (waitpid(pid, &s, 0)!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0 + || num1==-1 || num2==-1) { + if (exc) { throw ExecutionFailedExc("execution failed", *_cmd); } else { _success = false; @@ -157,6 +169,106 @@ mrw::Exec& mrw::Exec::execute(bool throwExc) throw(std::exception) { return *this; } +mrw::Exec& mrw::Exec::execute(const std::string& input, bool exc) + throw(std::exception) { + /// @c input length must be smaller than @c SSIZE_MAX. + /// I'll only add support for longer strings upon request. + assert(input.size()<=SSIZE_MAX && + "sdin input exeeds C library limit in mrw::Exec " + "please contact the author of the library"); + /** This method calls @c fork, sets up a pipe connection to pass @c + stdin, @c stdout and @c stderr from the child process to the + parent process using mrw::Pipe and calls @c execvp to execute + the program. */ + _success = false; + _res = _err = ""; + mrw::Pipe stdIn(true), // child terminates if stdin is non-blocking + stdOut, stdErr; + if (!stdIn || !stdOut || !stdErr) + throw mrw::ExecutionFailedExc("cannot create pipe", *_cmd); + pid_t pid(fork()); + if (pid<0) throw ExecutionFailedExc("cannot fork", *_cmd); + if (pid) { // parent + stdIn.close_in(); + stdOut.close_out(); + stdErr.close_out(); + if (!stdIn || !stdOut || !stdErr) + throw ExecutionFailedExc("cannot close pipes", *_cmd); + ssize_t num0(-1), num1(-1), num2(-1); + std::string in(input); + fd_set writefds, readfds; + char buf[4096]; + while (num0 || num1 || num2) try { // no end of file in stdin, -out, -err + int res(0), s(0); + if ((res=waitpid(pid, &s, WNOHANG))) { + if (res!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) + throw ExecutionFailedExc("execution failed", *_cmd); + _success = true; + return *this; + } + // select... + FD_ZERO(&writefds); + FD_ZERO(&readfds); + if (num0) FD_SET(stdIn.ostream(), &writefds); + if (num1) FD_SET(stdOut.istream(), &readfds); + if (num2) FD_SET(stdErr.istream(), &readfds); + timeval tm = {1, 0}; + int n(mrw::max((num0?stdIn.ostream():0), + mrw::max((num1?stdOut.istream():0), + (num2?stdErr.istream():0)))); + int num = select(n, &readfds, &writefds, 0, &tm); + if (num<0) throw ExecutionFailedExc("select failed", *_cmd); + // check and handle stdin + if (num0 && num>0 + && FD_ISSET(stdIn.ostream(), &writefds)) + if ((num0=write(stdIn.ostream(), in.c_str(), in.size()))>0) { + if ((unsigned int)num00 && FD_ISSET(stdOut.istream(), &readfds)) + if ((num1=read(stdOut.istream(), buf, sizeof(buf)))>0) + _res += std::string(buf, num1); + else if (num1==-1 && (errno!=EINTR&&errno!=EAGAIN)) + throw ExecutionFailedExc("readin stdout", *_cmd); + // check and handle stderr + if (num>0 && FD_ISSET(stdErr.istream(), &readfds)) + if ((num2=read(stdErr.istream(), buf, sizeof(buf)))>0) + _err += std::string(buf, num2); + else if (num2==-1 && (errno!=EINTR&&errno!=EAGAIN)) + throw ExecutionFailedExc("readin stderr", *_cmd); + } catch (...) { + if (exc) throw; + _success = false; + return *this; + } + // wait for child to get return code + int s(0); + if (waitpid(pid, &s, 0)!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) { + if (exc) { + throw ExecutionFailedExc("execution failed", *_cmd); + } else { + _success = false; + return *this; + } + } + } else { // child + stdIn.close_out(); + stdOut.close_in(); + stdErr.close_in(); + stdIn.connect_cin(); + stdOut.connect_cout(); // if cin is non blocking, child terminates here?!? + stdErr.connect_cerr(); + execvp(_cmd->path(), _cmd->args()); + exit(1); // execute failed + } + _success = true; + return *this; +} + mrw::Exec& mrw::Exec::operator>>(std::string& res) throw(std::exception) { execute(); res += _res; diff --git a/mrw/exec.hpp b/mrw/exec.hpp index b0ff4bc..5a93a27 100644 --- a/mrw/exec.hpp +++ b/mrw/exec.hpp @@ -9,6 +9,9 @@ @license LGPL, see file COPYING $Log$ + Revision 1.5 2004/12/14 20:30:09 marc + added possibility to pass string to stdin of child process + Revision 1.4 2004/10/07 09:27:01 marc errors in documentation @@ -67,6 +70,18 @@ namespace mrw { // you can trace x.what() and x.stacktrace() } @endcode + + It is also possible to pass an @c stdin input argument to the + subprocess called: + + @code + try { + mrw::Exec cat = mrw::Cmd("/bin/cat") + .execute("this is passed to stdin"); + // "cat" passes all from stdin to stdout, therefore: + assert(cat.result()=="this is passed to stdin"); + } catch (...) {} // ignore + @endcode */ //@{ @@ -99,6 +114,9 @@ namespace mrw { and returns the two streams @c cout and @c cerr, also known as @c stderr and @c stdout. + Method @c execute can optionally also take a string parameter + that is passed to @c stdin of the child process. + There are different ways of usage for this class. A simple way, one line of code, to get only the resulting stream (no error) is: @@ -146,6 +164,42 @@ namespace mrw { */ Exec& execute(bool exc=true) throw(std::exception); + /** @brief Execute the command, pass @c stdin. + + @param input Input that is passed to @c stdin of the child process. + + @param exc + - @c true throw an exception if return status is not zero + - @c false throw only an exception in case of a fatal error + + @throw ExecutionFailedExc is thrown if + - fork fails + - creation or setup of pipes failed + - if given parameter is @c true (the default) also if the + executed program terminates with an error + */ + Exec& execute(const std::string& input, bool exc=true) + throw(std::exception); + + /** @brief Execute the command, pass @c stdin. + + @param input Input that is passed to @c stdin of the child process. + + @param exc + - @c true throw an exception if return status is not zero + - @c false throw only an exception in case of a fatal error + + @throw ExecutionFailedExc is thrown if + - fork fails + - creation or setup of pipes failed + - if given parameter is @c true (the default) also if the + executed program terminates with an error + */ + Exec& execute(char const*const input, bool exc=true) + throw(std::exception) { + return execute(std::string(input), exc); + } + /** @brief Executes the command if not done, streams @c stdout into a string If the command has not yet been executed successfully, it is @@ -220,7 +274,7 @@ namespace mrw { /** @brief A system command to be executed @pre #include - This class is used in conjunction with mrw::Exec. It mus be + This class is used in conjunction with mrw::Exec. It must be initialized with the command name, then the command parameters are appended either with commas, or by streaming them into the command, whatever you like. @@ -284,6 +338,60 @@ namespace mrw { */ Exec execute(bool exc=true) const throw(std::exception); + /** @brief Create a mrw::Exec and execute the command given an input + + Creates a mrw::Exec, executes the command, passes the input + and the flag to mrw::Exec::execute() and returns the created + mrw::Exec. The result of the execution can be retrieved + through the returned mrw::Exec object: The methods + mrw::Exec::success(), mrw::Exec::result() and + mrw::Exec::error() provide the necessary information. + + @param input Input that is passed to @c stdin of the child process. + + @param exc + - @c true throw an exception if return status is not zero + - @c false throw only an exception in case of a fatal error + + @return the mrw::Exec that has executed the command + + @throw ExecutionFailedExc is thrown if + - fork fails + - creation or setup of pipes failed + - if given parameter is @c true (the default) also if the + executed program terminates with an error + */ + Exec execute(const std::string& input, bool exc=true) const + throw(std::exception); + + /** @brief Create a mrw::Exec and execute the command given an input + + Creates a mrw::Exec, executes the command, passes the input + and the flag to mrw::Exec::execute() and returns the created + mrw::Exec. The result of the execution can be retrieved + through the returned mrw::Exec object: The methods + mrw::Exec::success(), mrw::Exec::result() and + mrw::Exec::error() provide the necessary information. + + @param input Input that is passed to @c stdin of the child process. + + @param exc + - @c true throw an exception if return status is not zero + - @c false throw only an exception in case of a fatal error + + @return the mrw::Exec that has executed the command + + @throw ExecutionFailedExc is thrown if + - fork fails + - creation or setup of pipes failed + - if given parameter is @c true (the default) also if the + executed program terminates with an error + */ + Exec execute(char const*const input, bool exc=true) const + throw(std::exception) { + return execute(std::string(input), exc); + } + private: /// Exec is allowed to call @c path() and @c args(). friend class Exec; diff --git a/mrw/exec_test.cpp b/mrw/exec_test.cpp index fe0e6e1..a216ba1 100644 --- a/mrw/exec_test.cpp +++ b/mrw/exec_test.cpp @@ -9,6 +9,9 @@ @license LGPL, see file COPYING $Log$ + Revision 1.6 2004/12/14 20:30:10 marc + added possibility to pass string to stdin of child process + Revision 1.5 2004/10/13 10:43:11 marc test for bad exception specification @@ -40,14 +43,20 @@ public: std::string res = (mrw::Cmd("/bin/ls"), "-l", "..").execute(); CPPUNIT_ASSERT(res.find("COPYING")