| /** |
| * @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(); |
| } |