// copied from: https://github.com/wheybags/simple_exec/blob/5a74c507c4ce1b2bb166177ead4cca7cfa23cb35/simple_exec.h // simple_exec.h, single header library to run external programs + retrieve their status code and output (unix only for now) // // do this: // #define SIMPLE_EXEC_IMPLEMENTATION // before you include this file in *one* C or C++ file to create the implementation. // i.e. it should look like this: // #define SIMPLE_EXEC_IMPLEMENTATION // #include "simple_exec.h" #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 // SIMPLE_EXEC_H #ifdef SIMPLE_EXEC_IMPLEMENTATION #include #include #include #include #include #include #include #include #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 }; int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs) { // adapted from: https://stackoverflow.com/a/479103 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); pid_t pid; switch( pid = fork() ) { case -1: { release_assert(0 && "Fork failed"); break; } case 0: // child { 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); } // unused 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: // parent { // unused 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: // End-of-File, or non-blocking read. { int status = 0; release_assert(waitpid(pid, &status, 0) == pid); // done with these now 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; } // free any un-needed memory with realloc + add a null terminator for convenience 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; } } } } } } 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 //SIMPLE_EXEC_IMPLEMENTATION