/** @file $Id$ $Date$ $Author$ @copy © Marc Wäckerlin @license LGPL, see file COPYING $Log$ Revision 1.7 2004/12/17 17:38:57 marc it works perfectly with blocking pipes and buffer size of 1 Revision 1.6 2004/12/17 16:28:51 marc stable implementation with select for both executes 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 - new file header for all sources - work around warning in mrw::auto - possibility to compile without log4cxx - work around bugs in demangle.h and libiberty.h - corrections in documentation - added simple tracing mechanism - more warnings - small corrections in Auto<>::Free and a new test for it - possibility to compile without stack trace */ #include #include #include #include // max #include // waitpid #include // fork, exec #include // memcpy #include // assert #include mrw::ExecutionFailedExc::ExecutionFailedExc(const std::string& w, const std::string& c) throw(std::bad_exception): _what(std::string("mrw::Exec: command execution failed\n")+ std::string(" failed command was: \""+c+"\"\n")+ std::string(" error was: \"")+w+'"') { /** @c what looks like: @verbatim mrw::Exec: command execution failed failed command was: "/bin/OOOOPS -v -q --crash" error was: "execution failed" @endverbatim */ } 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); while (++it!=_cmd.end()) c+=' '+*it; return c; } mrw::Cmd::operator mrw::Exec() const throw(std::bad_exception) { return mrw::Exec(*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); } 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]; int i(0); for (ArgList::const_iterator it(_cmd.begin()); it!=_cmd.end(); ++it) memcpy(array[i++]=new char[it->size()+1], it->c_str(), it->size()+1); array[i] = 0; return array; } 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 process using mrw::Pipe and calls @c execvp to execute the program. */ _success = false; _res = _err = ""; mrw::Pipe stdOut(true), stdErr(true); if (!stdOut || !stdErr) throw mrw::ExecutionFailedExc("cannot create pipe", *_cmd); pid_t pid(fork()); if (pid<0) throw ExecutionFailedExc("cannot fork", *_cmd); if (pid) { // parent stdOut.close_out(); stdErr.close_out(); if (!stdOut || !stdErr) throw ExecutionFailedExc("cannot close pipe", *_cmd); ssize_t num1(-1), num2(-1); fd_set readfds; char buf[1]; // used to have larger buffer, but since non blocking fails... int res(0), s(0); /** @bug It sometimes did not get the whole input since I changed to non blocking pipes. Now pipes are blocking again and buffer size is 1, so I hope all problems are resolved. Please report any problems! */ while (num1||num2) try { // not all end of file if (!res && (res=waitpid(pid, &s, WNOHANG))) if (res!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) throw ExecutionFailedExc("execution failed", *_cmd); // select... FD_ZERO(&readfds); if (num1) FD_SET(stdOut.istream(), &readfds); if (num2) FD_SET(stdErr.istream(), &readfds); int n(mrw::max((num1?stdOut.istream():0), (num2?stdErr.istream():0))); timeval tm = {0, 10000}; int num = select(n+1, &readfds, 0, 0, 0); if (num<0) throw ExecutionFailedExc("select failed", *_cmd); if (num==0 && res==pid) break; // process ended, no data left // check and handle stdout if (num>0 && 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; } std::cout<<"num1="<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; } std::cout<<"num0="<