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

#ifndef CHILD_READER_H
#define CHILD_READER_H

#include <sys/types.h>

#include <vector>
#include <string>

/**
 * a class to read stdout / stderr from a child process.
 *
 * two interfaces are provided. read line by line: getline() or read all data
 * in one : get_data(). In all case get_data() must be called once to flush the
 * stderr child output
 */
/*
 * FIXME: code review is needed:
 *  - check the getline()/get_data()/block_read() interface.
 *  the expected behavior is:
 *  caller can call getline until nothing is available from the stdout of the
 * child. in this case child stderr is acumulated in buf2 and can be read
 * through get_data(). get_data() is blocking until the child close stderr /
 * stdout (even if the child die by a signal ?). The following corner case must
 * work but I'm unsure if the code reflect this behavior: the last line of the
 * child stdout have not necessarilly a LF terminator. the child can output any
 * size of data in stderr.
 */
class child_reader {
public:
	/** fork a process. use error() to get error code. Do not try to
	 * use other public member interface if error() return non-zero */
	child_reader(std::string const & cmd,
		std::vector<std::string> const & args);

	/** wait for the termination of the child process if this have not
	 * already occur. In this case return code of the child process is not
	 * available. */
	~child_reader();

	/** fill result from on line of stdout of the child process.
	 * must be used as:
	 * child_reader reader(...);
	 * while (reader.getline(line)) .... */
	bool getline(std::string & result);

	/** fill out / err with the stdout / stderr of the child process.
	 * You can call this after calling one or more time getline(...). This
	 * call is blocking until the child die and so on all subsequent
	 * call will fail */
	bool get_data(std::ostream & out, std::ostream & err);

	/** rather to rely on dtor to wait for the termination of the child you
	 * can use terminate_process() to get the return code of the child
	 * process */
	int terminate_process();

	/** return the status of the first error encoutered
	 * != 0 : something feel wrong, use error_str() to get an error
	 * message */
	int error() const { return first_error; }

	/**
	 * return an error message if appropriate, if the process has
	 * been successfully exec'ed and is not terminate the error message
	 * is always empty. Error message is also empty if the child process
	 * terminate successfully. Else three type of error message exist:
	 *  - "unable to fork" followed by sterror(errno)
	 *  - "process_name return xxx"  xxx is return code
	 *  - "process_name terminated by signal xxx" xxx is signal number
	 */
	std::string error_str() const;

private:
	// ctor helper: create the child process.
	void exec_command(std::string const & cmd,
			  std::vector<std::string> const & args);
	// return false when eof condition is reached on fd1. fd2 can have
	// already input in the pipe buffer or in buf2.
	bool block_read();

	int fd1;
	int fd2;
	ssize_t pos1;
	ssize_t end1;
	ssize_t pos2;
	ssize_t end2;
	pid_t pid;
	int first_error;
	// child stderr is handled especially, we need to retain data even
	// if caller read only stdout of the child.
	char * buf2;
	ssize_t sz_buf2;
	char * buf1;
	std::string process_name;
	bool is_terminated;
	bool terminate_on_exception;
	bool forked;
};

#endif // CHILD_READER_H