Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#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