// Copyright (c) 2007, Monju System Architects
//
// Released under the Monju-Public copyright license:
//
// Permission is hereby granted, free of charge, to any person or entity
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies and modifications
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of published, sold, or
// ownership-transferred versions of the Software. The notices are not
// required for binary-only releases or binary-only derivatives of the
// Software, or documentation provided with binary-only releases and
// binary-only derivatives.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// NOTES:
// * The Windows version is untested.
//
// TODO:
// * Add optional environment.
// INCLUDES.
#include "public/subprocess.h"
#include "public/parseArgument.h"
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#endif
// createSubprocess()
//
// Create a subprocess with "pipes" to the child stdin, stderr, and stdout.
// Returns the process ID.
#ifdef _WIN32
PROCESS_INFO createSubprocess(const char* commandLine,
STANDARD_PIPE* pStdin,
STANDARD_PIPE* pStdout,
STANDARD_PIPE* pStderr) {
// SOME TEMPORARY HANDLES.
HANDLE childStandardInputRead = NULL;
HANDLE childStandardInputWrite = NULL;
HANDLE childStandardOutputRead = NULL;
HANDLE childStandardOutputWrite = NULL;
HANDLE childStandardErrorRead = NULL;
HANDLE childStandardErrorWrite = NULL;
// CREATE THE SECURITY ATTRIBUTES OBJECT.
SECURITY_ATTRIBUTES securityAttributes;
memset(&securityAttributes, sizeof(SECURITY_ATTRIBUTES), 0);
securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
securityAttributes.bInheritHandle = true;
securityAttributes.lpSecurityDescriptor = NULL;
// GET A HANDLE TO THE CURRENT PROCESS.
HANDLE currentProcessHandle = GetCurrentProcess();
// CREATE PIPES.
if (!CreatePipe(&childStandardInputRead,
pStdin,
&securityAttributes,
0)) {
throw("CreatePipe(): failed to create child standard input.");
}
if (!CreatePipe(pStdout,
&childStandardOutputWrite,
&securityAttributes,
0)) {
throw("CreatePipe(): failed to create child standard output.");
}
if (!CreatePipe(pStderr,
&childStandardErrorWrite,
&securityAttributes,
0)) {
throw("CreatePipe(): failed to create child standard error.");
}
// MAKE PIPES INHERITABLE.
if (pStdin != NULL) {
if (!SetHandleInformation(childStandardInputRead, HANDLE_FLAG_INHERIT, 0)) {
throw("SetHandleInformation(): failed to set standard input info.");
}
if (!SetHandleInformation(*pStdin, HANDLE_FLAG_INHERIT, 0)) {
throw("SetHandleInformation(): failed to set standard input info.");
}
}
if (pStdout != NULL) {
if (!SetHandleInformation(*pStdout, HANDLE_FLAG_INHERIT, 0)) {
throw("SetHandleInformation(): failed to set standard output info.");
}
if (!SetHandleInformation(childStandardOutputWrite, HANDLE_FLAG_INHERIT, 0)) {
throw("SetHandleInformation(): failed to set standard output info.");
}
}
if (pStderr != NULL) {
if (!SetHandleInformation(pStderr, HANDLE_FLAG_INHERIT, 0)) {
throw("SetHandleInformation(): failed to set standard error info.");
}
if (!SetHandleInformation(childStandardErrorWrite, HANDLE_FLAG_INHERIT, 0)) {
throw("SetHandleInformation(): failed to set standard error info.");
}
}
// SET UP ARGUMENTS FOR CreateProcess().
PROCESS_INFORMATION processInfo;
STARTUPINFO startupInfo;
ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION));
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
startupInfo.cb = sizeof(STARTUPINFO);
if (pStdin) {
startupInfo.hStdInput = childStandardInputRead;
}
if (pStdout) {
startupInfo.hStdOutput = childStandardOutputWrite;
}
if (pStderr) {
startupInfo.hStdError = childStandardErrorWrite;
}
// WINDOWS REQUIRES A WRITABLE COMMAND LINE FIELD.
int length = strlen(commandLine);
char commandLineCopy[length+1];
memcpy(commandLineCopy, commandLine, (length+1) * sizeof(char));
// CREATE THE CHILD PROCESS.
LPVOID pEnvironment = NULL; // INHERIT.
LPCTSTR pWorkingDir = NULL; // INHERIT.
if (!CreateProcess(NULL,
commandLineCopy,
NULL,
NULL,
TRUE,
0,
pEnvironment,
pWorkingDir,
&startupInfo,
&processInfo)) {
throw("CreateProcess(): failed to create child process.");
}
}
PROCESS_INFO createSubprocess(char* const* arguments,
STANDARD_PIPE* pStdin,
STANDARD_PIPE* pStdout,
STANDARD_PIPE* pStderr) {
int totalLength = 0;
int numberOfArguments;
int onArgument;
const char* pArgument;
// FOR WINDOWS, DETERMINE THE TOTAL LENGTH OF THE ARGUMENTS.
pArgument = arguments[0];
for(onArgument=0; pArgument != NULL; onArgument++) {
totalLength += strlen(pArgument);
pArgument = arguments[onArgument];
}
numberOfArguments = onArgument;
// ALLOCATE THE BUFFER. WE MIGHT HAVE A LOT OF ESCAPING, SO ALLOCATE FOR THAT.
char pCommandLine[(totalLength * 2) + (onArgument * 2) + 1];
// FILL THE BUFFER. ESCAPE ALL ARGUMENTS.
int onWrite=0;
for(onArgument=0; onArgument<numberOfArguments; onArgument++) {
pCommandLine[onWrite++] = '\"';
pArgument = arguments[onArgument];
for (int onChar=0; onChar<strlen(pArgument); onChar++) {
if (pArgument[onChar] == '\0') {
break;
}
if (pArgument[onChar] == '\"') {
pCommandLine[onWrite++] = '\\';
}
else if (pArgument[onChar] == '\\') {
pCommandLine[onWrite++] = '\\';
}
pCommandLine[onWrite++] = pArgument[onChar];
}
pCommandLine[onWrite++] = '\"';
if (onArgument < (numberOfArguments - 1)) {
pCommandLine[onWrite++] = ' ';
}
}
// CALL THE OTHER FUNCTION.
return createSubprocess(pCommandLine, pStdin, pStdout, pStderr);
}
#else // #ifndef _WIN32
PROCESS_INFO createSubprocess(const char* commandLine,
STANDARD_PIPE* pStdin,
STANDARD_PIPE* pStdout,
STANDARD_PIPE* pStderr) {
char* pStrings[1000];
int position = 0;
int onArgument = 0;
char string[2000];
int stringLength;
int commandLineLength = strlen(commandLine);
int childPid;
// ALLOCATE AND POPULATE THE ARGUMENT STRINGS.
try {
while (position < commandLineLength) {
parseArgument(commandLine, string, &position, 2000);
stringLength = strlen(string) + 1;
pStrings[onArgument] = (char*)malloc(stringLength * sizeof(char));
memcpy(pStrings[onArgument], string, (stringLength * sizeof(char)));
onArgument++;
if (onArgument >= 1000) {
throw("createSubprocess(): buffer overflowed.");
}
}
// NULL TERMINATE THE LIST.
pStrings[onArgument] = NULL;
// CREATE THE SUBPROCESS.
childPid = createSubprocess((char* const*)pStrings, pStdin, pStdout, pStderr);
}
// CLEAN UP.
catch(...) {
while(onArgument > 0) {
onArgument --;
free(pStrings[onArgument]);
}
throw;
}
while(onArgument > 0) {
onArgument --;
free(pStrings[onArgument]);
}
// RETURN THE CHILD PROCESS ID.
return childPid;
}
PROCESS_INFO createSubprocess(char* const* arguments, int* pStdin, int* pStdout, int* pStderr) {
int standardInput[2];
int standardOutput[2];
int standardError[2];
int pid;
// CREATE STDIN, STDOUT, AND STDERR PIPES.
if ( pipe(standardInput) < 0 ) {
throw("createSubprocess(): pipe() failed (1).");
}
if ( pipe(standardOutput) < 0 ) {
close(standardInput[0]);
close(standardInput[1]);
throw("createSubprocess(): pipe() failed (2).");
}
if ( pipe(standardError) < 0 ) {
close(standardInput[0]);
close(standardInput[1]);
close(standardOutput[0]);
close(standardOutput[1]);
throw("createSubprocess(): pipe() failed (3).");
}
// FORK.
pid = fork();
if (pid > 0) { // parent
close(standardInput[0]);
close(standardOutput[1]);
close(standardError[1]);
*pStdin = standardInput[1];
*pStderr = standardOutput[0];
*pStdout = standardOutput[0];
return pid;
}
else if (pid == 0) { // child
close(standardInput[1]);
close(standardOutput[0]);
close(standardOutput[0]);
dup(standardInput[0]);
dup(standardOutput[1]);
dup(standardOutput[1]);
close(0);
close(1);
close(2);
execvp(arguments[0], arguments);
exit(1);
}
// HANDLE FORK ERROR.
close(standardInput[0]);
close(standardInput[1]);
close(standardOutput[0]);
close(standardOutput[1]);
close(standardOutput[0]);
close(standardOutput[1]);
throw("createSubprocess(): fork() failed.");
}
#endif // #indef _WIN32
|