Created
January 18, 2019 15:23
-
-
Save dpryden/c6042622297272c4e7afe59d70b0caae to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <errno.h> | |
#include <stdbool.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
/* | |
* run_subprocess: Run a command as a subprocess and return its output. | |
* | |
* command: array of strings, terminated by a NULL string. | |
* The first string in this array is the command name. It will be passed | |
* as argv[0] to the child process. The remaining strings are command-line | |
* arguments. | |
* | |
* Note that, unlike the system(2) syscall, this function takes the arguments | |
* in an already tokenized form. Therefore, no further tokenization is | |
* performed. You can pass arbitary strings as arguments this way. | |
* | |
* The return value is a string containing the stdout of the command. (The | |
* stderr of the subprocess is not redirected.) The size of this string is | |
* limited by available memory, so if the command writes a lot of output it | |
* might fail. If any error occurs while running the subprocess, the returned | |
* string will be NULL. In any other case, the caller is responsible for | |
* freeing the return value. | |
* | |
* In the event of an error, error messages will be printed to stderr and | |
* NULL will be returned. | |
*/ | |
char* run_subprocess(char* command[]) { | |
int pipe_fds[2]; | |
if (pipe(pipe_fds) == -1) { | |
fprintf(stderr, "pipe() failed: %s\n", strerror(errno)); | |
return NULL; | |
} | |
int parent_pipe_fd = pipe_fds[0]; | |
int child_pipe_fd = pipe_fds[1]; | |
char* result = NULL; | |
size_t result_len = 0; | |
pid_t child_pid = fork(); | |
if (child_pid == -1) { | |
fprintf(stderr, "fork() failed: %s\n", strerror(errno)); | |
// Clean up the pipe we created before returning. | |
close(parent_pipe_fd); | |
close(child_pipe_fd); | |
return NULL; | |
} | |
if (child_pid == 0) { | |
// We are the child. Set the pipe as our stdout. | |
if (dup2(child_pipe_fd, STDOUT_FILENO) == -1) { | |
fprintf(stderr, "in child, dup2() failed: %s\n", strerror(errno)); | |
_exit(EXIT_FAILURE); | |
} | |
// Once we've duplicated it, we can close the original descriptor. | |
if (close(child_pipe_fd) == -1) { | |
fprintf(stderr, "in child, close(%d) failed: %s\n", | |
child_pipe_fd, strerror(errno)); | |
_exit(EXIT_FAILURE); | |
} | |
// Close file descriptors we won't be using in this process. | |
if (close(parent_pipe_fd) == -1) { | |
fprintf(stderr, "in child, close(%d) failed: %s\n", | |
parent_pipe_fd, strerror(errno)); | |
_exit(EXIT_FAILURE); | |
} | |
// Exec the command. | |
execvp(command[0], command); | |
// If execvp returns, then it failed. | |
fprintf(stderr, "exec() failed: %s\n", strerror(errno)); | |
_exit(EXIT_FAILURE); | |
} | |
// We are the parent. Close file descriptors we won't be using in this | |
// process. | |
if (close(child_pipe_fd) == -1) { | |
fprintf(stderr, "in parent, close(%d) failed: %s\n", | |
child_pipe_fd, strerror(errno)); | |
exit(EXIT_FAILURE); | |
} | |
char buffer[100]; | |
ssize_t bytes_read = 0; | |
while(true) { | |
// Read chunks of bytes from the pipe. | |
bytes_read = read(parent_pipe_fd, buffer, sizeof(buffer)); | |
if (bytes_read == -1) { | |
// Error during read. Clean up as best we can. | |
fprintf(stderr, "pipe read() failed: %s\n", strerror(errno)); | |
free(result); | |
result = NULL; | |
break; | |
} | |
if (bytes_read == 0) { | |
// End of file. Exit the loop. | |
break; | |
} | |
// Grow our result string by the number of bytes read and append the | |
// new bytes there. | |
result = realloc(result, result_len + bytes_read + 1); | |
if (!result) { | |
fprintf(stderr, "realloc() failed: %s\n", strerror(errno)); | |
break; | |
} | |
strncpy(result + result_len, buffer, bytes_read); | |
result_len += bytes_read; | |
result[result_len] = '\0'; | |
} | |
if (close(parent_pipe_fd) == -1) { | |
fprintf(stderr, "in parent, close(%d) failed: %s\n", | |
parent_pipe_fd, strerror(errno)); | |
} | |
int child_status = 0; | |
if (waitpid(child_pid, &child_status, /* options */ 0) == -1) { | |
fprintf(stderr, "wait() failed: %s\n", strerror(errno)); | |
} | |
int child_exit_code = WEXITSTATUS(child_status); | |
if (child_exit_code != EXIT_SUCCESS) { | |
fprintf(stderr, "Child exited with code %d\n", child_exit_code); | |
} | |
return result; | |
} | |
int main(int argc, char** argv) { | |
if (argc < 2) { | |
fprintf(stderr, "No command specified!\n"); | |
exit(EXIT_FAILURE); | |
} | |
printf("Using command: ["); | |
for (int i = 1; i < argc; i++) { | |
if (i > 1) { | |
printf(", "); | |
} | |
printf("\"%s\"", argv[i]); | |
} | |
printf("]\n"); | |
// The command line to run is our command line, skipping the first | |
// argument (which is us). | |
char** command = &argv[1]; | |
char* result = run_subprocess(command); | |
if (!result) { | |
exit(EXIT_FAILURE); | |
} | |
printf("Got %lu bytes of output: \"%s\"\n", strlen(result), result); | |
free(result); | |
exit(EXIT_SUCCESS); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment