| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648 | // \file child.cpp
//
// Copyright (C) 2014 MicroNeil Research Corporation.
//
// This program is part of the MicroNeil Research Open Library Project. For
// more information go to http://www.microneil.com/OpenLibrary/index.html
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA
//==============================================================================
#ifndef WIN32
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <cstring>
#include <cerrno>
#endif
#include <iostream> // Temporary.
#include <stdexcept>
#include "child.hpp"
namespace CodeDweller {
  Child::Child(std::vector<std::string> args, size_t bufSize) :
    readStreambuf(bufSize),
    writeStreambuf(bufSize),
    reader(&readStreambuf),
    writer(&writeStreambuf),
    cmdArgs(args) {
    init();
  }
  Child::Child(std::string childpath, size_t bufSize) :
    readStreambuf(bufSize),
    writeStreambuf(bufSize),
    reader(&readStreambuf),
    writer(&writeStreambuf),
    cmdline(childpath) {
    cmdArgs.push_back(childpath);
    init();
  }
  Child::~Child() {
    // Close handles.
  }
  void
  Child::init() {
    if (cmdArgs.empty()) {
      throw std::invalid_argument("A child executable must be specified.");
    }
    reader.exceptions(std::istream::failbit | std::istream::badbit);
    writer.exceptions(std::ostream::failbit | std::ostream::badbit);
    childStarted = false;
    childExited = false;
    exitCodeObtainedFlag = false;
    exitCode = 0;
  }
  void
  Child::run() {
    if (childStarted) {
      throw std::logic_error("Child process was active when "
			     "run() was called");
    }
#ifdef WIN32
    // Set the bInheritHandle flag so pipe handles are inherited. 
    SECURITY_ATTRIBUTES securityAttributes; 
    securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); 
    securityAttributes.bInheritHandle = true; 
    securityAttributes.lpSecurityDescriptor = NULL; 
    // Create a pipe for the child process's STDOUT.
    HANDLE childStdOutAtChild;
    HANDLE childStdOutAtParent;
    HANDLE childStdInAtChild;
    HANDLE childStdInAtParent;
    int bufferSize = 0;
    if (!CreatePipe(&childStdOutAtParent,
		    &childStdOutAtChild,
		    &securityAttributes,
		    bufferSize)) {
      throw std::runtime_error("Error from CreatePipe for stdout:  " +
			       getErrorText());
    }
    // Ensure the read handle to the pipe for STDOUT is not inherited.
    int inheritFlag = 0;
    if (!SetHandleInformation(childStdOutAtParent,
			      HANDLE_FLAG_INHERIT,
			      inheritFlag) ) {
      throw std::runtime_error("Error from GetHandleInformation for stdout:  " +
			       getErrorText());
    }
    // Create a pipe for the child process's STDIN. 
    if (! CreatePipe(&childStdInAtChild,
		     &childStdInAtParent,
		     &securityAttributes,
		     bufferSize)) {
      throw std::runtime_error("Error from CreatePipe for stdin:  " +
			       getErrorText());
    }
    // Ensure the write handle to the pipe for STDIN is not inherited. 
    if (!SetHandleInformation(childStdInAtParent,
			      HANDLE_FLAG_INHERIT,
			      inheritFlag)) {
      throw std::runtime_error("Error from GetHandleInformation for stdin:  " +
			       getErrorText());
    }
 
    // Set up members of the PROCESS_INFORMATION structure. 
    PROCESS_INFORMATION processInfo; 
    std::fill((char *) &processInfo,
	      ((char *) &processInfo) + sizeof(PROCESS_INFORMATION),
	      0);
 
    // Set up members of the STARTUPINFO structure.  This structure
    // specifies the STDIN and STDOUT handles for redirection.
    STARTUPINFO startInfo;
    std::fill((char *) &startInfo,
	      ((char *) &startInfo) + sizeof(STARTUPINFO),
	      0);
    startInfo.cb = sizeof(STARTUPINFO); 
    startInfo.hStdError = childStdOutAtChild;
    startInfo.hStdOutput = childStdOutAtChild;
    startInfo.hStdInput = childStdInAtChild;
    startInfo.dwFlags |= STARTF_USESTDHANDLES;
 
    // Assemble the command line.
    std::string cmdline;
    if (cmdArgs.size() == 1) {
      cmdline = cmdArgs[0];
    } else {
      // Append all but last command-line arguments.
      for (size_t i = 0; i < cmdArgs.size() - 1; i++) {
	cmdline += cmdArgs[i] + " ";
      }
      cmdline += cmdArgs.back(); // Append last command-line argument.
    }
    // Create the child process. 
    bool status;
 
    status = CreateProcess(NULL, 
			   (char *) cmdline.c_str(),
			   NULL,          // process security attributes 
			   NULL,          // primary thread security attributes 
			   true,          // handles are inherited 
			   0,             // creation flags 
			   NULL,          // use parent's environment 
			   NULL,          // use parent's current directory 
			   &startInfo,    // STARTUPINFO pointer 
			   &processInfo); // receives PROCESS_INFORMATION 
    // If an error occurs, exit the application. 
    if (!status ) {
      throw std::runtime_error("Error from CreateProcess with "
			       "command line \"" + cmdline + "\":  " +
			       getErrorText());
    }
    // Provide the stream buffers with the handles for communicating
    // with the child process.
    readStreambuf.setInputHandle(childStdOutAtParent);
    writeStreambuf.setOutputHandle(childStdInAtParent);
    // Save the handles to the child process and its primary thread.
    childProcess = processInfo.hProcess;
    childThread = processInfo.hThread;
    // Close the child's end of the pipes.
    if (!CloseHandle(childStdOutAtChild)) {
      throw std::runtime_error("Error closing the child process "
			       "stdout handle:  " + getErrorText());
    }
     
    if (!CloseHandle(childStdInAtChild)) {
      throw std::runtime_error("Error closing the child process "
			       "stdin handle:  " + getErrorText());
    }
#else
    // Create the pipes for the stdin and stdout.
    int childStdInPipe[2];
    int childStdOutPipe[2];
    if (pipe(childStdInPipe) != 0) {
      throw std::runtime_error("Error creating pipe for stdin:  " +
			       getErrorText());
    }
    if (pipe(childStdOutPipe) != 0) {
      close(childStdInPipe[0]);
      close(childStdInPipe[1]);
      throw std::runtime_error("Error creating pipe for stdout:  " +
			       getErrorText());
    }
    // Create the child process. 
    childPid = fork();
    if (-1 == childPid) {
      for (int i = 0; i < 2; i++) {
	close(childStdInPipe[i]);
	close(childStdOutPipe[i]);
      }
      throw std::runtime_error("Error creating child process:  " +
			       getErrorText());
    }
 
    if (0 == childPid) {
      // The child executes this.  Redirect stdin.
      if (dup2(childStdInPipe[0], STDIN_FILENO) == -1) {
	std::string errMsg;
	// Send message to parent.
	errMsg = "Error redirecting stdin in the child:  " + getErrorText();
	write(childStdOutPipe[1], errMsg.data(), errMsg.size());
	exit(-1);
      }
      // Redirect stdout.
      if (dup2(childStdOutPipe[1], STDOUT_FILENO) == -1) {
	std::string errMsg;
	// Send message to parent.
	errMsg = "Error redirecting stdout in the child:  " + getErrorText();
	write(childStdOutPipe[1], errMsg.data(), errMsg.size());
	exit(-1);
      }
      // Close pipes.
      if ( (close(childStdInPipe[0]) != 0) ||
	   (close(childStdInPipe[1]) != 0) ||
	   (close(childStdOutPipe[0]) != 0) ||
	   (close(childStdOutPipe[1]) != 0) ) {
	std::string errMsg;
	// Send message to parent.
	errMsg = "Error closing the pipes in the child:  " + getErrorText();
	write(STDOUT_FILENO, errMsg.data(), errMsg.size());
	exit(-1);
      }
      // Prepare the arguments.
      std::vector<const char *> execvArgv;
      for (auto &arg : cmdArgs) {
	execvArgv.push_back(arg.c_str());
      }
      execvArgv.push_back((char *) NULL);
      // Run the child process image.
      (void) execv(execvArgv[0], (char * const *) &(execvArgv[0]));
      // Error from exec.
      std::string errMsg;
      // Send message to parent.
      errMsg = "Error from exec:  " + getErrorText();
      write(STDOUT_FILENO, errMsg.data(), errMsg.size());
      exit(-1);
    }
    // std::cout << "Child pid:  " << childPid << std::endl; // DEBUG.
    // Provide the stream buffers with the file descriptors for
    // communicating with the child process.
    readStreambuf.setInputFileDescriptor(childStdOutPipe[0]);
    writeStreambuf.setOutputFileDescriptor(childStdInPipe[1]);
    // Close the child's end of the pipes.
    if ( (close(childStdInPipe[0]) != 0) ||
	 (close(childStdOutPipe[1]) != 0) ) {
      std::string errMsg;
      throw std::runtime_error("Error closing child's end of pipes in "
			       "the parent:  " + getErrorText());
    }
#endif
    childStarted = true;
  }
  void
  Child::terminate() {
    if (isDone()) {
      return;
    }
#ifdef WIN32
    if (!TerminateProcess(childProcess, terminateExitCode)) {
#else
    if (kill(childPid, SIGTERM) != 0) {
#endif
      throw std::runtime_error("Error terminating the child process:  " +
			       getErrorText());
    }
  }
  bool
  Child::isDone() {
    if (childExited) {
      return true;
    }
    if (!childStarted) {
      throw std::logic_error("Child process was not started "
			     "when isDone() was called");
    }
    int result;
#ifdef WIN32
    if (!GetExitCodeProcess(childProcess, (LPDWORD) &result)) {
      throw std::runtime_error("Error checking status of child process:  " +
			       getErrorText());
    }
    if (STILL_ACTIVE == result) {
      return false;
    }
    // Child process has exited.  Save the exit code.
    exitCode = result;
    exitCodeObtainedFlag = true;
#else
    int status = 0;
    result = waitpid(childPid, &status, WNOHANG);
    // std::cout << "isDone().  waitpid(" << childPid << ",...) returned " << result << std::endl; // DEBUG
    if (-1 == result) {
      throw std::runtime_error("Error checking status of child process:  " +
			       getErrorText());
    } else if (0 == result) {
      // Child is still running.
      // std::cout << "isDone().  Child is still running..." << std::endl; // DEBUG.
      return false;
    }
    // std::cout << "isDone().  Child exited." << std::endl; // DEBUG.
    if (WIFEXITED(status)) {
      // Child exited normally.
      exitCode = WEXITSTATUS(status);
      exitCodeObtainedFlag = true;
      //std::cout << "isDone().  Child exited normally.  Exit code:  " << exitCode << std::endl; // DEBUG.
    }
#endif
    childExited = true;
    return true;
  }
  int32_t
  Child::result() {
    if (exitCodeObtainedFlag) {
      return exitCode;
    }
    // Check whether the process is running, and get the exit code.
    if (!isDone()) {
      throw std::logic_error("Child process was still running"
			     "when result() was called");
    }
    // Child process has exited.
    if (!exitCodeObtainedFlag) {
      // Exit code is not available.
      throw std::runtime_error("Child process has exited but the exit "
			       "code is not available");
    }
    return exitCode;
  }
  std::string
  Child::getErrorText() {
#ifdef WIN32
    LPVOID winMsgBuf;
    DWORD lastError = GetLastError(); 
    FormatMessage(
		  FORMAT_MESSAGE_ALLOCATE_BUFFER | 
		  FORMAT_MESSAGE_FROM_SYSTEM |
		  FORMAT_MESSAGE_IGNORE_INSERTS,
		  NULL,
		  lastError,
		  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		  (char *) &winMsgBuf,
		  0, NULL );
    std::string errMsg((char *) winMsgBuf);
    LocalFree(winMsgBuf);
    return errMsg;
#else
    return strerror(errno);
#endif
  }
  Child::ReadStreambuf::ReadStreambuf(std::size_t bufferSize) :
#ifdef WIN32
    inputHandle(0),
#else
    inputFileDescriptor(-1),
#endif
    buffer(bufferSize + 1) {
    char *end = &(buffer.front()) + buffer.size();
    // Indicate to underflow that underflow has not been called.
    setg(end, end, end);
  }
#ifdef WIN32
  void
  Child::ReadStreambuf::setInputHandle(HANDLE inHandle) {
    inputHandle = inHandle;
  }
#else
  void
  Child::ReadStreambuf::setInputFileDescriptor(int inFd) {
    inputFileDescriptor = inFd;
  }
#endif
  std::streambuf::int_type
  Child::ReadStreambuf::underflow() {
    // Check for empty buffer.
    if (gptr() < egptr()) {
      // Not empty.
      return traits_type::to_int_type(*gptr());
    }
    // Need to fill the buffer.
    char *base = &(buffer.front());
    char *start = base;
    // Check whether this is the first fill.
    if (eback() == base) {
      // Not the first fill.  Copy one putback character.
      *(eback()) = *(egptr() - 1);
      start++;
    }
    // start points to the start of the buffer.  Fill buffer.
#ifdef WIN32
    DWORD nBytesRead;
    if (!ReadFile(inputHandle,
		  start,
		  buffer.size() - (start - base),
		  &nBytesRead,
		  NULL)) {
      return traits_type::eof();
    }
#else
    size_t nBytesRead;
    nBytesRead = read(inputFileDescriptor,
		      start,
		      buffer.size() - (start - base));
    if (-1 == nBytesRead) {
      return traits_type::eof();
    }
#endif
    // Check for EOF.
    if (0 == nBytesRead) {
      return traits_type::eof();
    }
    // Update buffer pointers.
    setg(base, start, start + nBytesRead);
    return traits_type::to_int_type(*gptr());
  }
  Child::WriteStreambuf::WriteStreambuf(std::size_t bufferSize) :
#ifdef WIN32
    outputHandle(0),
#else
    outputFileDescriptor(-1),
#endif
    buffer(bufferSize + 1) {
    char *base = &(buffer.front());
    // Indicate to overflow that overflow has not been called.
    setp(base, base + buffer.size() - 1);
  }
#ifdef WIN32
  void
  Child::WriteStreambuf::setOutputHandle(HANDLE outHandle) {
    outputHandle = outHandle;
  }
#else
  void
  Child::WriteStreambuf::setOutputFileDescriptor(int outFd) {
    outputFileDescriptor = outFd;
  }
#endif
  void
  Child::WriteStreambuf::flushBuffer() {
    // Write.
    std::ptrdiff_t nBytes = pptr() - pbase();
#ifdef WIN32
    DWORD nBytesWritten;
    if (!WriteFile(outputHandle,
		   pbase(),
		   nBytes,
		   &nBytesWritten,
		   NULL)) {
      // Clear the output buffer.
      pbump(-nBytes);
      throw std::runtime_error("Error writing to child process:  " +
			       getErrorText());
    }
#else
    size_t nBytesWritten;
    nBytesWritten = write(outputFileDescriptor, pbase(), nBytes);
#endif
    // Clear the output buffer.
    pbump(-nBytes);
    if (nBytes != nBytesWritten) {
      throw std::runtime_error("Not all data was written to to child "
			       "process:  " + getErrorText());
    }
    return;
  }
  std::streambuf::int_type
  Child::WriteStreambuf::overflow(int_type ch) {
    // Check whether we're writing EOF.
    if (traits_type::eof() != ch) {
      // Not writing EOF.
      *(pptr()) = ch;
      pbump(1);
      // Write.
      flushBuffer();
      // Success.
      return ch;
    }
    return traits_type::eof();
  }
  int
  Child::WriteStreambuf::sync() {
    flushBuffer(); // Throws exception on failure.
    // Success.
    return 1;
  }
}
 |