|
|
@@ -20,14 +20,17 @@ |
|
|
|
//==============================================================================
|
|
|
|
|
|
|
|
#include <iostream> // Temporary.
|
|
|
|
|
|
|
|
#include <stdexcept>
|
|
|
|
|
|
|
|
#include "child.hpp"
|
|
|
|
|
|
|
|
namespace CodeDweller {
|
|
|
|
|
|
|
|
Child::Child(std::vector<std::string> args) {
|
|
|
|
Child::Child(std::vector<std::string> args, size_t bufSize) :
|
|
|
|
readStreambuf(bufSize),
|
|
|
|
writeStreambuf(bufSize),
|
|
|
|
reader(&readStreambuf),
|
|
|
|
writer(&writeStreambuf) {
|
|
|
|
|
|
|
|
init();
|
|
|
|
|
|
|
@@ -45,33 +48,34 @@ namespace CodeDweller { |
|
|
|
cmdline += args.back(); // Append last command-line argument.
|
|
|
|
}
|
|
|
|
|
|
|
|
Child::Child(std::string childpath) :
|
|
|
|
Child::Child(std::string childpath, size_t bufSize) :
|
|
|
|
readStreambuf(bufSize),
|
|
|
|
writeStreambuf(bufSize),
|
|
|
|
reader(&readStreambuf),
|
|
|
|
writer(&writeStreambuf),
|
|
|
|
cmdline(childpath) {
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
Child::~Child() {
|
|
|
|
// Close handles.
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Child::init() {
|
|
|
|
childStarted = false;
|
|
|
|
exitCodeObtainedFlag = false;
|
|
|
|
exitCode = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::istream *
|
|
|
|
Child::reader() {
|
|
|
|
return new std::istream(std::cin.rdbuf());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::ostream *
|
|
|
|
Child::writer() {
|
|
|
|
return new std::ostream(std::cout.rdbuf());
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Child::run() {
|
|
|
|
|
|
|
|
if (childStarted) {
|
|
|
|
throw std::logic_error("Child process was active when "
|
|
|
|
"run() was called");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the bInheritHandle flag so pipe handles are inherited.
|
|
|
|
SECURITY_ATTRIBUTES securityAttributes;
|
|
|
|
|
|
|
@@ -81,7 +85,9 @@ namespace CodeDweller { |
|
|
|
|
|
|
|
// Create a pipe for the child process's STDOUT.
|
|
|
|
HANDLE childStdOutAtChild;
|
|
|
|
HANDLE childStdOutAtParent;
|
|
|
|
HANDLE childStdInAtChild;
|
|
|
|
HANDLE childStdInAtParent;
|
|
|
|
int bufferSize = 0;
|
|
|
|
|
|
|
|
if (!CreatePipe(&childStdOutAtParent,
|
|
|
@@ -139,39 +145,59 @@ namespace CodeDweller { |
|
|
|
startInfo.dwFlags |= STARTF_USESTDHANDLES;
|
|
|
|
|
|
|
|
// Create the child process.
|
|
|
|
bool createSuccess;
|
|
|
|
bool status;
|
|
|
|
|
|
|
|
createSuccess = CreateProcess(NULL,
|
|
|
|
(char *) cmdline.c_str(), // command line
|
|
|
|
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
|
|
|
|
|
|
|
|
status = CreateProcess(NULL,
|
|
|
|
(char *) cmdline.c_str(), // command line
|
|
|
|
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 (!createSuccess ) {
|
|
|
|
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;
|
|
|
|
|
|
|
|
childStarted = true;
|
|
|
|
|
|
|
|
// Close the child's end of the pipes.
|
|
|
|
CloseHandle(childStdOutAtChild);
|
|
|
|
CloseHandle(childStdInAtChild);
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Child::terminate() {
|
|
|
|
|
|
|
|
if (isDone()) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!TerminateProcess(childProcess, terminateExitCode)) {
|
|
|
|
throw std::runtime_error("Error terminating the child process: " +
|
|
|
|
getErrorText());
|
|
|
@@ -179,9 +205,55 @@ namespace CodeDweller { |
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Child::isDone() {
|
|
|
|
|
|
|
|
if (exitCodeObtainedFlag) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!childStarted) {
|
|
|
|
throw std::logic_error("Child process was not started "
|
|
|
|
"when isDone() was called");
|
|
|
|
}
|
|
|
|
|
|
|
|
int result;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t
|
|
|
|
Child::result() {
|
|
|
|
|
|
|
|
if (exitCodeObtainedFlag) {
|
|
|
|
|
|
|
|
return exitCode;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!childStarted) {
|
|
|
|
throw std::logic_error("Child process was not started "
|
|
|
|
"when result() was called");
|
|
|
|
}
|
|
|
|
|
|
|
|
int result;
|
|
|
|
|
|
|
|
if (!GetExitCodeProcess(childProcess, (LPDWORD) &result)) {
|
|
|
@@ -189,6 +261,16 @@ namespace CodeDweller { |
|
|
|
getErrorText());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error if the process has not exited.
|
|
|
|
if (STILL_ACTIVE == result) {
|
|
|
|
throw std::logic_error("Child process was active when "
|
|
|
|
"result() was called");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Child process has exited. Save the exit code.
|
|
|
|
exitCode = result;
|
|
|
|
exitCodeObtainedFlag = true;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
@@ -214,4 +296,138 @@ namespace CodeDweller { |
|
|
|
return errMsg;
|
|
|
|
}
|
|
|
|
|
|
|
|
Child::ReadStreambuf::ReadStreambuf(std::size_t bufferSize) :
|
|
|
|
inputHandle(0),
|
|
|
|
buffer(bufferSize + 1) {
|
|
|
|
|
|
|
|
char *end = &(buffer.front()) + buffer.size();
|
|
|
|
|
|
|
|
// Indicate to underflow that underflow has not been called.
|
|
|
|
setg(end, end, end);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Child::ReadStreambuf::setInputHandle(HANDLE inHandle) {
|
|
|
|
|
|
|
|
inputHandle = inHandle;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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 putback characters.
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// start points to the start of the buffer. Fill buffer.
|
|
|
|
DWORD nBytesRead;
|
|
|
|
|
|
|
|
if (!ReadFile(inputHandle,
|
|
|
|
start,
|
|
|
|
buffer.size() - (start - base),
|
|
|
|
&nBytesRead,
|
|
|
|
NULL)) {
|
|
|
|
return traits_type::eof();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) :
|
|
|
|
outputHandle(0),
|
|
|
|
buffer(bufferSize + 1) {
|
|
|
|
|
|
|
|
char *base = &(buffer.front());
|
|
|
|
|
|
|
|
// Indicate to overflow that overflow has not been called.
|
|
|
|
setp(base, base + buffer.size() - 1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Child::WriteStreambuf::setOutputHandle(HANDLE outHandle) {
|
|
|
|
|
|
|
|
outputHandle = outHandle;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Child::WriteStreambuf::flushBuffer() {
|
|
|
|
|
|
|
|
// Write.
|
|
|
|
std::ptrdiff_t nBytes = pptr() - pbase();
|
|
|
|
DWORD nBytesWritten;
|
|
|
|
|
|
|
|
if (!WriteFile(outputHandle,
|
|
|
|
pbase(),
|
|
|
|
nBytes,
|
|
|
|
&nBytesWritten,
|
|
|
|
NULL)) {
|
|
|
|
throw std::runtime_error("Error writing to child process: " +
|
|
|
|
getErrorText());
|
|
|
|
}
|
|
|
|
if (nBytes != nBytesWritten) {
|
|
|
|
throw std::runtime_error("Not all data was written to to child process: " +
|
|
|
|
getErrorText());
|
|
|
|
}
|
|
|
|
pbump(-nBytes);
|
|
|
|
|
|
|
|
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 traits_type::not_eof('a');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
Child::WriteStreambuf::sync() {
|
|
|
|
|
|
|
|
flushBuffer();
|
|
|
|
return 1; // Success.
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|