/** * @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 #include #include #include #include #include #include #include #include "op_libiberty.h" #include "child_reader.h" using namespace std; child_reader::child_reader(string const & cmd, vector 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 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(); }