Last active
February 13, 2020 11:04
-
-
Save fonic/a01a7a45e09289635cf19d7878e85103 to your computer and use it in GitHub Desktop.
C Run Command Function and Wrappers (Stack Overflow: https://stackoverflow.com/q/59576159/1976617)
This file contains 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
/************************************************************************** | |
* * | |
* C Run Command Function and Wrappers * | |
* * | |
* Related Stack Overflow question: * | |
* https://stackoverflow.com/q/59576159/1976617 * | |
* * | |
* Created by Fonic <https://github.com/fonic> * | |
* Date: 01/07/20 * | |
* * | |
**************************************************************************/ | |
/*************************************** | |
* * | |
* Includes * | |
* * | |
***************************************/ | |
#include <stdarg.h> // va_start, va_end, va_list, ... | |
#include <stdio.h> // stdout, stderr, stdin, EOF, fdopen | |
#include <stdlib.h> // exit, free | |
#include <unistd.h> // exec*, pipe, fork, dup, close | |
#include <sys/wait.h> // wait, waitpid | |
#include <string.h> // strerror | |
#include <errno.h> // errno | |
#include <signal.h> // kill, SIGKILL | |
/*************************************** | |
* * | |
* Functions * | |
* * | |
***************************************/ | |
// Debug output | |
static FILE *dbg_dst = NULL; | |
#define debugf(format, ...) fprintf(dbg_dst, "[%s] [%s:%-3d:%s] "format"\n", "DEBUG", __FILE__, __LINE__, __func__, ##__VA_ARGS__) | |
#define infof(format, ...) fprintf(dbg_dst, "[%s] [%s:%-3d:%s] "format"\n", "INFO ", __FILE__, __LINE__, __func__, ##__VA_ARGS__) | |
#define warnf(format, ...) fprintf(dbg_dst, "[%s] [%s:%-3d:%s] "format"\n", "WARN ", __FILE__, __LINE__, __func__, ##__VA_ARGS__) | |
#define errorf(format, ...) fprintf(dbg_dst, "[%s] [%s:%-3d:%s] "format"\n", "ERROR", __FILE__, __LINE__, __func__, ##__VA_ARGS__) | |
// Runs command with arguments, returns output and return value (arguments provided as array) | |
// NOTE: array of arguments must end with NULL element | |
// NOTE: both stdout and stderr of executed command are captured and stored in output | |
// NOTE: if output != NULL, (*output) is allocated using malloc and has to be freed afterwards by caller | |
#define RUN_COMMAND_INITIAL_BUFFER_SIZE 4096 | |
int run_command(char **output, int *retval, const char *command, const char* const args[]) | |
{ | |
int pipe_fds[2]; | |
pid_t pid; | |
debugf("Running command '%s':", command); | |
// Create pipe for parent-child communication | |
debugf("Creating pipe..."); | |
if (pipe(pipe_fds) == -1) { | |
errorf("Failed to create pipe: %s", strerror(errno)); | |
return 1; | |
} | |
// Fork into parent and child | |
debugf("Forking into parent and child..."); | |
pid = fork(); | |
if (pid == -1) { | |
errorf("Failed to fork: %s", strerror(errno)); | |
return 1; | |
} | |
// Child | |
if (pid == 0) { | |
pid_t cpid = getpid(); | |
int dbg_dst_fd_backup = -1; | |
FILE *dbg_dst_file_backup = NULL; | |
debugf("Child: pid: %d", cpid); | |
// Backup current debug output destination, redirect debug output to backup | |
// NOTE: without this, debug output would be sent to parent due to redirection of stdout/stderr below | |
if (dbg_dst != NULL) { | |
debugf("Child: backing up debug output destination, redirecting debug output to backup..."); | |
int dbg_dst_fd = fileno(dbg_dst); | |
if (dbg_dst_fd == -1) { | |
errorf("Child: failed to determine file descriptor of debug output destination: %s", strerror(errno)); | |
goto child_error; | |
} | |
dbg_dst_fd_backup = dup(dbg_dst_fd); | |
if (dbg_dst_fd_backup == -1) { | |
errorf("Child: failed to duplicate file descriptor of debug output destination: %s", strerror(errno)); | |
goto child_error; | |
} | |
dbg_dst_file_backup = fdopen(dbg_dst_fd_backup, "w"); | |
if (dbg_dst_fd_backup == -1) { | |
errorf("Child: failed to open backup of debug output destination for writing: %s", strerror(errno)); | |
goto child_error; | |
} | |
dbg_dst = dbg_dst_file_backup; | |
} | |
// Redirect stdout/stderr to pipe -> send output to parent | |
debugf("Child: redirecting stdout to pipe..."); | |
if (dup2(pipe_fds[1], STDOUT_FILENO) == -1) { | |
errorf("Child: failed to redirect stdout: %s", strerror(errno)); | |
goto child_error; | |
} | |
debugf("Child: redirecting stderr to pipe..."); | |
if (dup2(pipe_fds[1], STDERR_FILENO) == -1) { | |
errorf("Child: failed to redirect stderr: %s", strerror(errno)); | |
goto child_error; | |
} | |
// Close both pipe ends (pipe_fds[0] belongs to parent, pipe_fds[1] no longer needed after dup2) | |
debugf("Child: closing both pipe ends..."); | |
close(pipe_fds[0]); | |
close(pipe_fds[1]); | |
pipe_fds[0] = pipe_fds[1] = -1; | |
// Set buffering for stdout/stderr | |
debugf("Child: setting buffering for stdout..."); | |
if (setvbuf(stdout, NULL, _IONBF, 0) != 0) { | |
errorf("Child: failed to set buffering for stdout: %s", strerror(errno)); | |
goto child_error; | |
} | |
debugf("Child: setting buffering for stderr..."); | |
if (setvbuf(stderr, NULL, _IONBF, 0) != 0) { | |
errorf("Child: failed to set buffering for stderr: %s", strerror(errno)); | |
goto child_error; | |
} | |
// Execute command -> replaces child process, execution ends on execvp line | |
// NOTE: execvp works like shell, i.e. PATH is searched if no absolute path to executable provided | |
debugf("Child: executing command..."); | |
execvp(command, (char * const *)args); | |
errorf("Child: failed to execute command: %s", strerror(errno)); | |
// Terminate child (will only be reached in case of an error) | |
child_error: | |
if (pipe_fds[0] != -1) | |
close(pipe_fds[0]); | |
if (pipe_fds[1] != -1) | |
close(pipe_fds[1]); | |
if (dbg_dst_fd_backup != -1) | |
close(dbg_dst_fd_backup); | |
if (dbg_dst_file_backup != NULL) | |
fclose(dbg_dst_file_backup); | |
exit(1); | |
} | |
// Parent | |
FILE *pipe_file = NULL; | |
char *buffer = NULL; | |
long unsigned int buf_size; | |
long unsigned int buf_data; | |
long unsigned int buf_free; | |
int wstatus; | |
debugf("Parent: child created (pid: %i)", pid); | |
// Close child's side of pipe | |
debugf("Parent: closing child's side of pipe..."); | |
close(pipe_fds[1]); | |
// Open parent's side of pipe for reading | |
debugf("Parent: opening pipe for reading..."); | |
pipe_file = fdopen(pipe_fds[0], "r"); | |
if (pipe_file == NULL) { | |
errorf("Parent: failed to open pipe for reading: %s", strerror(errno)); | |
goto parent_error; | |
} | |
// Allocate buffer to store child's output | |
debugf("Parent: allocating buffer..."); | |
buf_size = RUN_COMMAND_INITIAL_BUFFER_SIZE; | |
buf_data = 0; | |
buf_free = buf_size; | |
buffer = (char *) malloc(buf_size); | |
if (buffer == NULL) { | |
errorf("Parent: failed to allocate buffer"); | |
goto parent_error; | |
} | |
// Read from parent's side of pipe until EOF | |
debugf("Parent: reading from pipe..."); | |
while (!feof(pipe_file)) { | |
debugf("Parent: buf_size: %lu, buf_data: %lu, buf_free: %lu", buf_size, buf_data, buf_free); | |
int n = fread(buffer + buf_data, 1, buf_free, pipe_file); | |
if (n <= 0 && ferror(pipe_file) != 0) { | |
errorf("Parent: failed to read from stdin"); | |
goto parent_error; | |
} | |
buf_data += n; | |
buf_free -= n; | |
if (buf_free <= 0) { | |
buf_free += buf_size; | |
buf_size *= 2; | |
char *newbuf = (char *) realloc(buffer, buf_size); | |
if (newbuf == NULL) { | |
errorf("Parent: failed to reallocate buffer"); | |
goto parent_error; | |
} | |
buffer = newbuf; | |
} | |
} | |
if (buf_data > 0 && buffer[buf_data-1] == '\n') // remove trailing newline | |
buf_data--; | |
buffer[buf_data++] = '\0'; | |
debugf("Parent: buf_size: %lu, buf_data: %lu, buf_free: %lu", buf_size, buf_data, buf_free); | |
// Resize buffer to final size | |
debugf("Parent: resizing buffer to final size..."); | |
buf_size = buf_data; | |
buf_free = 0; | |
char *newbuf = (char *) realloc(buffer, buf_size); | |
if (newbuf == NULL) { | |
errorf("Parent: failed to reallocate buffer"); | |
goto parent_error; | |
} | |
buffer = newbuf; | |
debugf("Parent: buf_size: %lu, buf_data: %lu, buf_free: %lu", buf_size, buf_data, buf_free); | |
// Wait for child to finish | |
debugf("Parent: waiting for child to terminate..."); | |
if (waitpid(pid, &wstatus, 0) == -1) { | |
errorf("Parent: child failed to terminate, killing"); | |
kill(pid, SIGKILL); | |
} | |
debugf("Parent: child terminated (wstatus: %i, exit status: %i)", wstatus, WEXITSTATUS(wstatus)); | |
// Close pipe file and pipe | |
debugf("Parent: closing pipe file and pipe..."); | |
fclose(pipe_file); | |
close(pipe_fds[0]); | |
// Return results using provided references | |
if (output != NULL) { | |
(*output) = buffer; | |
} else { | |
debugf("Parent: discarding buffer contents"); | |
free(buffer); | |
} | |
if (retval != NULL) | |
(*retval) = WEXITSTATUS(wstatus); | |
debugf("Finished running command"); | |
return 0; | |
parent_error: | |
close(pipe_fds[0]); | |
kill(pid, SIGKILL); | |
if (pipe_file != NULL) | |
fclose(pipe_file); | |
if (buffer != NULL) | |
free(buffer); | |
return 1; | |
} | |
// Runs command with arguments, returns output and return value (variadic arguments; static maximum limit; string references) | |
// NOTE: this works, but documentation is unclear about exact va_* behavior regarding validity of retrieved arguments | |
#define RUN_COMMAND_MAX_ARGS 100 | |
int run_command2(char **output, int *retval, const char *command, ...) | |
{ | |
va_list val; | |
const char *args[RUN_COMMAND_MAX_ARGS]; | |
// Put reference to command, references to variadic arguments and NULL in args | |
va_start(val, command); | |
args[0] = command; | |
int i = 0; | |
do { | |
debugf("Argument %i: %s", i, args[i]); | |
i++; | |
args[i] = va_arg(val, const char *); | |
} while (args[i] != NULL && i < RUN_COMMAND_MAX_ARGS-1); | |
args[i] = NULL; | |
va_end(val); | |
// Call 'run_command()', return result | |
return run_command(output, retval, command, args); | |
} | |
// Runs command with arguments, returns output and return value (variadic arguments; dynamic, no limit; string references) | |
// NOTE: this works, but documentation is unclear about exact va_* behavior regarding validity of retrieved arguments | |
// NOTE: this uses a variable length array (VLA) to store args; VLAs are controversial, e.g. https://lkml.org/lkml/2018/3/7/621 | |
int run_command2_5(char **output, int *retval, const char *command, ...) | |
{ | |
va_list val; | |
int argc; | |
// Determine number of arguments (minimum: command + NULL == 2) | |
va_start(val, command); | |
argc = 1; | |
do { | |
argc++; | |
} while (va_arg(val, const char *) != NULL); | |
va_end(val); | |
debugf("argc: %i", argc); | |
// Allocate args as VLA | |
const char *args[argc]; | |
// Put reference to command, references to variadic arguments and NULL in args | |
args[0] = command; | |
va_start(val, command); | |
int i = 0; | |
do { | |
debugf("Argument %i: %s", i, args[i]); | |
i++; | |
args[i] = va_arg(val, const char *); | |
} while (i < argc && args[i] != NULL); | |
va_end(val); | |
// Call 'run_command()', return result | |
return run_command(output, retval, command, args); | |
} | |
// Runs command with arguments, returns output and return value (variadic arguments; dynamic, no limit; string references) | |
// NOTE: this works, but documentation is unclear about exact va_* behavior regarding validity of retrieved arguments | |
// NOTE: probably seconds best solution after run_command4() | |
int run_command3(char **output, int *retval, const char *command, ...) | |
{ | |
va_list val; | |
const char **args; | |
int argc; | |
int result; | |
// Determine number of arguments (minimum: command + NULL == 2) | |
va_start(val, command); | |
argc = 1; | |
do { | |
argc++; | |
} while (va_arg(val, const char *) != NULL); | |
va_end(val); | |
debugf("argc: %i", argc); | |
// Allocate args (array of pointers to string) | |
args = (const char **) malloc(argc * sizeof(const char *)); | |
if (args == NULL) { | |
errorf("Failed to allocate args"); | |
return 1; | |
} | |
// Put reference to command, references to variadic arguments and NULL in args | |
args[0] = command; | |
va_start(val, command); | |
int i = 0; | |
do { | |
debugf("Argument %i: %s", i, args[i]); | |
i++; | |
args[i] = va_arg(val, const char *); | |
} while (i < argc && args[i] != NULL); | |
va_end(val); | |
// Call 'run_command()' | |
result = run_command(output, retval, command, args); | |
// Free args, return result | |
free(args); | |
return result; | |
} | |
// Runs command with arguments, returns output and return value (variadic arguments; dynamic, no limit; string copies) | |
// NOTE: safest variant - all argument strings are copied, thus independent of exact va_* behavior regarding validity of retrieved arguments | |
int run_command4(char **output, int *retval, const char *command, ...) | |
{ | |
va_list val; | |
char **args; | |
int argc; | |
int result = 1; // assume error | |
// Determine number of arguments (minimum: command + NULL == 2) | |
va_start(val, command); | |
argc = 1; | |
do { | |
argc++; | |
} while (va_arg(val, const char *) != NULL); | |
va_end(val); | |
debugf("argc: %i", argc); | |
// Allocate args (array of pointers to string) | |
args = (char **) calloc(argc, sizeof(char *)); | |
if (args == NULL) { | |
errorf("Failed to allocate args"); | |
goto bail_out; | |
} | |
// Copy command to args[0] | |
args[0] = strdup(command); | |
if (args[0] == NULL) { | |
errorf("Failed to copy argument %i: %s", 0, strerror(errno)); | |
goto bail_out; | |
} | |
debugf("Argument %i: %s", 0, args[0]); | |
// Copy variadic arguments to args[1..argc-2], i.e. spots in between command ... NULL | |
va_start(val, command); | |
for (int i = 1; i <= argc-2; i++) { | |
args[i] = strdup(va_arg(val, const char *)); | |
if (args[i] == NULL) { | |
errorf("Failed to copy argument %i: %s", i, strerror(errno)); | |
goto bail_out; | |
} | |
debugf("Argument %i: %s", i, args[i]); | |
} | |
va_end(val); | |
// Terminate list of arguments with NULL | |
args[argc-1] = NULL; | |
// Call 'run_command()' | |
result = run_command(output, retval, command, (const char * const *)args); | |
// Free args, return result | |
bail_out: | |
if (args != NULL) { | |
for (int i = 0; i <= argc-2; i++) { | |
debugf("Freeing argument %i: %s", i, args[i]); | |
free(args[i]); | |
} | |
} | |
free(args); | |
return result; | |
} | |
/*************************************** | |
* * | |
* Main * | |
* * | |
***************************************/ | |
int main(int argc, char *argv[]) | |
{ | |
dbg_dst = stderr; | |
char *output = NULL; | |
int retval = 0; | |
const char *command = "find"; | |
const char* const args[] = { command, "/tmp", "-type", "f", NULL }; | |
run_command(&output, &retval, command, args); | |
infof("Retval: %i, output: '%s'", retval, output); | |
free(output); | |
run_command2(&output, &retval, "/bin/echo", "hello", "how", "are", "you", "today", "?", NULL); | |
infof("Retval: %i, output: '%s'", retval, output); | |
free(output); | |
run_command2_5(&output, &retval, "/bin/echo", "hello", "how", "are", "you", "today", "?", NULL); | |
infof("Retval: %i, output: '%s'", retval, output); | |
free(output); | |
run_command3(&output, &retval, "/bin/echo", "hello", "how", "are", "you", "today", "?", NULL); | |
infof("Retval: %i, output: '%s'", retval, output); | |
free(output); | |
run_command4(&output, &retval, "/bin/echo", "hello", "how", "are", "you", "today", "?", NULL); | |
infof("Retval: %i, output: '%s'", retval, output); | |
free(output); | |
run_command4(NULL, NULL, "/bin/sleep", "3s", NULL); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage:
run_command.c
gcc run_command.c -o run_command -g -Wall -fsanitize=address -fno-omit-frame-pointer
./run_command