summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2020-11-16 11:51:31 +0100
committerJo-Philipp Wich <jo@mein.io>2020-11-16 11:51:31 +0100
commit4d8a79d62a3d1024d09193ed857371526c4f22e8 (patch)
tree96590d861ff20e4accb1c11554918539a898077e
parentd0ede0646f231179b3eab5a7aa4ab1e399f7c5be (diff)
lib: add system() function
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--README.md30
-rw-r--r--lib.c116
2 files changed, 146 insertions, 0 deletions
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 <stdlib.h>
#include <stdarg.h>
#include <string.h>
+#include <signal.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <dlfcn.h>
#include <libgen.h>
+#include <unistd.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/wait.h>
__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 },
};