2004-08-28 16:21:25 +00:00
|
|
|
/** @file
|
|
|
|
|
|
|
|
$Id$
|
|
|
|
|
|
|
|
$Date$
|
|
|
|
$Author$
|
|
|
|
|
|
|
|
@copy © Marc Wäckerlin
|
|
|
|
@license LGPL, see file <a href="license.html">COPYING</a>
|
|
|
|
|
|
|
|
$Log$
|
2004-12-17 17:38:57 +00:00
|
|
|
Revision 1.7 2004/12/17 17:38:57 marc
|
|
|
|
it works perfectly with blocking pipes and buffer size of 1
|
|
|
|
|
2004-12-17 16:28:51 +00:00
|
|
|
Revision 1.6 2004/12/17 16:28:51 marc
|
|
|
|
stable implementation with select for both executes
|
|
|
|
|
2004-12-14 20:30:10 +00:00
|
|
|
Revision 1.5 2004/12/14 20:30:09 marc
|
|
|
|
added possibility to pass string to stdin of child process
|
|
|
|
|
2004-08-28 16:21:25 +00:00
|
|
|
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<T>
|
|
|
|
- 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
|
|
|
|
|
|
|
|
*/
|
2004-04-21 06:39:20 +00:00
|
|
|
#include <mrw/exec.hpp>
|
|
|
|
#include <mrw/unistd.hpp>
|
2004-08-28 16:21:25 +00:00
|
|
|
#include <mrw/exception.hpp>
|
2004-12-14 20:30:10 +00:00
|
|
|
#include <mrw/stdext.hpp> // max
|
2004-04-21 06:39:20 +00:00
|
|
|
#include <sys/wait.h> // waitpid
|
|
|
|
#include <unistd.h> // fork, exec
|
|
|
|
#include <string.h> // memcpy
|
2004-12-14 20:30:10 +00:00
|
|
|
#include <assert.h> // assert
|
2004-04-21 06:39:20 +00:00
|
|
|
|
2004-12-17 17:38:57 +00:00
|
|
|
#include <iostream>
|
2004-04-21 06:39:20 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2004-12-14 20:30:10 +00:00
|
|
|
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);
|
2004-04-21 06:39:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2004-12-14 20:30:10 +00:00
|
|
|
mrw::Exec& mrw::Exec::execute(bool exc) throw(std::exception) {
|
2004-04-21 06:39:20 +00:00
|
|
|
/** This method calls @c fork, sets up a pipe connection to pass @c
|
2004-12-14 20:30:10 +00:00
|
|
|
stdout and @c stderr from the child process to the parent
|
|
|
|
process using mrw::Pipe and calls @c execvp to execute the
|
|
|
|
program. */
|
2004-04-21 06:39:20 +00:00
|
|
|
_success = false;
|
|
|
|
_res = _err = "";
|
2004-12-17 17:38:57 +00:00
|
|
|
mrw::Pipe stdOut(true), stdErr(true);
|
2004-04-23 16:03:29 +00:00
|
|
|
if (!stdOut || !stdErr)
|
2004-04-21 06:39:20 +00:00
|
|
|
throw mrw::ExecutionFailedExc("cannot create pipe", *_cmd);
|
|
|
|
pid_t pid(fork());
|
|
|
|
if (pid<0)
|
|
|
|
throw ExecutionFailedExc("cannot fork", *_cmd);
|
|
|
|
if (pid) { // parent
|
2004-04-23 16:03:29 +00:00
|
|
|
stdOut.close_out();
|
|
|
|
stdErr.close_out();
|
|
|
|
if (!stdOut || !stdErr)
|
2004-04-21 06:39:20 +00:00
|
|
|
throw ExecutionFailedExc("cannot close pipe", *_cmd);
|
2004-12-17 16:28:51 +00:00
|
|
|
ssize_t num1(-1), num2(-1);
|
|
|
|
fd_set readfds;
|
2004-12-17 17:38:57 +00:00
|
|
|
char buf[1]; // used to have larger buffer, but since non blocking fails...
|
2004-12-17 16:28:51 +00:00
|
|
|
int res(0), s(0);
|
2004-12-17 17:38:57 +00:00
|
|
|
/** @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! */
|
2004-12-17 16:28:51 +00:00
|
|
|
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};
|
2004-12-17 17:38:57 +00:00
|
|
|
int num = select(n+1, &readfds, 0, 0, 0);
|
2004-12-17 16:28:51 +00:00
|
|
|
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;
|
2004-04-21 06:39:20 +00:00
|
|
|
}
|
2004-12-17 17:38:57 +00:00
|
|
|
std::cout<<"num1="<<num1<<", num2="<<num2<<std::endl;
|
2004-04-21 06:39:20 +00:00
|
|
|
} else { // child
|
2004-04-23 16:03:29 +00:00
|
|
|
stdOut.close_in();
|
|
|
|
stdErr.close_in();
|
|
|
|
stdOut.connect_cout();
|
|
|
|
stdErr.connect_cerr();
|
2004-04-21 06:39:20 +00:00
|
|
|
execvp(_cmd->path(), _cmd->args());
|
|
|
|
exit(1); // execute failed
|
|
|
|
}
|
|
|
|
_success = true;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2004-12-14 20:30:10 +00:00
|
|
|
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
|
2004-12-17 17:38:57 +00:00
|
|
|
stdOut(true), stdErr(true); // non blocking is also a problem here
|
2004-12-14 20:30:10 +00:00
|
|
|
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;
|
2004-12-17 17:38:57 +00:00
|
|
|
char buf[1]; // used to have larger buffer, but since non blocking fails...
|
2004-12-17 16:28:51 +00:00
|
|
|
int res(0), s(0);
|
2004-12-17 17:38:57 +00:00
|
|
|
/** @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! */
|
2004-12-17 16:28:51 +00:00
|
|
|
while (num0||num1||num2) try { // not all end of file
|
|
|
|
if (!res && (res=waitpid(pid, &s, WNOHANG)))
|
2004-12-14 20:30:10 +00:00
|
|
|
if (res!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0)
|
|
|
|
throw ExecutionFailedExc("execution failed", *_cmd);
|
|
|
|
// 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);
|
|
|
|
int n(mrw::max((num0?stdIn.ostream():0),
|
|
|
|
mrw::max((num1?stdOut.istream():0),
|
|
|
|
(num2?stdErr.istream():0))));
|
2004-12-17 16:28:51 +00:00
|
|
|
timeval tm = {0, 10000};
|
2004-12-17 17:38:57 +00:00
|
|
|
int num = select(n+1, &readfds, &writefds, 0, &tm);
|
2004-12-14 20:30:10 +00:00
|
|
|
if (num<0) throw ExecutionFailedExc("select failed", *_cmd);
|
2004-12-17 16:28:51 +00:00
|
|
|
if (num==0 && res==pid) break; // process ended, no data left
|
2004-12-14 20:30:10 +00:00
|
|
|
// 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)num0<in.size())
|
|
|
|
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);
|
|
|
|
// 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;
|
|
|
|
}
|
2004-12-17 17:38:57 +00:00
|
|
|
std::cout<<"num0="<<num0<<", num1="<<num1<<", num2="<<num2<<std::endl;
|
2004-12-14 20:30:10 +00:00
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
|
2004-08-28 16:21:25 +00:00
|
|
|
mrw::Exec& mrw::Exec::operator>>(std::string& res) throw(std::exception) {
|
2004-04-21 06:39:20 +00:00
|
|
|
execute();
|
|
|
|
res += _res;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2004-08-28 16:21:25 +00:00
|
|
|
mrw::Exec::operator std::string&() throw(std::exception) {
|
2004-04-21 06:39:20 +00:00
|
|
|
if (!_success) execute();
|
|
|
|
return _res;
|
|
|
|
}
|
|
|
|
|
|
|
|
mrw::Exec::operator bool() throw(std::bad_exception) {
|
|
|
|
return _success;
|
|
|
|
}
|
|
|
|
|
2004-08-28 16:21:25 +00:00
|
|
|
std::string& mrw::Exec::result() throw(std::exception) {
|
2004-04-21 06:39:20 +00:00
|
|
|
if (!_success) execute();
|
|
|
|
return _res;
|
|
|
|
}
|
|
|
|
|
2004-08-28 16:21:25 +00:00
|
|
|
std::string& mrw::Exec::error() throw(std::exception) {
|
2004-04-21 06:39:20 +00:00
|
|
|
if (!_success) execute();
|
|
|
|
return _err;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool mrw::Exec::success() throw(std::bad_exception) {
|
|
|
|
return _success;
|
|
|
|
}
|