// \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 #include #include #include #include #include #include #endif #include #include "child.hpp" namespace CodeDweller { Child::Child(std::vector 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."); } childStarted = false; childExited = false; exitCodeObtainedFlag = false; exitCode = 0; } size_t Child::numBytesAvailable() const { return readStreambuf.numBytesAvailable(); } 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 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 size_t Child::ReadStreambuf::numBytesAvailable() const { size_t nBytesAvailable = egptr() - gptr(); #ifdef _WIN32 DWORD lpTotalBytesAvail; if (!PeekNamedPipe(inputHandle, NULL, 0, NULL, &lpTotalBytesAvail, NULL)) { throw std::runtime_error("Error from PeekNamedPipe: " + getErrorText()); } if (lpTotalBytesAvail > 0) { nBytesAvailable++; } #else fd_set readFd; int retVal; struct timeval timeout = {0, 0}; FD_ZERO(&readFd); FD_SET(inputFileDescriptor, &readFd); // Check if input is available. retVal = select(inputFileDescriptor + 1, &readFd, NULL, NULL, &timeout); if (-1 == retVal) { throw std::runtime_error("Error from select(): " + getErrorText()); } else if (retVal > 0) { nBytesAvailable++; } #endif return nBytesAvailable; } 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 ssize_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 ssize_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; } }