|
|
@@ -32,7 +32,9 @@ |
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include "CodeDweller/timing.hpp"
|
|
|
|
#include "CodeDweller/child.hpp"
|
|
|
|
|
|
|
|
namespace CodeDweller {
|
|
|
@@ -692,4 +694,860 @@ namespace CodeDweller { |
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Child::CircularBuffer::CircularBuffer(size_t maxSize) :
|
|
|
|
buffer(maxSize),
|
|
|
|
capacity(maxSize) {
|
|
|
|
|
|
|
|
iBegin = 0;
|
|
|
|
iEnd = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Child::CircularBuffer::empty() const {
|
|
|
|
return (iBegin == iEnd);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t Child::CircularBuffer::nUsed() const {
|
|
|
|
return capacity - nFree();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::CircularBuffer::clear() {
|
|
|
|
iBegin = 0;
|
|
|
|
iEnd = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t Child::CircularBuffer::nFree() const {
|
|
|
|
|
|
|
|
if (iBegin <= iEnd) {
|
|
|
|
return capacity - (iEnd - iBegin);
|
|
|
|
}
|
|
|
|
return iBegin - iEnd;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::CircularBuffer::put(char const *ptr, size_t nBytes) {
|
|
|
|
|
|
|
|
for (size_t i = 0; i < nBytes; i++) {
|
|
|
|
buffer[iEnd] = *ptr;
|
|
|
|
ptr++;
|
|
|
|
nextIndex(iEnd);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::CircularBuffer::getAndErase(std::string &buf, size_t nBytes) {
|
|
|
|
|
|
|
|
if ( (0 == nBytes) || (nBytes > nUsed()) ) {
|
|
|
|
nBytes = nUsed();
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.clear();
|
|
|
|
if (buf.capacity() < nBytes) {
|
|
|
|
buf.reserve(nBytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < nBytes; i++) {
|
|
|
|
buf.push_back(buffer[iBegin]);
|
|
|
|
nextIndex(iBegin);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Child::Child(std::vector<std::string> const &args,
|
|
|
|
size_t bufSize,
|
|
|
|
std::uint16_t nominalAboveMinPollTime_ms,
|
|
|
|
std::uint16_t deltaPollTime_ms) :
|
|
|
|
bufferCapacity(bufSize),
|
|
|
|
readBuffer(bufferCapacity),
|
|
|
|
writeBuffer(bufferCapacity),
|
|
|
|
nominalPollTime_ms(nominalAboveMinPollTime_ms +
|
|
|
|
CodeDweller::MinimumSleeperTime),
|
|
|
|
maximumPollTime_ms(nominalPollTime_ms + deltaPollTime_ms) {
|
|
|
|
|
|
|
|
init();
|
|
|
|
open(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
Child::Child(std::string const &childpath,
|
|
|
|
size_t bufSize,
|
|
|
|
std::uint16_t nominalAboveMinPollTime_ms,
|
|
|
|
std::uint16_t deltaPollTime_ms) :
|
|
|
|
bufferCapacity(bufSize),
|
|
|
|
readBuffer(bufferCapacity),
|
|
|
|
writeBuffer(bufferCapacity),
|
|
|
|
nominalPollTime_ms(nominalAboveMinPollTime_ms +
|
|
|
|
CodeDweller::MinimumSleeperTime),
|
|
|
|
maximumPollTime_ms(nominalPollTime_ms + deltaPollTime_ms) {
|
|
|
|
|
|
|
|
init();
|
|
|
|
open(childpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
Child::Child(size_t bufSize,
|
|
|
|
std::uint16_t nominalAboveMinPollTime_ms,
|
|
|
|
std::uint16_t deltaPollTime_ms) :
|
|
|
|
bufferCapacity(bufSize),
|
|
|
|
readBuffer(bufferCapacity),
|
|
|
|
writeBuffer(bufferCapacity),
|
|
|
|
nominalPollTime_ms(nominalAboveMinPollTime_ms +
|
|
|
|
CodeDweller::MinimumSleeperTime),
|
|
|
|
maximumPollTime_ms(nominalPollTime_ms + deltaPollTime_ms) {
|
|
|
|
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
Child::~Child() {
|
|
|
|
try {
|
|
|
|
close();
|
|
|
|
} catch (...) {
|
|
|
|
;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::open(std::vector<std::string> const &args) {
|
|
|
|
cmdArgs = args;
|
|
|
|
|
|
|
|
if (isRunning()) {
|
|
|
|
throw std::logic_error("The child process was already active.");
|
|
|
|
}
|
|
|
|
|
|
|
|
init();
|
|
|
|
run();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::open(std::string const &childpath) {
|
|
|
|
|
|
|
|
if (isRunning()) {
|
|
|
|
throw std::logic_error("The child process was already active.");
|
|
|
|
}
|
|
|
|
|
|
|
|
cmdArgs.clear();
|
|
|
|
cmdArgs.push_back(childpath);
|
|
|
|
init();
|
|
|
|
run();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::init() {
|
|
|
|
|
|
|
|
childStarted = false;
|
|
|
|
childExited = false;
|
|
|
|
exitCodeObtainedFlag = false;
|
|
|
|
exitCode = 0;
|
|
|
|
|
|
|
|
readBuffer.clear();
|
|
|
|
nWriteBytes = 0;
|
|
|
|
nTransmitBytes = 0;
|
|
|
|
|
|
|
|
threadsAreRunning = false;
|
|
|
|
stopFlag = true;
|
|
|
|
errorText.clear();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Child::write(std::string const &data) {
|
|
|
|
|
|
|
|
if (!isRunning()) {
|
|
|
|
throw std::logic_error("No child process is running.");
|
|
|
|
}
|
|
|
|
|
|
|
|
#warning Check that this is okay before locking
|
|
|
|
if (data.size() > bufferCapacity - nWriteBytes) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(writeBufferMutex);
|
|
|
|
|
|
|
|
std::copy(data.data(),
|
|
|
|
data.data() + data.size(),
|
|
|
|
&(writeBuffer[nWriteBytes]));
|
|
|
|
|
|
|
|
nWriteBytes += data.size();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t Child::writeAndShrink(std::string &data) {
|
|
|
|
|
|
|
|
if (!isRunning()) {
|
|
|
|
throw std::logic_error("No child process is running.");
|
|
|
|
}
|
|
|
|
|
|
|
|
#warning Check that this is okay before locking
|
|
|
|
size_t nFree = bufferCapacity - nWriteBytes;
|
|
|
|
|
|
|
|
if (0 == nFree) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(writeBufferMutex);
|
|
|
|
|
|
|
|
size_t nBytesToCopy = data.size();
|
|
|
|
|
|
|
|
if (nBytesToCopy > nFree) {
|
|
|
|
nBytesToCopy = nFree;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::copy(data.data(),
|
|
|
|
data.data() + nBytesToCopy,
|
|
|
|
&(writeBuffer[nWriteBytes]));
|
|
|
|
nWriteBytes += nBytesToCopy;
|
|
|
|
|
|
|
|
data.erase(0, nBytesToCopy);
|
|
|
|
|
|
|
|
return nBytesToCopy;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Child::isFinishedWriting() const {
|
|
|
|
return ( (0 == nWriteBytes) &&
|
|
|
|
(0 == nTransmitBytes) );
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t Child::read(std::string &data, size_t nBytes) {
|
|
|
|
|
|
|
|
if (!isRunning()) {
|
|
|
|
throw std::logic_error("No child process is running.");
|
|
|
|
}
|
|
|
|
|
|
|
|
data.clear();
|
|
|
|
|
|
|
|
#warning Check that this is okay before locking
|
|
|
|
if (readBuffer.empty()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(readBufferMutex);
|
|
|
|
|
|
|
|
size_t nBytesToRead = nBytes;
|
|
|
|
|
|
|
|
if (nBytesToRead > readBuffer.nUsed()) {
|
|
|
|
nBytesToRead = readBuffer.nUsed();
|
|
|
|
}
|
|
|
|
|
|
|
|
readBuffer.getAndErase(data, nBytesToRead);
|
|
|
|
|
|
|
|
return data.size();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::close() {
|
|
|
|
|
|
|
|
if (!childStarted) {
|
|
|
|
throw std::logic_error("Child process was not started "
|
|
|
|
"when close() was called");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop the reader and writer threads. Note: None of the error
|
|
|
|
// conditions that cause an exception to be thrown by join()
|
|
|
|
// can ever occur.
|
|
|
|
stopFlag = true;
|
|
|
|
|
|
|
|
// Terminate the child if it's running.
|
|
|
|
if (isRunning()) {
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
if (!TerminateProcess(childProcess, terminateExitCode)) {
|
|
|
|
#else
|
|
|
|
if (kill(childPid, SIGTERM) != 0) {
|
|
|
|
#endif
|
|
|
|
throw std::runtime_error("Error terminating the child process: " +
|
|
|
|
getErrorText());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (threadsAreRunning) {
|
|
|
|
|
|
|
|
readerThread.join();
|
|
|
|
writerThread.join();
|
|
|
|
threadsAreRunning = false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset.
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Child::isRunning() const {
|
|
|
|
return childStarted && !childExited;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Child::errorOccurred(std::string &errorDescription) const {
|
|
|
|
errorDescription = errorText;
|
|
|
|
return !errorDescription.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::run() {
|
|
|
|
|
|
|
|
if (childStarted) {
|
|
|
|
throw std::logic_error("Child process was active when "
|
|
|
|
"run() was called");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmdArgs.empty()) {
|
|
|
|
throw std::invalid_argument("A child executable must be specified.");
|
|
|
|
}
|
|
|
|
|
|
|
|
#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.
|
|
|
|
inputHandle = childStdOutAtParent;
|
|
|
|
outputHandle = 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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Provide the stream buffers with the file descriptors for
|
|
|
|
// communicating with the child process.
|
|
|
|
inputFileDescriptor = childStdOutPipe[0];
|
|
|
|
outputFileDescriptor = 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;
|
|
|
|
|
|
|
|
// Start the reader and writer threads.
|
|
|
|
stopFlag = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
std::thread readerTemp(&Child::readFromChild, this);
|
|
|
|
readerThread = std::move(readerTemp);
|
|
|
|
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
|
|
|
|
throw std::runtime_error("Error starting reader thread: " +
|
|
|
|
getErrorText());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
std::thread writerTemp(&Child::writeToChild, this);
|
|
|
|
writerThread = std::move(writerTemp);
|
|
|
|
|
|
|
|
} catch (std::exception &e) {
|
|
|
|
|
|
|
|
stopFlag = true;
|
|
|
|
readerThread.join();
|
|
|
|
|
|
|
|
throw std::runtime_error("Error starting writer thread: " +
|
|
|
|
getErrorText());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
threadsAreRunning = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
if (-1 == result) {
|
|
|
|
throw std::runtime_error("Error checking status of child process: " +
|
|
|
|
getErrorText());
|
|
|
|
} else if (0 == result) {
|
|
|
|
|
|
|
|
// Child is still running.
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WIFEXITED(status)) {
|
|
|
|
|
|
|
|
// Child exited normally.
|
|
|
|
exitCode = WEXITSTATUS(status);
|
|
|
|
exitCodeObtainedFlag = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
childExited = true;
|
|
|
|
|
|
|
|
// Stop threads.
|
|
|
|
stopFlag = true;
|
|
|
|
readerThread.join();
|
|
|
|
writerThread.join();
|
|
|
|
threadsAreRunning = false;
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::readFromChild() {
|
|
|
|
|
|
|
|
std::vector<char> rxBuf(bufferCapacity);
|
|
|
|
|
|
|
|
CodeDweller::PollTimer pollTimer(nominalPollTime_ms, maximumPollTime_ms);
|
|
|
|
|
|
|
|
auto sleepTime = std::chrono::milliseconds(maximumPollTime_ms);
|
|
|
|
|
|
|
|
while (!stopFlag) {
|
|
|
|
|
|
|
|
char *bufferPtr;
|
|
|
|
|
|
|
|
bufferPtr = &(rxBuf[0]);
|
|
|
|
|
|
|
|
// Blocking read from the child.
|
|
|
|
#ifdef _WIN32
|
|
|
|
DWORD nBytesRead;
|
|
|
|
|
|
|
|
if (!ReadFile(inputHandle,
|
|
|
|
bufferPtr,
|
|
|
|
bufferCapacity,
|
|
|
|
&nBytesRead,
|
|
|
|
NULL)) {
|
|
|
|
|
|
|
|
if (stopFlag) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
errorText = "Error reading from the child process: ";
|
|
|
|
errorText += getErrorText();
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
ssize_t nBytesRead;
|
|
|
|
|
|
|
|
nBytesRead = ::read(inputFileDescriptor,
|
|
|
|
bufferPtr,
|
|
|
|
bufferCapacity);
|
|
|
|
if (-1 == nBytesRead) {
|
|
|
|
|
|
|
|
if (stopFlag) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
errorText = "Error reading from the child process: ";
|
|
|
|
errorText += getErrorText();
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Copy to the shared read buffer.
|
|
|
|
while ((nBytesRead > 0) && !stopFlag) {
|
|
|
|
|
|
|
|
int nBytesToPut;
|
|
|
|
|
|
|
|
nBytesToPut = nBytesRead;
|
|
|
|
|
|
|
|
#warning Check thread safety
|
|
|
|
if (nBytesToPut > readBuffer.nFree()) {
|
|
|
|
nBytesToPut = readBuffer.nFree();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nBytesToPut > 0) {
|
|
|
|
std::lock_guard<std::mutex> lock(readBufferMutex);
|
|
|
|
|
|
|
|
readBuffer.put(bufferPtr, nBytesToPut);
|
|
|
|
bufferPtr += nBytesToPut;
|
|
|
|
nBytesRead -= nBytesToPut;
|
|
|
|
|
|
|
|
pollTimer.reset();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
pollTimer.pause();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Child::writeToChild() {
|
|
|
|
|
|
|
|
std::vector<char> localWriteBuffer(bufferCapacity);
|
|
|
|
size_t nLocalWriteBytes;
|
|
|
|
|
|
|
|
CodeDweller::PollTimer pollTimer(nominalPollTime_ms, maximumPollTime_ms);
|
|
|
|
|
|
|
|
auto sleepTime = std::chrono::milliseconds(maximumPollTime_ms);
|
|
|
|
|
|
|
|
while (!stopFlag) {
|
|
|
|
|
|
|
|
char *bufferPtr;
|
|
|
|
|
|
|
|
// Poll for data in the shared write buffer.
|
|
|
|
while ((0 == nWriteBytes) && !stopFlag) {
|
|
|
|
pollTimer.pause();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stopFlag) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy from the shared write buffer.
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(writeBufferMutex);
|
|
|
|
|
|
|
|
localWriteBuffer.swap(writeBuffer);
|
|
|
|
nLocalWriteBytes = nWriteBytes;
|
|
|
|
nWriteBytes = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stopFlag) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
pollTimer.reset();
|
|
|
|
|
|
|
|
// Blocking write to the child.
|
|
|
|
bufferPtr = &(localWriteBuffer[0]);
|
|
|
|
|
|
|
|
while (nLocalWriteBytes > 0) {
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
DWORD nBytesWritten;
|
|
|
|
|
|
|
|
if (!WriteFile(outputHandle,
|
|
|
|
bufferPtr,
|
|
|
|
nLocalWriteBytes,
|
|
|
|
&nBytesWritten,
|
|
|
|
NULL)) {
|
|
|
|
|
|
|
|
if (stopFlag) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
errorText = "Error writing to the child process: ";
|
|
|
|
errorText += getErrorText();
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stopFlag) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
ssize_t nBytesWritten;
|
|
|
|
|
|
|
|
nBytesWritten = ::write(outputFileDescriptor,
|
|
|
|
bufferPtr,
|
|
|
|
nLocalWriteBytes);
|
|
|
|
|
|
|
|
if (stopFlag) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (-1 == nBytesWritten) {
|
|
|
|
|
|
|
|
if (ENOSPC != errno) {
|
|
|
|
|
|
|
|
// Some error other than no space.
|
|
|
|
errorText = "Error writing to the child process: ";
|
|
|
|
errorText += getErrorText();
|
|
|
|
goto exit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
nLocalWriteBytes -= nBytesWritten;
|
|
|
|
bufferPtr += nBytesWritten;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
exit: return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|