C++ Library containing a lot of needful things: Stack Trace, Command Line Parser, Resource Handling, Configuration Files, Unix Command Execution, Directories, Regular Expressions, Tokenizer, Function Trace, Standard Extensions.
552 lines
20 KiB
552 lines
20 KiB
/** @file |
|
|
|
$Id$ |
|
|
|
$Date$ |
|
$Author$ |
|
|
|
@copy © Marc Wäckerlin |
|
@license LGPL, see file <a href="license.html">COPYING</a> |
|
|
|
$Log$ |
|
Revision 1.14 2005/11/29 12:39:42 marc |
|
make it compilable with gcc 4.0.2 and newer doxygen |
|
|
|
Revision 1.13 2005/04/20 18:32:14 marc |
|
*** empty log message *** |
|
|
|
Revision 1.12 2005/04/20 18:12:55 marc |
|
added kill() for PartialExec |
|
|
|
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 |
|
|
|
Revision 1.9 2004/12/20 07:40:35 marc |
|
documentation improved, new grouping |
|
|
|
Revision 1.8 2004/12/18 21:00:09 marc |
|
everything is ok, when pipes are non blocking on parent's side and blocking on client's side, and select is not used (not necessary for non blocking IO) |
|
|
|
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<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 |
|
|
|
*/ |
|
#include <mrw/exec.hpp> |
|
#include <mrw/unistd.hpp> |
|
#include <mrw/exception.hpp> |
|
#include <sys/wait.h> // waitpid |
|
#include <unistd.h> // fork, exec |
|
#include <string.h> // memcpy |
|
#include <assert.h> // assert |
|
#include <sys/types.h> // kill |
|
#include <signal.h> // kill |
|
|
|
//=========================================================== ExecutionFailedExc |
|
|
|
//------------------------------------------------------------------------------ |
|
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 |
|
*/ |
|
} |
|
|
|
//========================================================================== 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); |
|
while (++it!=_cmd.end()) c+=' '+*it; |
|
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]; |
|
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; |
|
} |
|
|
|
//========================================================================= 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 |
|
process using mrw::Pipe and calls @c execvp to execute the |
|
program. */ |
|
_success = false; |
|
_res = _err = ""; |
|
mrw::Pipe stdOut(mrw::Pipe::block_output), stdErr(mrw::Pipe::block_output); |
|
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); |
|
char buf[4096]; |
|
int res(0), s(0); |
|
/* 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. */ |
|
while (num1||num2||!res) try { // not end of files or child terminated |
|
if (!res && (res=waitpid(pid, &s, WNOHANG))) |
|
if (res!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) |
|
throw ExecutionFailedExc("execution failed", *_cmd); |
|
if (num1<=0&&num2<=0) usleep(10000); // nothing to read last time |
|
// check and handle stdout |
|
if (num1 && (num1=read(stdOut.istream(), buf, sizeof(buf)))>0) |
|
_res += 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 += std::string(buf, num2); |
|
else if (num2==-1) |
|
if (errno!=EINTR&&errno!=EAGAIN) |
|
throw ExecutionFailedExc("readin stderr", *_cmd); |
|
else |
|
num2 = 1; |
|
} catch (...) { |
|
_success = false; |
|
if (exc) throw; |
|
return *this; |
|
} |
|
} else { // child |
|
stdOut.close_in(); |
|
stdErr.close_in(); |
|
stdOut.connect_cout(); |
|
stdErr.connect_cerr(); |
|
execvp(_cmd->path(), _cmd->args()); |
|
exit(1); // execute failed |
|
} |
|
_success = true; |
|
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 between the child process and the |
|
parent process using mrw::Pipe and calls @c execvp to execute |
|
the program. */ |
|
_success = false; |
|
_res = _err = ""; |
|
mrw::Pipe stdIn(mrw::Pipe::block_input), |
|
stdOut(mrw::Pipe::block_output), stdErr(mrw::Pipe::block_output); |
|
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); |
|
char buf[4096]; |
|
int res(0), s(0); |
|
/* 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. */ |
|
while (num0||num1||num2||!res) try { // not end of files or child terminated |
|
if (!res && (res=waitpid(pid, &s, WNOHANG))) |
|
if (res!=pid || WIFEXITED(s)!=0 && WEXITSTATUS(s)!=0) |
|
throw ExecutionFailedExc("execution failed", *_cmd); |
|
if (num0<=0&&num1<=0&&num2<=0) usleep(10000); // no activity last time |
|
// check and handle stdin |
|
if (num0 && (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) |
|
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) |
|
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) |
|
if (errno!=EINTR&&errno!=EAGAIN) |
|
throw ExecutionFailedExc("readin stderr", *_cmd); |
|
else |
|
num2 = 1; |
|
} catch (...) { |
|
_success = false; |
|
if (exc) throw; |
|
return *this; |
|
} |
|
} 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; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
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<mrw::PartialExec&>(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<mrw::Pipe>(new mrw::Pipe(mrw::Pipe::block_input)); |
|
_stdOut = std::auto_ptr<mrw::Pipe>(new mrw::Pipe(mrw::Pipe::block_output)); |
|
_stdErr = std::auto_ptr<mrw::Pipe>(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<std::string, std::string> |
|
mrw::PartialExec::read(const std::string& input, bool exc) |
|
throw(std::exception) { |
|
std::pair<std::string, std::string> 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; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
mrw::PartialExec& mrw::PartialExec::kill() throw() { |
|
::kill(_pid, SIGKILL); |
|
return *this; |
|
}
|
|
|