#ifndef SIMPLE_EXEC_H
#define SIMPLE_EXEC_H
int runCommand(char** stdOut,
int* stdOutByteCount,
int* returnCode,
int includeStdErr,
char* command,
...);
int runCommandArray(char** stdOut,
int* stdOutByteCount,
int* returnCode,
int includeStdErr,
char* const* allArgs);
#endif
#ifdef SIMPLE_EXEC_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <fcntl.h>
#include <signal.h>
#include "ftg_core.h"
#define release_assert(exp) \
{ \
if (!(exp)) { \
abort(); \
} \
}
enum PIPE_FILE_DESCRIPTORS { READ_FD = 0, WRITE_FD = 1 };
enum RUN_COMMAND_ERROR { COMMAND_RAN_OK = 0, COMMAND_NOT_FOUND = 1 };
void
sigchldHandler(int p)
{
FTG_UNUSED(p);
}
int
runCommandArray(char** stdOut,
int* stdOutByteCount,
int* returnCode,
int includeStdErr,
char* const* allArgs)
{
int bufferSize = 256;
char buffer[bufferSize + 1];
int dataReadFromChildDefaultSize = bufferSize * 5;
int dataReadFromChildSize = dataReadFromChildDefaultSize;
int dataReadFromChildUsed = 0;
char* dataReadFromChild = (char*)malloc(dataReadFromChildSize);
int parentToChild[2];
release_assert(pipe(parentToChild) == 0);
int childToParent[2];
release_assert(pipe(childToParent) == 0);
int errPipe[2];
release_assert(pipe(errPipe) == 0);
void (*prevHandler)(int);
prevHandler = signal(SIGCHLD, sigchldHandler);
pid_t pid;
switch (pid = fork()) {
case -1: {
release_assert(0 && "Fork failed");
break;
}
case 0: {
release_assert(dup2(parentToChild[READ_FD], STDIN_FILENO) != -1);
release_assert(dup2(childToParent[WRITE_FD], STDOUT_FILENO) != -1);
if (includeStdErr) {
release_assert(dup2(childToParent[WRITE_FD], STDERR_FILENO) != -1);
} else {
int devNull = open("/dev/null", O_WRONLY);
release_assert(dup2(devNull, STDERR_FILENO) != -1);
}
release_assert(close(parentToChild[WRITE_FD]) == 0);
release_assert(close(childToParent[READ_FD]) == 0);
release_assert(close(errPipe[READ_FD]) == 0);
const char* command = allArgs[0];
execvp(command, allArgs);
char err = 1;
ssize_t result = write(errPipe[WRITE_FD], &err, 1);
release_assert(result != -1);
close(errPipe[WRITE_FD]);
close(parentToChild[READ_FD]);
close(childToParent[WRITE_FD]);
exit(0);
}
default: {
release_assert(close(parentToChild[READ_FD]) == 0);
release_assert(close(childToParent[WRITE_FD]) == 0);
release_assert(close(errPipe[WRITE_FD]) == 0);
while (1) {
ssize_t bytesRead = 0;
switch (bytesRead = read(childToParent[READ_FD], buffer, bufferSize)) {
case 0: {
int status = 0;
release_assert(waitpid(pid, &status, 0) == pid);
release_assert(close(parentToChild[WRITE_FD]) == 0);
release_assert(close(childToParent[READ_FD]) == 0);
char errChar = 0;
ssize_t result = read(errPipe[READ_FD], &errChar, 1);
release_assert(result != -1);
close(errPipe[READ_FD]);
if (errChar) {
free(dataReadFromChild);
return COMMAND_NOT_FOUND;
}
dataReadFromChild =
(char*)realloc(dataReadFromChild, dataReadFromChildUsed + 1);
dataReadFromChild[dataReadFromChildUsed] = '\0';
if (stdOut != NULL)
*stdOut = dataReadFromChild;
else
free(dataReadFromChild);
if (stdOutByteCount != NULL)
*stdOutByteCount = dataReadFromChildUsed;
if (returnCode != NULL)
*returnCode = WEXITSTATUS(status);
return COMMAND_RAN_OK;
}
case -1: {
release_assert(0 && "read() failed");
break;
}
default: {
if (dataReadFromChildUsed + bytesRead + 1 >= dataReadFromChildSize) {
dataReadFromChildSize += dataReadFromChildDefaultSize;
dataReadFromChild =
(char*)realloc(dataReadFromChild, dataReadFromChildSize);
}
memcpy(dataReadFromChild + dataReadFromChildUsed, buffer, bytesRead);
dataReadFromChildUsed += bytesRead;
break;
}
}
}
}
}
signal(SIGCHLD, prevHandler);
}
int
runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...)
{
va_list vl;
va_start(vl, command);
char* currArg = NULL;
int allArgsInitialSize = 16;
int allArgsSize = allArgsInitialSize;
char** allArgs = (char**)malloc(sizeof(char*) * allArgsSize);
allArgs[0] = command;
int i = 1;
do {
currArg = va_arg(vl, char*);
allArgs[i] = currArg;
i++;
if (i >= allArgsSize) {
allArgsSize += allArgsInitialSize;
allArgs = (char**)realloc(allArgs, sizeof(char*) * allArgsSize);
}
} while (currArg != NULL);
va_end(vl);
int retval =
runCommandArray(stdOut, stdOutByteCount, returnCode, includeStdErr, allArgs);
free(allArgs);
return retval;
}
#endif