/**
 * @file child_reader.cpp
 * Facility for reading from child processes
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Philippe Elie
 * @author John Levon
 */

#include <unistd.h>
#include <sys/wait.h>
#include <limits.h>

#include <cerrno>
#include <sstream>
#include <iostream>
#include <cstring>
#include <cstdlib>

#include "op_libiberty.h"
#include "child_reader.h"

using namespace std;

child_reader::child_reader(string const & cmd, vector<string> const & args)
	:
	fd1(-1), fd2(-1),
	pos1(0), end1(0),
	pos2(0), end2(0),
	pid(0),
	first_error(0),
	buf2(0), sz_buf2(0),
	buf1(new char[PIPE_BUF]),
	process_name(cmd),
	is_terminated(true),
	terminate_on_exception(false),
	forked(false)
{
	exec_command(cmd, args);
}


child_reader::~child_reader()
{
	terminate_process();
	delete [] buf1;
	if (buf2) {
		// allocated through C alloc
		free(buf2);
	}
}


void child_reader::exec_command(string const & cmd, vector<string> const & args)
{
	int pstdout[2];
	int pstderr[2];

	if (pipe(pstdout) == -1 || pipe(pstderr) == -1) {
		first_error = errno;
		return;
	}

	pid = fork();
	switch (pid) {
		case -1:
			first_error = errno;
			return;

		case 0: {
			char const ** argv = new char const *[args.size() + 2];
			size_t i;
			argv[0] = cmd.c_str();

			for (i = 1 ; i <= args.size() ; ++i)
				argv[i] = args[i - 1].c_str();

			argv[i] = 0;

			// child: we can cleanup a few fd
			close(pstdout[0]);
			dup2(pstdout[1], STDOUT_FILENO);
			close(pstdout[1]);
			close(pstderr[0]);
			dup2(pstderr[1], STDERR_FILENO);
			close(pstderr[1]);

			execvp(cmd.c_str(), (char * const *)argv);

			int ret_code = errno;

			// we can communicate with parent by writing to stderr
			// and by returning a non zero error code. Setting
			// first_error in the child is a non-sense

			// we are in the child process: so this error message
			// is redirect to the parent process
			cerr << "Couldn't exec \"" << cmd << "\" : "
			     << strerror(errno) << endl;
			exit(ret_code);
		}

		default:;
			// parent: we do not write on these fd.
			close(pstdout[1]);
			close(pstderr[1]);
			forked = true;
			break;
	}

	fd1 = pstdout[0];
	fd2 = pstderr[0];

	is_terminated = false;

	return;
}


bool child_reader::block_read()
{
	fd_set read_fs;

	FD_ZERO(&read_fs);
	FD_SET(fd1, &read_fs);
	FD_SET(fd2, &read_fs);

	if (select(max(fd1, fd2) + 1, &read_fs, 0, 0, 0) >= 0) {
		if (FD_ISSET(fd1, &read_fs)) {
			ssize_t temp = read(fd1, buf1, PIPE_BUF);
			if (temp >= 0)
				end1 = temp;
			else
				end1 = 0;
		}

		if (FD_ISSET(fd2, &read_fs)) {
			if (end2 >= sz_buf2) {
				sz_buf2 = sz_buf2 ? sz_buf2 * 2 : PIPE_BUF;
				buf2 = (char *)xrealloc(buf2, sz_buf2);
			}

			ssize_t temp = read(fd2, buf2 + end2, sz_buf2 - end2);
			if (temp > 0)
				end2 += temp;
		}
	}

	bool ret = !(end1 == 0 && end2 == 0);

	if (end1 == -1)
		end1 = 0;
	if (end2 == -1)
		end2 = 0;

	return ret;
}


bool child_reader::getline(string & result)
{
	// some stl lacks string::clear()
	result.erase(result.begin(), result.end());

	bool ok = true;
	bool ret = true;
	bool can_stop = false;
	do {
		int temp = end2;
		if (pos1 >= end1) {
			pos1 = 0;
			ret = block_read();
		}

		// for efficiency try to copy as much as we can of data
		ssize_t temp_pos = pos1;
		while (temp_pos < end1 && ok) {
			char ch = buf1[temp_pos++];
			if (ch == '\n')
				ok = false;
		}

		// !ok ==> endl has been read so do not copy it.
		result.append(&buf1[pos1], (temp_pos - pos1) - !ok);

		if (!ok || !end1)
			can_stop = true;

		// reading zero byte from stdout don't mean than we exhausted
		// all stdout output, we must continue to try until reading
		// stdout and stderr return zero byte.
		if (ok && temp != end2)
			can_stop = false;

		pos1 = temp_pos;
	} while (!can_stop);

	// Is this correct ?
	return end1 != 0 || result.length() != 0;
}


bool child_reader::get_data(ostream & out, ostream & err)
{
	bool ret = true;
	while (ret) {
		ret = block_read();

		out.write(buf1, end1);
		err.write(buf2, end2);

		end1 = end2 = 0;
	}

	return first_error == 0;
}


int child_reader::terminate_process()
{
	// can be called explicitely or by dtor,
	// we must protect against multiple call
	if (!is_terminated) {
		int ret;
		waitpid(pid, &ret, 0);

		is_terminated = true;

		if (WIFEXITED(ret)) {
			first_error = WEXITSTATUS(ret) | WIFSIGNALED(ret);
		} else if (WIFSIGNALED(ret)) {
			terminate_on_exception = true;
			first_error = WTERMSIG(ret);
		} else {
			// FIXME: this seems impossible, waitpid *must* wait
			// and either the process terminate normally or through
			// a signal.
			first_error = -1;
		}
	}

	if (fd1 != -1) {
		close(fd1);
		fd1 = -1;
	}
	if (fd2 != -1) {
		close(fd2);
		fd2 = -1;
	}

	return first_error;
}


string child_reader::error_str() const
{
	ostringstream err;
	if (!forked) {
		err << string("unable to fork, error: ")
		    << strerror(first_error);
	} else if (is_terminated) {
		if (first_error) {
			if (terminate_on_exception) {
				err << process_name << " terminated by signal "
				    << first_error;
			} else {
				err << process_name << " return "
				    << first_error;
			}
		}
	}

	return err.str();
}