diff options
author | Joseph Hunkeler <jhunkeler@gmail.com> | 2020-03-18 22:25:27 -0400 |
---|---|---|
committer | Joseph Hunkeler <jhunkeler@gmail.com> | 2020-03-18 22:25:27 -0400 |
commit | ccaeb7092b5ad40b1b3833c987ba3ec4d47f0bb8 (patch) | |
tree | ae167772a9a2343aa77bf8944b56abe853f6a2ec /lib/shell.c | |
parent | 3731bb4679ee8716d4f579d5744c36a2d1b4a257 (diff) | |
download | spmc-ccaeb7092b5ad40b1b3833c987ba3ec4d47f0bb8.tar.gz |
Refactor project: build/install libspm[_static.a].so to make unit testing possible
Diffstat (limited to 'lib/shell.c')
-rw-r--r-- | lib/shell.c | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/lib/shell.c b/lib/shell.c new file mode 100644 index 0000000..6b28e64 --- /dev/null +++ b/lib/shell.c @@ -0,0 +1,116 @@ +/** + * @file shell.c + */ +#include "spm.h" + +/** + * A wrapper for `popen()` that executes non-interactive programs and reports their exit value. + * To redirect stdout and stderr you must do so from within the `fmt` string + * + * ~~~{.c} + * int fd = 1; // stdout + * const char *log_file = "log.txt"; + * Process *proc_info; + * int status; + * + * // Send stderr to stdout + * shell(&proc_info, SHELL_OUTPUT, "foo 2>&1"); + * // Send stdout and stderr to /dev/null + * shell(&proc_info, SHELL_OUTPUT,"bar &>/dev/null"); + * // Send stdout from baz to log.txt + * shell(&proc_info, SHELL_OUTPUT, "baz %d>%s", fd, log_file); + * // Do not record or redirect output from any streams + * shell(&proc_info, SHELL_DEFAULT, "biff"); + * ~~~ + * + * @param Process uninitialized `Process` struct will be populated with process data + * @param options change behavior of the function + * @param fmt shell command to execute (accepts `printf` style formatters) + * @param ... variadic arguments (used by `fmt`) + */ +void shell(Process **proc_info, u_int64_t option, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + + clockid_t clkid = CLOCK_REALTIME; + FILE *proc = NULL; + + (*proc_info) = (Process *)calloc(1, sizeof(Process)); + if (!(*proc_info)) { + fprintf(SYSERROR); + exit(errno); + } + (*proc_info)->returncode = -1; + + // outbuf needs to be an integer type because fgetc returns EOF (> char) + int *outbuf = (int *)calloc(1, sizeof(int)); + if (!outbuf) { + fprintf(SYSERROR); + exit(errno); + } + char *cmd = (char *)calloc(PATH_MAX, sizeof(char)); + if (!cmd) { + fprintf(SYSERROR); + exit(errno); + } + + vsnprintf(cmd, PATH_MAX, fmt, args); + + if (option & SHELL_BENCHMARK) { + if (clock_gettime(clkid, &(*proc_info)->start_time) == -1) { + perror("clock_gettime"); + exit(errno); + } + } + + proc = popen(cmd, "r"); + if (!proc) { + free(outbuf); + va_end(args); + return; + } + + if (option & SHELL_BENCHMARK) { + if (clock_gettime(clkid, &(*proc_info)->stop_time) == -1) { + perror("clock_gettime"); + exit(errno); + } + (*proc_info)->time_elapsed = ((*proc_info)->stop_time.tv_sec - (*proc_info)->start_time.tv_sec) + + ((*proc_info)->stop_time.tv_nsec - (*proc_info)->start_time.tv_nsec) / 1E9; + } + + if (option & SHELL_OUTPUT) { + size_t bytes_read = 0; + size_t i = 0; + size_t new_buf_size; + (*proc_info)->output = (char *)calloc(BUFSIZ, sizeof(char)); + + while ((*outbuf = fgetc(proc)) != EOF) { + if (i >= BUFSIZ) { + new_buf_size = BUFSIZ + (i + bytes_read); + (*proc_info)->output = (char *)realloc((*proc_info)->output, new_buf_size); + i = 0; + } + if (*outbuf) { + (*proc_info)->output[bytes_read] = (char)*outbuf; + } + bytes_read++; + i++; + } + } + (*proc_info)->returncode = pclose(proc); + va_end(args); + free(outbuf); + free(cmd); +} + +/** + * Free process resources allocated by `shell()` + * @param proc_info `Process` struct + */ +void shell_free(Process *proc_info) { + if (proc_info->output) { + free(proc_info->output); + } + free(proc_info); +} |