diff --git a/mrw/exec.cpp b/mrw/exec.cpp index 15b3bf3..72411af 100644 --- a/mrw/exec.cpp +++ b/mrw/exec.cpp @@ -9,6 +9,9 @@ @license LGPL, see file COPYING $Log$ + Revision 1.11 2005/04/19 18:48:00 marc + new feature PartialExec + Revision 1.10 2005/03/14 16:26:34 marc bugs have been fixed a long time ago, now no more in buglist @@ -48,7 +51,12 @@ #include // fork, exec #include // memcpy #include // assert +#include // kill +#include // kill + +//=========================================================== ExecutionFailedExc +//------------------------------------------------------------------------------ mrw::ExecutionFailedExc::ExecutionFailedExc(const std::string& w, const std::string& c) throw(std::bad_exception): @@ -65,22 +73,28 @@ mrw::ExecutionFailedExc::ExecutionFailedExc(const std::string& w, */ } +//========================================================================== Cmd + +//------------------------------------------------------------------------------ mrw::Cmd::Cmd(const std::string& c) throw(std::bad_exception) { _cmd.push_back(c); } +//------------------------------------------------------------------------------ mrw::Cmd& mrw::Cmd::operator,(const std::string& arg) throw(std::bad_exception) { _cmd.push_back(arg); return *this; } +//------------------------------------------------------------------------------ mrw::Cmd& mrw::Cmd::operator<<(const std::string& arg) throw(std::bad_exception) { _cmd.push_back(arg); return *this; } +//------------------------------------------------------------------------------ mrw::Cmd::operator std::string() const throw(std::bad_exception) { ArgList::const_iterator it(_cmd.begin()); std::string c(*it); @@ -88,22 +102,38 @@ mrw::Cmd::operator std::string() const throw(std::bad_exception) { return c; } +//------------------------------------------------------------------------------ mrw::Cmd::operator mrw::Exec() const throw(std::bad_exception) { return mrw::Exec(*this); } +//------------------------------------------------------------------------------ +mrw::Cmd::operator mrw::PartialExec() const throw(std::bad_exception) { + return mrw::PartialExec(*this); +} + +//------------------------------------------------------------------------------ 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); } +//------------------------------------------------------------------------------ +mrw::PartialExec mrw::Cmd::start(bool useInput) const throw(std::exception) { + return mrw::PartialExec(*this).start(useInput); +} + +//------------------------------------------------------------------------------ const char* mrw::Cmd::path() const throw(std::bad_exception) { return _cmd.front().c_str(); } + +//------------------------------------------------------------------------------ char** mrw::Cmd::args() const throw(std::bad_exception) { if (_cmd.size()==0) return 0; char** array = new char*[_cmd.size()+1]; @@ -114,25 +144,32 @@ char** mrw::Cmd::args() const throw(std::bad_exception) { return array; } +//========================================================================= Exec + +//------------------------------------------------------------------------------ mrw::Exec::Exec(const mrw::Cmd& c) throw(std::bad_exception): _cmd(new mrw::Cmd(c)), _success(false) { } +//------------------------------------------------------------------------------ mrw::Exec::Exec(const mrw::Exec& e) throw(std::bad_exception): _cmd(new mrw::Cmd(*e._cmd)), _res(e._res), _err(e._err), _success(e._success) { } +//------------------------------------------------------------------------------ mrw::Exec::~Exec() throw() { delete _cmd; } +//------------------------------------------------------------------------------ mrw::Exec& mrw::Exec::operator=(const mrw::Exec& e) throw(std::bad_exception) { if (this==&e) return *this; *_cmd=*e._cmd; _res=e._res; _err=e._err; _success=e._success; return *this; } +//------------------------------------------------------------------------------ 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 @@ -167,16 +204,22 @@ mrw::Exec& mrw::Exec::execute(bool exc) throw(std::exception) { // check and handle stdout if (num1 && (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); + else if (num1==-1) + if (errno!=EINTR&&errno!=EAGAIN) + throw ExecutionFailedExc("readin stdout", *_cmd); + else + num1 = 1; // check and handle stderr if (num2 && (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); + else if (num2==-1) + if (errno!=EINTR&&errno!=EAGAIN) + throw ExecutionFailedExc("readin stderr", *_cmd); + else + num2 = 1; } catch (...) { - if (exc) throw; _success = false; + if (exc) throw; return *this; } } else { // child @@ -191,6 +234,7 @@ mrw::Exec& mrw::Exec::execute(bool exc) 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. @@ -199,7 +243,7 @@ mrw::Exec& mrw::Exec::execute(const std::string& input, bool exc) "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 + stdin, @c stdout and @c stderr between the child process and the parent process using mrw::Pipe and calls @c execvp to execute the program. */ _success = false; @@ -236,21 +280,30 @@ mrw::Exec& mrw::Exec::execute(const std::string& input, bool exc) in = in.substr(num0); else if ((unsigned int)num0==in.size()) num0=0, stdIn.close_out(); - } else if (num0==-1 && (errno!=EINTR&&errno!=EAGAIN)) - throw ExecutionFailedExc("writing stdin", *_cmd); + } else if (num0==-1) + if (errno!=EINTR&&errno!=EAGAIN) + throw ExecutionFailedExc("writing stdin", *_cmd); + else + num0 = 1; // check and handle stdout if (num1 && (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); + else if (num1==-1) + if (errno!=EINTR&&errno!=EAGAIN) + throw ExecutionFailedExc("readin stdout", *_cmd); + else + num1 = 1; // check and handle stderr if (num2 && (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); + else if (num2==-1) + if (errno!=EINTR&&errno!=EAGAIN) + throw ExecutionFailedExc("readin stderr", *_cmd); + else + num2 = 1; } catch (...) { - if (exc) throw; _success = false; + if (exc) throw; return *this; } } else { // child @@ -267,31 +320,218 @@ mrw::Exec& mrw::Exec::execute(const std::string& input, bool exc) return *this; } +//------------------------------------------------------------------------------ mrw::Exec& mrw::Exec::operator>>(std::string& res) throw(std::exception) { execute(); res += _res; return *this; } +//------------------------------------------------------------------------------ mrw::Exec::operator std::string&() throw(std::exception) { if (!_success) execute(); return _res; } +//------------------------------------------------------------------------------ mrw::Exec::operator bool() throw(std::bad_exception) { return _success; } +//------------------------------------------------------------------------------ std::string& mrw::Exec::result() throw(std::exception) { if (!_success) execute(); return _res; } +//------------------------------------------------------------------------------ std::string& mrw::Exec::error() throw(std::exception) { if (!_success) execute(); return _err; } +//------------------------------------------------------------------------------ bool mrw::Exec::success() throw(std::bad_exception) { return _success; } + +//================================================================== PartialExec + +//------------------------------------------------------------------------------ +mrw::PartialExec::PartialExec(const mrw::Cmd& c) throw(std::bad_exception): + Exec(c), _finished(true), _finish(false) { +} + +//------------------------------------------------------------------------------ +mrw::PartialExec::PartialExec(mrw::PartialExec& e) + throw(std::bad_exception): + Exec(e), _finished(e._finished), _finish(e._finish), + _stdIn(e._stdIn), _stdOut(e._stdOut), _stdErr(e._stdErr), _input(e._input), + _num0(e._num0), _num1(e._num1), _num2(e._num2), + _lastPid(e._lastPid), _pid(e._pid) { + e._finished = true; +} + +//------------------------------------------------------------------------------ +mrw::PartialExec::PartialExec(const mrw::PartialExec& e) + throw(std::bad_exception): + Exec(e), _finished(e._finished), _finish(e._finish), + _input(e._input), + _num0(e._num0), _num1(e._num1), _num2(e._num2), + _lastPid(e._lastPid), _pid(e._pid) { + /// @warning @c const is casted away + mrw::PartialExec& nonConstE(const_cast(e)); + _stdIn = nonConstE._stdIn; + _stdOut = nonConstE._stdOut; + _stdErr = nonConstE._stdErr; + nonConstE._finished = true; + nonConstE._finish = false; +} + +//------------------------------------------------------------------------------ +mrw::PartialExec& mrw::PartialExec::operator=(mrw::PartialExec& e) + throw(std::bad_exception) { + if (this==&e) return *this; + *_cmd=*e._cmd; _res=e._res; _err=e._err; _success=e._success; + _finished = e._finished; + _finish = e._finish; + _stdIn = e._stdIn; _stdOut = e._stdOut; _stdErr = e._stdErr; + _input = e._input; _num0 = e._num0; _num1 = e._num1; _num2 = e._num2; + _lastPid = e._lastPid; + _pid = e._pid; + e._finished = true; + e._finish = false; + return *this; +} + +//------------------------------------------------------------------------------ +mrw::PartialExec& mrw::PartialExec::finish() throw() { + _finish = true; + return *this; +} + +//------------------------------------------------------------------------------ +bool mrw::PartialExec::finished() throw() { + return _finished; +} + +//------------------------------------------------------------------------------ +mrw::PartialExec& mrw::PartialExec::start(bool useInput) + throw(std::exception) { + if (!_finished) throw mrw::runtime_error("running process not yet finished"); + /** This method calls @c fork, sets up a pipe connection to pass @c + stdin, @c stdout and @c stderr between the child process and the + parent process using mrw::Pipe and calls @c execvp to execute + the program. */ + _success = false; + _finish = !useInput; + _input = ""; + _res = _err = ""; + _stdIn = std::auto_ptr(new mrw::Pipe(mrw::Pipe::block_input)); + _stdOut = std::auto_ptr(new mrw::Pipe(mrw::Pipe::block_output)); + _stdErr = std::auto_ptr(new mrw::Pipe(mrw::Pipe::block_output)); + if (!*_stdIn || !*_stdOut || !*_stdErr) + throw mrw::ExecutionFailedExc("cannot create pipe", *_cmd); + _lastPid = 0; + _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); + _num0 = _num1 = _num2 = 1; + _finished = false; + } else { // child + _stdIn->close_out(); + _stdOut->close_in(); + _stdErr->close_in(); + _stdIn->connect_cin(); + _stdOut->connect_cout(); + _stdErr->connect_cerr(); + execvp(_cmd->path(), _cmd->args()); + exit(1); // execute failed + } + _success = true; + return *this; +} + +//------------------------------------------------------------------------------ +std::pair + mrw::PartialExec::read(const std::string& input, bool exc) + throw(std::exception) { + std::pair output; + /** @note @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"); + /** @warning After calling finish(), it is forbidden to pass + any @c input string, it must then always be empty, + because the pipe is already closed! */ + assert(!(_finish && input.size()) && + "after calling PartialExec::finish(), it is forbidden " + "to pass new input text, because the pipe is already " + "closed! this is a programming but that must be solved!"); + _input += input; + /* It sometimes did not get the whole input since I changed to non + blocking pipes. Now I have found the solution to the problems: + Non blocking IO does not work in conjunction with select! Also + the pipe must not be nonblocking on both ends, but only on + one. */ + if (!_finished) try { // not finished + char buf[4096]; + int s(0); + if (!_lastPid && (_lastPid=waitpid(_pid, &s, WNOHANG))) { + if (_lastPid!=_pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) + throw ExecutionFailedExc("execution failed", *_cmd); + } + // check and handle stdin + if (_input.size() && _num0!=-1 && + (_num0=write(_stdIn->ostream(), _input.c_str(), _input.size()))>0) { + if ((unsigned int)_num0<_input.size()) + _input = _input.substr(_num0); + else if ((unsigned int)_num0==_input.size()) + _input.clear(); + } else if (_num0==-1) + if (errno!=EINTR&&errno!=EAGAIN) + throw ExecutionFailedExc("writing stdin", *_cmd); + else + _num0 = 1; + // check and handle stdout + if (_num1 && (_num1=::read(_stdOut->istream(), buf, sizeof(buf)))>0) + _res += output.first=std::string(buf, _num1); + else if (_num1==-1) + if (errno!=EINTR&&errno!=EAGAIN) + throw ExecutionFailedExc("readin stdout", *_cmd); + else + _num1 = 1; + // check and handle stderr + if (_num2 && (_num2=::read(_stdErr->istream(), buf, sizeof(buf)))>0) + _err += output.second=std::string(buf, _num2); + else if (_num2==-1) + if (errno!=EINTR&&errno!=EAGAIN) + throw ExecutionFailedExc("readin stderr", *_cmd); + else + _num2 = 1; + if (_finish && !_input.size()) { + _stdIn->close_out(); + _num0 = 0; + } + _finished = _finish && !_input.size() && !_num1 && !_num2 && _lastPid; + } catch (mrw::exception& x) { + _finished = true; + _success = false; + if (exc) throw; + return output; + } + if (_finished) _success = true; + return output; +} + +//------------------------------------------------------------------------------ +mrw::PartialExec& mrw::PartialExec::terminate() throw() { + kill(_pid, SIGTERM); + return *this; +} diff --git a/mrw/exec.hpp b/mrw/exec.hpp index 5a93a27..92e3737 100644 --- a/mrw/exec.hpp +++ b/mrw/exec.hpp @@ -9,6 +9,9 @@ @license LGPL, see file COPYING $Log$ + Revision 1.6 2005/04/19 18:48:00 marc + new feature PartialExec + Revision 1.5 2004/12/14 20:30:09 marc added possibility to pass string to stdin of child process @@ -35,6 +38,7 @@ #include #include #include +#include namespace mrw { @@ -81,12 +85,12 @@ namespace mrw { // "cat" passes all from stdin to stdout, therefore: assert(cat.result()=="this is passed to stdin"); } catch (...) {} // ignore - @endcode - */ + @endcode */ //@{ class Cmd; - + + //============================================================================ /** @brief Exception: Execution of command failed. @pre #include @@ -95,18 +99,18 @@ namespace mrw { to create the necessary pipes, or the command executing process terminated with an error. In the last case, you can access the error stream from @c stderr respectively @c cerr with method - mrw::Exec::error(). - */ + mrw::Exec::error(). */ class ExecutionFailedExc: public mrw::exception { - public: - ExecutionFailedExc(const std::string&, const std::string&) - throw(std::bad_exception); - virtual ~ExecutionFailedExc() throw() {} - virtual const char* what() const throw() {return _what.c_str();} - private: - std::string _what; + public: + ExecutionFailedExc(const std::string&, const std::string&) + throw(std::bad_exception); + virtual ~ExecutionFailedExc() throw() {} + virtual const char* what() const throw() {return _what.c_str();} + private: + std::string _what; }; + //============================================================================ /** @brief Execute a command in a new process. @pre #include @@ -137,140 +141,295 @@ namespace mrw { // ls.error() contains stderr @endcode - @note Please note that the command execution may throw an exception. - */ + @note Please note that the command execution may throw an exception. */ class Exec { - public: + + //................................................................ methods + public: - /** @brief Create an executor given a command. - Construction without passing a command is not possible. */ - Exec(const mrw::Cmd&) throw(std::bad_exception); + /** @brief Create an executor given a command. + Construction without passing a command is not possible. */ + Exec(const mrw::Cmd&) throw(std::bad_exception); - Exec(const mrw::Exec&) throw(std::bad_exception); - ~Exec() throw(); - Exec& operator=(const mrw::Exec&) throw(std::bad_exception); + Exec(const mrw::Exec&) throw(std::bad_exception); + ~Exec() throw(); + Exec& operator=(const mrw::Exec&) throw(std::bad_exception); - /** @brief Execute the command. + /** @brief Execute the command. - @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 + @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(bool exc=true) throw(std::exception); + @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(bool exc=true) throw(std::exception); - /** @brief Execute the command, pass @c stdin. + /** @brief Execute the command, pass @c stdin. - @param input Input that is passed to @c stdin of the child process. + @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 + @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); + @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. + /** @brief Execute the command, pass @c stdin. - @param input Input that is passed to @c stdin of the child process. + @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 + @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 + @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 - first executed, then the @c stdout output of the called - program is appended to the string. + If the command has not yet been executed successfully, it is + first executed, then the @c stdout output of the called + program is appended to the string. - @throw ExecutionFailedExc in case of any failure or if the - executed program does not return a zero exit status. - */ - Exec& operator>>(std::string&) throw(std::exception); + @throw ExecutionFailedExc in case of any failure or if the + executed program does not return a zero exit status. */ + Exec& operator>>(std::string&) throw(std::exception); - /** @brief Executes the command if not done, returns @c stdout as string + /** @brief Executes the command if not done, returns @c stdout as string - If the command has not yet been executed successfully, it is - first executed, then the @c stdout output of the called - program is returned. + If the command has not yet been executed successfully, it is + first executed, then the @c stdout output of the called + program is returned. - @return @c stdout of the called program + @return @c stdout of the called program - @throw ExecutionFailedExc in case of any failure or if the - executed program does not return a zero exit status. - */ - operator std::string&() throw(std::exception); + @throw ExecutionFailedExc in case of any failure or if the + executed program does not return a zero exit status. */ + operator std::string&() throw(std::exception); - /** @return - - @c true if the last execution was successful - - @c false if the last execution failed or the command was - never executed - */ - operator bool() throw(std::bad_exception); + /** @return + - @c true if the last execution was successful + - @c false if the last execution failed or the command was + never executed */ + operator bool() throw(std::bad_exception); - /** @brief Executes the command if not done, returns @c stdout as string + /** @brief Executes the command if not done, returns @c stdout as string - If the command has not yet been executed successfully, it is - first executed, then the @c stdout output of the called - program is returned. + If the command has not yet been executed successfully, it is + first executed, then the @c stdout output of the called + program is returned. - @return @c stdout of the called program + @return @c stdout of the called program - @throw ExecutionFailedExc in case of any failure or if the - executed program does not return a zero exit status. - */ - std::string& result() throw(std::exception); + @throw ExecutionFailedExc in case of any failure or if the + executed program does not return a zero exit status. */ + std::string& result() throw(std::exception); - /** @brief Executes the command if not done, returns @c stderr as string + /** @brief Executes the command if not done, returns @c stderr as string - If the command has not yet been executed successfully, it is - first executed, then the @c stderr error output of the called - program is returned. + If the command has not yet been executed successfully, it is + first executed, then the @c stderr error output of the called + program is returned. - @return @c stderr of the called program + @return @c stderr of the called program - @throw ExecutionFailedExc in case of any failure or if the - executed program does not return a zero exit status. - */ - std::string& error() throw(std::exception); + @throw ExecutionFailedExc in case of any failure or if the + executed program does not return a zero exit status. */ + std::string& error() throw(std::exception); - /** @return - - @c true if the last execution was successful - - @c false if the last execution failed or the command was - never executed - */ - bool success() throw(std::bad_exception); - - private: - Exec(); // no default constructor - mrw::Cmd* _cmd; - std::string _res, _err; - bool _success; + /** @return + - @c true if the last execution was successful + - @c false if the last execution failed or the command was + never executed */ + bool success() throw(std::bad_exception); + + //................................................................ methods + private: + + Exec(); // no default constructor + + //.............................................................. variables + private: + + friend class PartialExec; // don't want the variables protected + mrw::Cmd* _cmd; + std::string _res, _err; + bool _success; }; + //============================================================================ + /** @brief Execute a UNIX program in non blocking parts. + @pre #include + + A given UNIX command is executed, but the class does not wait + until it is finished, instead it gives back the control to the + caller. This behaviour is achieved using non blocking + communication. But the caller is responsible to retrieve all + information from the client, and if necessary to close the input + pipe of the client executable program. Therefore you have to + give back control from time to time, normally this is doen in a + @c while loop, where you can execute also different thing, + e.g. update a display of the result or similar. + + With this class, you can communicate with a child process, and + do other things at the same time, without the need for multi + threading. + + Execution of a program works the following way: + - do not use execute() (otherwise the behaviours is identical + to class mrw::Exec, you gain nothing, but also loose nothing) + - use start() to start the external program + - use start() or @c start(false) if you don't want to pass + input to the child process + - use @c start(true) if you want to pass input to the + child process + - if you called @c start(true), call finish() if you have no more + input to send to the child process (it's like an end-of-file) + - the execution is not terminated, before finished() returns + @c true + - while finished() is false, subsequently call read() to read + the output of the child process + + @warning After calling finish(), or if you did not call + start() with parameter @c true, it is forbidden to + pass anything but an empty string as first parameter to + read()! Anything else is a programming error and + results in an assertion failure and a core dump! + + @note If your program seems to hang, check if you call finish() + correctly! + + Here an example: + @code +mrw::PartialExec exec = mrw::Cmd("/bin/cat").start(true); +std::string res = exec.read("This is a test\n").first; +res += exec.read("This is another test\n").first; +exec.finish(); // close the input pipe of @c cat +while (!exec.finished()) res+=exec.read().first; +@endcode */ + class PartialExec: public Exec { + + //................................................................ methods + public: + + /** @brief Create an executor given a command. + Construction without passing a command is not possible. */ + PartialExec(const mrw::Cmd&) throw(std::bad_exception); + + /** @brief Copy construction invalidates the original object. + + All opened pipes (opened with start()) are lost in the + original object and are then owned by the new object. */ + PartialExec(mrw::PartialExec&) throw(std::bad_exception); + + /** @brief Copy construction invalidates the original object. + + @copydoc PartialExec(mrw::PartialExec&) + + @warning @c const for the argument is a fake! It is casted away! + + @param e @b Warning: const is casted away! */ + PartialExec(const mrw::PartialExec& e) throw(std::bad_exception); + + /** @brief Assignment invalidates the original object. + + @copydoc PartialExec(mrw::PartialExec&) */ + PartialExec& operator=(mrw::PartialExec&) throw(std::bad_exception); + + /** @brief Close the input pipe of the child process. + + If start() is called with argument @c false, then you + can pass input to @c stdin of the child process, but you @b + must call this method, after passing the last input + string. Otherwise, the child's input pipe won't be closed, + the child process does not stop waiting for more input! If + your program seems to hang, check if you call finish() + correctly! */ + PartialExec& finish() throw(); + + /** @brief Check if there's more data left to read(). + @return @c true if the child process has finished and all + data is read. */ + bool finished() throw(); + + /** @brief Start a new child process. + + At most one child process can run at the same time. + + @throw mrw::runtime_error if a previous child has not finished() yet + @throw mrw::ExecutionFailedExc if the child process cannot be started + @param useInput + - @c true if input will be sent to the child's @c stdin + - pass all input in the first parameter of read() + - finish() must be called when all input is sent + - @c false if no input is sent to the child's @c stdin + - the first parameter of read must allways be passed an + empty string */ + PartialExec& start(bool useInput=false) throw(std::exception); + + /** @brief Read from the subprocess, optionally pass an @c input to + @c stdin of the subprocess. + + @param input a string to pass to the child processes @c stdin + + @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 a pair containing the last read @c stdout and @c stderr + of the child + + @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 + + @note If start() was not called with parameter @c true, then + @c input must always be an empty string! + + @pre start() was called */ + std::pair read(const std::string& input="", + bool exc=true) + throw(std::exception); + + /// Terminates a running job by sending @c SIGTERM to the child process. + PartialExec& terminate() throw(); + + //................................................................ methods + private: + + PartialExec(); // no default constructor + + //.............................................................. variables + private: + + bool _finished; + bool _finish; + std::auto_ptr _stdIn, _stdOut, _stdErr; + std::string _input; + int _num0, _num1, _num2, _lastPid, _pid; + }; + + //============================================================================ /** @brief A system command to be executed @pre #include @@ -293,115 +452,62 @@ namespace mrw { @endcode */ class Cmd { - public: - /** @brief Create a command given the name of the executable - @param command the name of the program to execute (no parameter) - @note There is no default constructor. */ - Cmd(const std::string& command) throw(std::bad_exception); - - /** @brief Append a parameter to a command - @param param a parameter / commandline argument - to append to the command */ - Cmd& operator,(const std::string& param) throw(std::bad_exception); + public: + /** @brief Create a command given the name of the executable + @param command the name of the program to execute (no parameter) + @note There is no default constructor. */ + Cmd(const std::string& command) throw(std::bad_exception); + + /** @brief Append a parameter to a command + @param param a parameter / commandline argument + to append to the command */ + Cmd& operator,(const std::string& param) throw(std::bad_exception); - /** @brief Append a parameter to a command - @param param a parameter / commandline argument - to append to the command */ - Cmd& operator<<(const std::string& param) throw(std::bad_exception); + /** @brief Append a parameter to a command + @param param a parameter / commandline argument + to append to the command */ + Cmd& operator<<(const std::string& param) throw(std::bad_exception); - /** @return the command including parameter */ - operator std::string() const throw(std::bad_exception); + /** @return the command including parameter */ + operator std::string() const throw(std::bad_exception); - /** @return a mrw::Exec that's constructed with this class */ - operator mrw::Exec() const throw(std::bad_exception); + /** @return a mrw::Exec that's constructed with this class */ + operator mrw::Exec() const throw(std::bad_exception); - /** @brief Create a mrw::Exec and execute the command - - Creates a mrw::Exec, executes the command, passes 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 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 a mrw::PartialExec that's constructed with this class */ + operator mrw::PartialExec() const throw(std::bad_exception); - @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(bool exc=true) const throw(std::exception); + /** @brief Create a mrw::Exec and execute a child process. + @see Exec::execute(bool) */ + 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 a child process. + @see Exec::execute(const std::string&, bool) */ + 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 + /** @brief Create a mrw::Exec and execute a child process. + @see Exec::execute(char const*const, bool) */ + Exec execute(char const*const input, bool exc=true) const + throw(std::exception) { + return execute(std::string(input), exc); + } - @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); - } + /** @brief Create a new mrw::PartialExec and start a new child process. + @see PartialExec::start(bool) */ + PartialExec start(bool useInput=false) const throw(std::exception); private: - /// Exec is allowed to call @c path() and @c args(). + // Exec and PartialExec are allowed to call @c path() and @c args(). friend class Exec; - /// No default constructor. - Cmd(); + friend class PartialExec; + Cmd(); // No default constructor. const char* path() const throw(std::bad_exception); char** args() const throw(std::bad_exception); typedef std::list ArgList; ArgList _cmd; }; + //@} } #endif diff --git a/mrw/exec_test.cpp b/mrw/exec_test.cpp index 542f838..a0130c8 100644 --- a/mrw/exec_test.cpp +++ b/mrw/exec_test.cpp @@ -9,6 +9,9 @@ @license LGPL, see file COPYING $Log$ + Revision 1.9 2005/04/19 18:48:00 marc + new feature PartialExec + Revision 1.8 2005/04/07 20:55:21 marc Oops, there's a make distcheck...? Now it works. @@ -65,12 +68,44 @@ public: void unexpectedExc() throw(std::bad_exception) { std::string res = (mrw::Cmd("/bin/false")).execute().result(); } + void lsTest2() { + std::string res; + mrw::PartialExec exec = (mrw::Cmd("/bin/ls"), "-l", + std::string(getenv("srcdir"))+"/..").start(); + while (!exec.finished()) res+=exec.read().first; + CPPUNIT_ASSERT(res.find("COPYING")