/* process.cc - functions for process.hh
 * Copyright 2005 Bas Wijnen <wijnen@debian.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "process.hh"
#include "error.hh"
#include "debug.hh"
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

extern "C"
{
  extern char **environ;
}

namespace shevek
{
  char **process::make_pointers (std::list <std::string> const &source)
  {
    char **ret = new char *[source.size () + 1];
    int count = 0;
    for (std::list <std::string>::const_iterator
	   i = source.begin (); i != source.end (); ++i)
      {
	ret[count] = new char[i->size () + 1];
	memcpy (ret[count], i->data (), i->size ());
	ret[count][i->size ()] = '\0';
	++count;
      }
    ret[source.size ()] = 0;
    return ret;
  }

  void process::clean (char **pointers)
  {
    for (int i = 0; pointers[i]; ++i)
      delete[] pointers[i];
  }

  char *process::dup (char const *str)
  {
    int s = strlen (str);
    char *ret = new char[s + 1];
    memcpy (ret, str, s + 1);
    return ret;
  }

  Glib::RefPtr <process> process::create (std::string const &command,
					  std::list <std::string> &argv,
					  bool pipe_stdin, bool pipe_stdout,
					  bool pipe_stderr)
  {
    char **new_argv = make_pointers (argv);
    Glib::RefPtr <process> ret (new process (command, new_argv, environ,
					     pipe_stdin, pipe_stdout,
					     pipe_stderr));
    clean (new_argv);
    return ret;
  }

  Glib::RefPtr <process> process::create (std::string const &command,
					  std::list <std::string> &argv,
					  std::list <std::string> const &envp,
					  bool pipe_stdin, bool pipe_stdout,
					  bool pipe_stderr)
  {
    char **new_argv = make_pointers (argv);
    char **new_envp = make_pointers (envp);
    Glib::RefPtr <process> ret (new process (command, new_argv, new_envp,
					     pipe_stdin, pipe_stdout,
					     pipe_stderr) );
    clean (new_argv);
    clean (new_envp);
    return ret;
  }

  Glib::RefPtr <shevek::fd> process::in ()
  {
    return m_in;
  }

  Glib::RefPtr <shevek::fd> process::out ()
  {
    return m_out;
  }

  Glib::RefPtr <shevek::fd> process::err ()
  {
    return m_err;
  }

  pid_t process::pid ()
  {
    return m_pid;
  }

  process::process (std::string const &command, char **argv, char **envp,
		    bool pipe_stdin, bool pipe_stdout, bool pipe_stderr)
  {
    startfunc;
    int fds[3][2];
    if ((pipe_stdin && ::pipe (fds[0]))
	|| (pipe_stdout && ::pipe (fds[1]))
	|| (pipe_stderr && ::pipe (fds[2])))
      {
	shevek_error_errno ("unable to create pipes for communication");
	return;
      }
    m_pid = ::fork ();
    switch (m_pid)
      {
      case -1: // error
	shevek_error_errno ("unable to fork child");
	return;
      case 0:
	// child
	if (pipe_stdin)
	  {
	    ::close (fds[0][1]);
	    ::close (STDIN_FILENO);
	    ::dup2 (fds[0][0], STDIN_FILENO);
	    ::close (fds[0][0]);
	  }
	if (pipe_stdout)
	  {
            ::close (fds[1][0]);
	    ::close (STDOUT_FILENO);
	    ::dup2 (fds[1][1], STDOUT_FILENO);
	    ::close (fds[1][1]);
	  }
	if (pipe_stderr)
	  {
	    ::close (fds[2][0]);
	    ::close (STDERR_FILENO);
	    ::dup2 (fds[2][1], STDERR_FILENO);
	    ::close (fds[2][1]);
	  }
	execve (command.c_str (), argv, envp);
	shevek_error_errno ("execve failed");
	return;
      default: // parent
	if (pipe_stdin)
	  {
	    ::close (fds[0][0]);
	    m_in = fd::create (fds[0][1]);
	    m_in->set_error (sigc::mem_fun (m_in.operator-> (), &fd::reset));
	  }
	if (pipe_stdout)
	  {
	    ::close (fds[1][1]);
	    m_out = fd::create (fds[1][0]);
	    m_out->set_error (sigc::mem_fun (m_out.operator-> (), &fd::reset));
	  }
	if (pipe_stderr)
	  {
            ::close (fds[2][1]);
	    m_err = fd::create (fds[2][0]);
	    m_err->set_error (sigc::mem_fun (m_err.operator-> (), &fd::reset));
	  }
	return;
      }
  }

  process::~process ()
  {
    startfunc;
    if (m_in)
      {
	::close (m_in->get_fd () );
	m_in->set_fd (-1);
      }
    if (m_out)
      {
	::close (m_out->get_fd () );
	m_out->set_fd (-1);
      }
    if (m_err)
      {
	::close (m_err->get_fd () );
	m_err->set_fd (-1);
      }
    ::kill (m_pid, SIGHUP);
    ::waitpid (m_pid, 0, 0);
  }

  std::string process::run (std::string const &command, std::string const &sh)
  {
    Glib::RefPtr <process> p = shell (command, false, true, false, sh);
    std::string result;
    while (true)
      {
	std::string &part = p->m_out->read_block ();
	if (part.empty () )
	  break;
	result += part;
	part.clear ();
      }
    return result;
  }
}
