/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;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;
}
