277 lines
5.1 KiB
C++
277 lines
5.1 KiB
C++
/**
|
|
* @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();
|
|
}
|