#include "config.h" #include #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SIGNAL_H #include #endif #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include "asserts.h" #include "error.h" #include "fs.h" #include "exec.h" /** C'tor */ execute::execute() { clear(); } /** D'tor */ execute::~execute() { clear(); } /** Reset the execute class to default values, kill the child processif one is * running */ void execute::clear(void) { if (child_running() && is_parent()) signal_child(SIGKILL); m_fd1[0] = 0; m_fd1[1] = 0; m_fd2[0] = 0; m_fd2[1] = 0; m_fd3[0] = 0; m_fd3[1] = 0; m_pid = 0; m_status = 0; m_in_eof = false; m_out_eof = false; m_err_eof = false; m_child_started = false; } /** Generic signal handler */ static void _signal_handler(int signo) { throw(ERROR(0,"Tried to write to a read-only pipe")); } /** Fork a child process */ void execute::fork(void) { clear(); if (signal(SIGPIPE, _signal_handler) == SIG_ERR) throw(ERROR(errno,"Could not catch SIGPIPIE signal")); if (pipe(m_fd1) < 0) throw(ERROR(errno,"Could not create pipe for in")); if (pipe(m_fd2) < 0) throw(ERROR(errno,"Could not create pipe for out")); if (pipe(m_fd3) < 0) throw(ERROR(errno,"Could not create pipe for err")); if ((m_pid = ::fork()) < 0) throw(ERROR(errno,"Could not fork")); if (m_pid > 0) { // Parent process close(m_fd1[0]); close(m_fd2[1]); close(m_fd3[1]); m_child_started = true; } else { // Child process close(m_fd1[1]); close(m_fd2[0]); close(m_fd3[0]); } } /** Returns true if called by the child */ bool execute::is_child(void) { if (m_pid == 0) { return(true); } return(false); } /** Returns true if called by the parent */ bool execute::is_parent(void) { bool value; value = !is_child(); return(value); } /** Returns the PID */ pid_t execute::my_pid(void) { pid_t value; value = pid(); return(value); } /** Called by the child to exit with a particular code */ void execute::exit(int code) { if (is_child()) ::exit(code); } /** Called by the child to reroute the child's stdin, stdout, and stderr to * the parent */ void execute::reroute_stdio(void) { if (!is_child()) return; if (m_fd1[0] != STDIN_FILENO) { if (dup2(m_fd1[0], STDIN_FILENO) != STDIN_FILENO) { error e(errno,ERROR_INSTANCE("dup2() failed for stdin")); std::cerr << e << std::endl; exit(127); } close(m_fd1[0]); } if (m_fd2[1] != STDOUT_FILENO) { if (dup2(m_fd2[1], STDOUT_FILENO) != STDOUT_FILENO) { error e(errno,ERROR_INSTANCE("dup2() failed for stdout")); std::cerr << e << std::endl; exit(127); } close(m_fd2[1]); } if (m_fd3[1] != STDERR_FILENO) { if (dup2(m_fd3[1], STDERR_FILENO) != STDERR_FILENO) { error e(errno,ERROR_INSTANCE("dup2() failed for stderr")); std::cerr << e << std::endl; exit(127); } close(m_fd3[1]); } } /** Returns the child's PID */ pid_t execute::child_pid(void) { return(m_pid); } /** Send a signal to the child */ void execute::signal_child(int signal_no) { kill(m_pid, signal_no); } /** Send a HUP signal to the child */ void execute::hup_child(void) { signal_child(SIGHUP); } /** Send a KILL signal to the child */ void execute::kill_child(void) { signal_child(SIGKILL); } /** Wait for the child to exit */ void execute::wait(void) { if (is_parent()) ::wait(&m_status); } /** Check the child's status */ pid_t execute::check_child_(void) { pid_t value; value = waitpid(m_pid, &m_status, WNOHANG|WUNTRACED); return(value); } /** Returns true if the child has been started */ bool execute::child_started(void) const { return(m_child_started); } /** Returns true if the child is running */ bool execute::child_running(void) { bool value; // value = (check_child_() == 0); // Should this be != -1 instead? value = (check_child_() != -1); return(value); } /** Returns true of the child has existed */ bool execute::child_exited(void) { bool value; value = (check_child_() == -1); return(value); } /** Returns true if the child has exited normally */ bool execute::child_exited_normally(void) { bool value; // TODO: Something's screwy here... // value = (child_exited() && WIFEXITED(m_status)); value = (child_exited() && (child_exit_code() == 0)); return(value); } /** Returns true if the child was signaled */ bool execute::child_signaled(void) { bool value; check_child_(); value = WIFSIGNALED(m_status); return(value); } /** Return the child's exit code */ int execute::child_exit_code(void) { int value; // TODO: Something's screwy here... check_child_(); value = WEXITSTATUS(m_status); return(value); } /** If the child was signaled, return the signal number */ int execute::child_signal_no(void) { int value; check_child_(); if (child_exited()) { value = WTERMSIG(m_status); return(value); } else { value = WSTOPSIG(m_status); return(value); } } /** Return a file descriptor for I/O between parent and child If called by the parent, a writeable file descriptor is returned. If called by the child, a readable file descriptor is returned. If retroute_stdio() was called by the child, then the returned file descriptor is the same as that used by the child for stdin. */ int execute::in_fd(void) { if (is_parent()) { // Return write end of input pipe return(m_fd1[1]); } else { // Return read end of input pipe return(m_fd1[0]); } } /** Return a file descriptor for I/O between parent a child. If called by the parent, a readable file descriptor is returned. If called by the child, a writable file descriptor is returned. If reroute_stdio() was called by the child, then the returned file descriptor is the same as that used by the child for stdout. */ int execute::out_fd(void) { if (is_parent()) { // Return read end of output pipe return(m_fd2[0]); } else { // Return write end of output pipe return(m_fd2[1]); } } /** Return a file descriptor for I/O between parent and child. If called by the parent, a readable file descriptor is returned. If called by the child, a writeable file descriptor is returned. If reroute_stdio() was called by the child, then the returned file descriptior is the same as that used by the child for stderr. */ int execute::err_fd(void) { if (is_parent()) { // return read end of error pipe return(m_fd3[0]); } else { // return write end of error pipe return(m_fd3[1]); } } /** Return true if the file descriptor is ready to be written to */ bool execute::check_write_ready_(int fd) { struct timeval timeout = { 0, 0 }; fd_set wset; FD_ZERO(&wset); FD_SET(fd, &wset); select(fd+1, 0, &wset, 0, &timeout); if (FD_ISSET(fd, &wset)) { return(true); } return(false); } /** Return true if the file descriptor has input ready to be read */ bool execute::check_read_ready_(int fd) { struct timeval timeout = { 0, 0 }; fd_set rset; FD_ZERO(&rset); FD_SET(fd, &rset); select(fd+1, &rset, 0, 0, &timeout); if (FD_ISSET(fd, &rset)) { return(true); } return(false); } /** Execute a command, rerouting stdin, stdout, and stderr to parent */ void execute::exec(const std::string command) { fork(); if (is_parent()) return; reroute_stdio(); execl("/bin/sh", "sh", "-c", command.c_str(), (char *)0); exit(127); } /** Check I/O for input If called by the parent, check if ready to write to child's input. If called by the child, check if input is ready to be read. If reroute_stdio() was called by the child, then this pipe is the same as used by the child for stdin. */ bool execute::in_ready(void) { bool value; if (is_parent()) value = check_write_ready_(in_fd()); else value = check_read_ready_(in_fd()); return(value); } /** Check I/O for output If called by the parent, check if output from child is ready to be read. If called by the child, check if output to parent is ready to be written to. If reroute_stdio() was called by the child, then this pipe is the same as used by the child for stdout. */ bool execute::out_ready(void) { bool value; if (is_parent()) value = check_read_ready_(out_fd()); else value = check_write_ready_(out_fd()); return(value); } /** Check I/O for output If called by the parent, check if output from child is ready to be read. If called by the child, check if output to parent is ready to be written to. If reroute_stdio() was called by the child, then this pipe is the same as used by the child for stderr. */ bool execute::err_ready(void) { bool value; if (is_parent()) value = check_read_ready_(err_fd()); else value = check_write_ready_(err_fd()); return(value); } /** Check for input EOF */ bool execute::in_eof(void) { return(m_in_eof); } /** Check for output EOF */ bool execute::out_eof(void) { return(m_out_eof); } /** Check for err EOF */ bool execute::err_eof(void) { return(m_err_eof); } /** Allow child to read input from in_fd() */ int execute::in_read(char* buf, const int len) { int n; int err; n = read(in_fd(), buf, len); err = errno; errno = 0; if (n > 0) { return(n); } if (child_started() && !child_running() && (err != EAGAIN)) m_in_eof = true; return(0); } /** Allow parent to write output to in_fd() */ int execute::in_write(const char* buf, const int len) { int n; int err; n = write(in_fd(), buf, len); err = errno; errno = 0; if (n > 0) { return(n); } if (child_started() && !child_running() && (err != EAGAIN)) m_in_eof = true; return(0); } /** Allow parent to read out_fd() */ int execute::out_read(char* buf, const int len) { int n; int err; n = read(out_fd(), buf, len); err = errno; errno = 0; if (n > 0) { return(n); } if (child_started() && !child_running() && (err != EAGAIN)) m_out_eof = true; return(0); } /** Allow child to write to out_fd() */ int execute::out_write(const char* buf, const int len) { int n; int err; n = write(out_fd(), buf, len); err = errno; errno = 0; if (n > 0) { return(n); } if (child_started() && !child_running() && (err != EAGAIN)) m_out_eof = true; return(0); } /** Allow parent to read from err_fd() */ int execute::err_read(char* buf, const int len) { int n; int err; n = read(err_fd(), buf, len); err = errno; errno = 0; if (n > 0) { return(n); } if (child_started() && !child_running() && (err != EAGAIN)) m_err_eof = true; return(0); } /** Allow child to write to err_fd() */ int execute::err_write(const char* buf, const int len) { int n; int err; n = write(err_fd(), buf, len); err = errno; errno = 0; if (n > 0) { return(n); } if (child_started() && !child_running() && (err != EAGAIN)) m_err_eof = true; return(0); } /** Dump execute object information -- used for debugging */ void execute::print(std::ostream& out) { out << "execute::is_child() = " << is_child() << std::endl; out << "execute::is_parent() = " << is_parent() << std::endl; out << "execute::my_pid() = " << my_pid() << std::endl; out << "execute::child_running() = " << child_running() << std::endl; out << "execute::child_exited() = " << child_exited() << std::endl; out << "execute::child_exited_normally() = " << child_exited_normally() << std::endl; out << "execute::child_signaled() = " << child_signaled() << std::endl; out << "execute::child_exit_code() = " << child_exit_code() << std::endl; out << "execute::child_signal_no() = " << child_signal_no() << std::endl; out << "execute::in_fd() = " << in_fd() << std::endl; out << "execute::out_fd() = " << out_fd() << std::endl; out << "execute::err_fd() = " << err_fd() << std::endl; out << "execute::in_ready() = " << in_ready() << std::endl; out << "execute::out_ready() = " << out_ready() << std::endl; out << "execute::err_ready() = " << err_ready() << std::endl; } /** Convenience function to call execute::print() */ std::ostream& operator << (std::ostream& out, execute& exe) { exe.print(out); return(out); }