From 4d8a79d62a3d1024d09193ed857371526c4f22e8 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Mon, 16 Nov 2020 11:51:31 +0100 Subject: lib: add system() function Signed-off-by: Jo-Philipp Wich --- README.md | 30 ++++++++++++++++ lib.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/README.md b/README.md index 4b170d3..2dd0173 100644 --- a/README.md +++ b/README.md @@ -993,3 +993,33 @@ Print any of the given values to stderr. Arrays and objects are converted to their JSON representation. Returns the amount of bytes printed. + +#### 6.52. `system(command, timeout)` + +Executes the given command, waits for completion and returns the resulting +exit code. + +The command argument may be either a string, in which case it is passed to +`/bin/sh -c` or an array, which is directly converted into an `execv()` +argument vector. + +If the program terminated normally, a positive integer holding the programs +`exit()` code is returned. If the program was terminated by an uncatched +signal, a negative signal number is returned, e.g. `-9` if the program was +terminated by `SIGKILL`. + +If the optional timeout argument is specified, the program is terminated by +`SIGKILL` after that many milliseconds if it didn't complete within the timeout. + +Omitting the timeout argument, or passing `0` disabled the command timeout. + +```javascript +// Execute through `/bin/sh` +system("echo 'Hello world' && exit 3"); // prints "Hello world" to stdout and returns 3 + +// Execute argument vector +system("/usr/bin/date", "+%s"]); // prints the UNIX timestamp to stdout and returns 0 + +// Apply a timeout +system("sleep 3 && echo 'Success'", 1000); // returns -9 +``` \ No newline at end of file diff --git a/lib.c b/lib.c index eb9ef60..4c05b2e 100644 --- a/lib.c +++ b/lib.c @@ -25,15 +25,18 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include +#include __attribute__((format(printf, 3, 5))) static void @@ -2193,6 +2196,118 @@ ut_warn(struct ut_state *s, uint32_t off, struct json_object *args) return ut_print_common(s, off, args, stderr); } +static struct json_object * +ut_system(struct ut_state *s, uint32_t off, struct json_object *args) +{ + struct json_object *cmdline = json_object_array_get_idx(args, 0); + struct json_object *timeout = json_object_array_get_idx(args, 1); + struct ut_op *op = ut_get_op(s, off); + sigset_t sigmask, sigomask; + const char **arglist, *fn; + struct timespec ts; + int64_t tms; + pid_t cld; + size_t i; + int rc; + + switch (json_object_get_type(cmdline)) { + case json_type_string: + arglist = xalloc(sizeof(*arglist) * 4); + arglist[0] = "/bin/sh"; + arglist[1] = "-c"; + arglist[2] = json_object_get_string(cmdline); + arglist[3] = NULL; + break; + + case json_type_array: + arglist = xalloc(sizeof(*arglist) * (json_object_array_length(cmdline) + 1)); + + for (i = 0; i < json_object_array_length(cmdline); i++) + arglist[i] = json_object_get_string(json_object_array_get_idx(cmdline, i)); + + arglist[i] = NULL; + + break; + + default: + return ut_new_exception(s, op->off, "Passed command is neither string nor array"); + } + + if (timeout && (!json_object_is_type(timeout, json_type_int) || json_object_get_int64(timeout) < 0)) + return ut_new_exception(s, op->off, "Invalid timeout specified"); + + tms = timeout ? json_object_get_int64(timeout) : 0; + + if (tms > 0) { + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &sigmask, &sigomask) < 0) { + fn = "sigprocmask"; + goto fail; + } + } + + cld = fork(); + + switch (cld) { + case -1: + fn = "fork"; + goto fail; + + case 0: + execv(arglist[0], (char * const *)arglist); + exit(-1); + + break; + + default: + if (tms > 0) { + ts.tv_sec = tms / 1000; + ts.tv_nsec = (tms % 1000) * 1000000; + + while (1) { + if (sigtimedwait(&sigmask, NULL, &ts) < 0) { + if (errno == EINTR) + continue; + + if (errno != EAGAIN) { + fn = "sigtimedwait"; + goto fail; + } + + kill(cld, SIGKILL); + } + + break; + } + } + + if (waitpid(cld, &rc, 0) < 0) { + fn = "waitpid"; + goto fail; + } + + sigprocmask(SIG_SETMASK, &sigomask, NULL); + free(arglist); + + if (WIFEXITED(rc)) + return xjs_new_int64(WEXITSTATUS(rc)); + else if (WIFSIGNALED(rc)) + return xjs_new_int64(-WTERMSIG(rc)); + else if (WIFSTOPPED(rc)) + return xjs_new_int64(-WSTOPSIG(rc)); + + return NULL; + } + +fail: + sigprocmask(SIG_SETMASK, &sigomask, NULL); + free(arglist); + + return ut_new_exception(s, op->off, "%s(): %s", fn, strerror(errno)); +} + const struct ut_ops ut = { .register_function = ut_register_function, .register_type = ut_register_extended_type, @@ -2250,6 +2365,7 @@ static const struct { const char *name; ut_c_fn *func; } functions[] = { { "json", ut_json }, { "include", ut_include }, { "warn", ut_warn }, + { "system", ut_system }, }; -- cgit v1.2.3