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.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
11 KiB

/** @file
$Id$
$Date$
$Author$
@copy © Marc Wäckerlin
@license LGPL, see file <a href="license.html">COPYING</a>
$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<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 <mrw/stdext.hpp> // max
#include <sys/wait.h> // waitpid
#include <unistd.h> // fork, exec
#include <string.h> // memcpy
#include <assert.h> // assert
#include <iostream>
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);
20 years ago
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
20 years ago
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="<<num1<<", num2="<<num2<<std::endl;
} else { // child
20 years ago
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 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(true), stdErr(true); // non blocking is also a problem here
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[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 (num0||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(&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))));
timeval tm = {0, 10000};
int num = select(n+1, &readfds, &writefds, 0, &tm);
if (num<0) throw ExecutionFailedExc("select failed", *_cmd);
if (num==0 && res==pid) break; // process ended, no data left
// 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;
}
std::cout<<"num0="<<num0<<", num1="<<num1<<", num2="<<num2<<std::endl;
} 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;
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;
}