summaryrefslogtreecommitdiffstats
path: root/libglusterfs
diff options
context:
space:
mode:
authorCsaba Henk <csaba@gluster.com>2011-05-26 03:32:26 +0000
committerAnand Avati <avati@gluster.com>2011-05-26 08:55:18 -0700
commit302ad26982d1eb7762e743e14dda627ffb317379 (patch)
tree6028138f92c630f76903bce11bacd0146cb14a26 /libglusterfs
parent3435813379c276c3c020cb4e3942554be0040ecc (diff)
libglusterfs: add 'run' sub-lib for safe and convenient invocation of external programs
Summary: - arguments first collected, then the invocation happens with fork + exec - flexible specification of arguments (besides si{mp,ng}le argument addition, support for adding multiple of them at a time / specifying one with printf style formatting) [ ==> goodbye printf percentage soup when composing commands ] - single point of error check - simple command runs are done in just one line - support for redirection, popen(3) like functionality API is documented in details in libglusterfs/src/run.h Signed-off-by: Csaba Henk <csaba@gluster.com> Signed-off-by: Anand Avati <avati@gluster.com> BUG: 2562 (invoke external commands precisely with fork + exec) URL: http://bugs.gluster.com/cgi-bin/bugzilla3/show_bug.cgi?id=2562
Diffstat (limited to 'libglusterfs')
-rw-r--r--libglusterfs/src/Makefile.am4
-rw-r--r--libglusterfs/src/mem-types.h4
-rw-r--r--libglusterfs/src/run.c476
-rw-r--r--libglusterfs/src/run.h197
4 files changed, 678 insertions, 3 deletions
diff --git a/libglusterfs/src/Makefile.am b/libglusterfs/src/Makefile.am
index cc7a8e889bd..3405ee5c319 100644
--- a/libglusterfs/src/Makefile.am
+++ b/libglusterfs/src/Makefile.am
@@ -6,9 +6,9 @@ libglusterfs_la_LIBADD = @LEXLIB@
lib_LTLIBRARIES = libglusterfs.la
-libglusterfs_la_SOURCES = dict.c graph.lex.c y.tab.c xlator.c logging.c hashfn.c defaults.c common-utils.c timer.c inode.c call-stub.c compat.c fd.c compat-errno.c event.c mem-pool.c gf-dirent.c syscall.c iobuf.c globals.c statedump.c stack.c checksum.c $(CONTRIBDIR)/md5/md5.c $(CONTRIBDIR)/rbtree/rb.c rbthash.c latency.c graph.c $(CONTRIBDIR)/uuid/clear.c $(CONTRIBDIR)/uuid/copy.c $(CONTRIBDIR)/uuid/gen_uuid.c $(CONTRIBDIR)/uuid/pack.c $(CONTRIBDIR)/uuid/parse.c $(CONTRIBDIR)/uuid/unparse.c $(CONTRIBDIR)/uuid/uuid_time.c $(CONTRIBDIR)/uuid/compare.c $(CONTRIBDIR)/uuid/isnull.c $(CONTRIBDIR)/uuid/unpack.c syncop.c graph-print.c trie.c
+libglusterfs_la_SOURCES = dict.c graph.lex.c y.tab.c xlator.c logging.c hashfn.c defaults.c common-utils.c timer.c inode.c call-stub.c compat.c fd.c compat-errno.c event.c mem-pool.c gf-dirent.c syscall.c iobuf.c globals.c statedump.c stack.c checksum.c $(CONTRIBDIR)/md5/md5.c $(CONTRIBDIR)/rbtree/rb.c rbthash.c latency.c graph.c $(CONTRIBDIR)/uuid/clear.c $(CONTRIBDIR)/uuid/copy.c $(CONTRIBDIR)/uuid/gen_uuid.c $(CONTRIBDIR)/uuid/pack.c $(CONTRIBDIR)/uuid/parse.c $(CONTRIBDIR)/uuid/unparse.c $(CONTRIBDIR)/uuid/uuid_time.c $(CONTRIBDIR)/uuid/compare.c $(CONTRIBDIR)/uuid/isnull.c $(CONTRIBDIR)/uuid/unpack.c syncop.c graph-print.c trie.c run.c
-noinst_HEADERS = common-utils.h defaults.h dict.h glusterfs.h hashfn.h logging.h xlator.h stack.h timer.h list.h inode.h call-stub.h compat.h fd.h revision.h compat-errno.h event.h mem-pool.h byte-order.h gf-dirent.h locking.h syscall.h iobuf.h globals.h statedump.h checksum.h $(CONTRIBDIR)/md5/md5.h $(CONTRIBDIR)/rbtree/rb.h rbthash.h iatt.h latency.h mem-types.h $(CONTRIBDIR)/uuid/uuidd.h $(CONTRIBDIR)/uuid/uuid.h $(CONTRIBDIR)/uuid/uuidP.h $(CONTRIBDIR)/uuid/uuid_types.h syncop.h graph-utils.h trie.h
+noinst_HEADERS = common-utils.h defaults.h dict.h glusterfs.h hashfn.h logging.h xlator.h stack.h timer.h list.h inode.h call-stub.h compat.h fd.h revision.h compat-errno.h event.h mem-pool.h byte-order.h gf-dirent.h locking.h syscall.h iobuf.h globals.h statedump.h checksum.h $(CONTRIBDIR)/md5/md5.h $(CONTRIBDIR)/rbtree/rb.h rbthash.h iatt.h latency.h mem-types.h $(CONTRIBDIR)/uuid/uuidd.h $(CONTRIBDIR)/uuid/uuid.h $(CONTRIBDIR)/uuid/uuidP.h $(CONTRIBDIR)/uuid/uuid_types.h syncop.h graph-utils.h trie.h run.h
EXTRA_DIST = graph.l graph.y $(CONTRIBDIR)/apple/daemon.c $(CONTRIBDIR)/apple/daemon.h
diff --git a/libglusterfs/src/mem-types.h b/libglusterfs/src/mem-types.h
index 9d63d28af97..d09472f992f 100644
--- a/libglusterfs/src/mem-types.h
+++ b/libglusterfs/src/mem-types.h
@@ -104,6 +104,8 @@ enum gf_common_mem_types_ {
gf_common_mt_trie_node = 79,
gf_common_mt_trie_buf = 80,
gf_common_mt_trie_end = 81,
- gf_common_mt_end = 82
+ gf_common_mt_run_argv = 82,
+ gf_common_mt_run_logbuf = 83,
+ gf_common_mt_end = 84
};
#endif
diff --git a/libglusterfs/src/run.c b/libglusterfs/src/run.c
new file mode 100644
index 00000000000..052a2fdb5a9
--- /dev/null
+++ b/libglusterfs/src/run.c
@@ -0,0 +1,476 @@
+/*
+ Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com>
+ This file is part of GlusterFS.
+
+ GlusterFS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ GlusterFS is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <assert.h>
+#include <sys/wait.h>
+
+#ifdef RUN_STANDALONE
+#define GF_CALLOC(n, s, t) calloc(n, s)
+#define GF_ASSERT(cond) assert(cond)
+#define GF_REALLOC(p, s) realloc(p, s)
+#define GF_FREE(p) free(p)
+#define gf_strdup(s) strdup(s)
+#define gf_vasprintf(p, f, va) vasprintf(p, f, va)
+#define gf_loglevel_t int
+#define gf_log(dom, levl, fmt, args...) printf("LOG: " fmt "\n", ##args)
+#define LOG_DEBUG 0
+#ifdef __linux__
+#define GF_LINUX_HOST_OS
+#endif
+#else /* ! RUN_STANDALONE */
+#include "glusterfs.h"
+#include "common-utils.h"
+#endif
+
+#include "run.h"
+
+void
+runinit (runner_t *runner)
+{
+ int i = 0;
+
+ runner->argvlen = 64;
+ runner->argv = GF_CALLOC (runner->argvlen,
+ sizeof (*runner->argv),
+ gf_common_mt_run_argv);
+ runner->runerr = runner->argv ? 0 : errno;
+ runner->chpid = -1;
+ for (i = 0; i < 3; i++) {
+ runner->chfd[i] = -1;
+ runner->chio[i] = NULL;
+ }
+}
+
+FILE *
+runner_chio (runner_t *runner, int fd)
+{
+ GF_ASSERT (fd > 0 && fd < 3);
+
+ return runner->chio[fd];
+}
+
+static void
+runner_insert_arg (runner_t *runner, char *arg)
+{
+ int i = 0;
+
+ GF_ASSERT (arg);
+
+ if (runner->runerr)
+ return;
+
+ for (i = 0; i < runner->argvlen; i++) {
+ if (runner->argv[i] == NULL)
+ break;
+ }
+ GF_ASSERT (i < runner->argvlen);
+
+ if (i == runner->argvlen - 1) {
+ runner->argv = GF_REALLOC (runner->argv,
+ runner->argvlen * 2 * sizeof (*runner->argv));
+ if (!runner->argv) {
+ runner->runerr = errno;
+ return;
+ }
+ memset (/* "+" is aware of the type of its left side,
+ * no need to multiply with type-size */
+ runner->argv + runner->argvlen,
+ 0, runner->argvlen * sizeof (*runner->argv));
+ runner->argvlen *= 2;
+ }
+
+ runner->argv[i] = arg;
+}
+
+void
+runner_add_arg (runner_t *runner, const char *arg)
+{
+ arg = gf_strdup (arg);
+ if (!arg) {
+ runner->runerr = errno;
+ return;
+ }
+
+ runner_insert_arg (runner, (char *)arg);
+}
+
+static void
+runner_va_add_args (runner_t *runner, va_list argp)
+{
+ const char *arg;
+
+ while ((arg = va_arg (argp, const char *)))
+ runner_add_arg (runner, arg);
+}
+
+void
+runner_add_args (runner_t *runner, ...)
+{
+ va_list argp;
+
+ va_start (argp, runner);
+ runner_va_add_args (runner, argp);
+ va_end (argp);
+}
+
+void
+runner_argprintf (runner_t *runner, const char *format, ...)
+{
+ va_list argva;
+ char *arg = NULL;
+ int ret = 0;
+
+ va_start (argva, format);
+ ret = gf_vasprintf (&arg, format, argva);
+ va_end (argva);
+
+ if (ret < 0) {
+ runner->runerr = errno;
+ return;
+ }
+
+ runner_insert_arg (runner, arg);
+}
+
+void
+runner_log (runner_t *runner, const char *dom, gf_loglevel_t lvl,
+ const char *msg)
+{
+ char *buf = NULL;
+ size_t len = 0;
+ int i = 0;
+
+ if (runner->runerr)
+ return;
+
+ for (i = 0;; i++) {
+ if (runner->argv[i] == NULL)
+ break;
+ len += (strlen (runner->argv[i]) + 1);
+ }
+
+ buf = GF_CALLOC (1, len + 1, gf_common_mt_run_logbuf);
+ if (!buf) {
+ runner->runerr = errno;
+ return;
+ }
+ for (i = 0;; i++) {
+ if (runner->argv[i] == NULL)
+ break;
+ strcat (buf, runner->argv[i]);
+ strcat (buf, " ");
+ }
+ if (len > 0)
+ buf[len - 1] = '\0';
+
+ gf_log (dom, lvl, "%s: %s", msg, buf);
+
+ GF_FREE (buf);
+}
+
+void
+runner_redir (runner_t *runner, int fd, int tgt_fd)
+{
+ GF_ASSERT (fd > 0 && fd < 3);
+
+ runner->chfd[fd] = (tgt_fd >= 0) ? tgt_fd : -2;
+}
+
+int
+runner_start (runner_t *runner)
+{
+ int pi[3][2] = {{-1, -1}, {-1, -1}, {-1, -1}};
+ int xpi[2];
+ int ret = 0;
+ int errno_priv = 0;
+ int i = 0;
+ sigset_t set;
+
+ if (runner->runerr) {
+ errno = runner->runerr;
+ return -1;
+ }
+
+ GF_ASSERT (runner->argv[0]);
+
+ /* set up a channel to child to communicate back
+ * possible execve(2) failures
+ */
+ ret = pipe(xpi);
+ if (ret != -1)
+ ret = fcntl (xpi[1], F_SETFD, FD_CLOEXEC);
+
+ for (i = 0; i < 3; i++) {
+ if (runner->chfd[i] != -2)
+ continue;
+ ret = pipe (pi[i]);
+ if (ret != -1) {
+ runner->chio[i] = fdopen (pi[i][i ? 0 : 1], i ? "r" : "w");
+ if (!runner->chio[i])
+ ret = -1;
+ }
+ }
+
+ if (ret != -1)
+ runner->chpid = fork ();
+ switch (runner->chpid) {
+ case -1:
+ errno_priv = errno;
+ close (xpi[0]);
+ close (xpi[1]);
+ for (i = 0; i < 3; i++) {
+ close (pi[i][0]);
+ close (pi[i][1]);
+ }
+ errno = errno_priv;
+ return -1;
+ case 0:
+ for (i = 0; i < 3; i++)
+ close (pi[i][i ? 0 : 1]);
+ close (xpi[0]);
+ ret = 0;
+#ifdef GF_LINUX_HOST_OS
+ {
+ DIR *d = NULL;
+ struct dirent *de = NULL;
+ char *e = NULL;
+
+ d = opendir ("/proc/self/fd");
+ if (d) {
+ while ((de = readdir (d))) {
+ i = strtoul (de->d_name, &e, 10);
+ if (*e == '\0' &&
+ i > 2 && i != dirfd (d) &&
+ i != pi[0][0] && i != pi[1][1] &&
+ i != pi[2][1] && i != xpi[1])
+ close (i);
+ }
+ closedir (d);
+ } else
+ ret = -1;
+ }
+#else
+ for (i = 3; i < 65536; i++) {
+ if (i != pi[0][0] && i != pi[1][1] &&
+ i != pi[2][1] && i != xpi[1])
+ close (i);
+ }
+#endif
+
+ for (i = 0; i < 3; i++) {
+ if (ret == -1)
+ break;
+ switch (runner->chfd[i]) {
+ case -1:
+ /* no redir */
+ break;
+ case -2:
+ /* redir to pipe */
+ ret = dup2 (pi[i][i ? 1 : 0], i);
+ errno_priv = errno;
+ close (pi[i][i ? 1 : 0]);
+ errno = errno_priv;
+ break;
+ default:
+ /* redir to file */
+ ret = dup2 (runner->chfd[i], i);
+ }
+ }
+
+ if (ret != -1) {
+ /* save child from inheriting our singal handling */
+ sigemptyset (&set);
+ sigprocmask (SIG_SETMASK, &set, NULL);
+
+ execvp (runner->argv[0], runner->argv);
+ }
+ write (xpi[1], &errno, sizeof (errno));
+ _exit (1);
+ }
+
+ errno_priv = errno;
+ for (i = 0; i < 3; i++)
+ close (pi[i][i ? 1 : 0]);
+ close (xpi[1]);
+ if (ret == -1) {
+ for (i = 0; i < 3; i++) {
+ if (runner->chio[i]) {
+ fclose (runner->chio[i]);
+ runner->chio[i] = NULL;
+ }
+ }
+ } else {
+ ret = read (xpi[0], (char *)&errno_priv, sizeof (errno_priv));
+ close (xpi[0]);
+ if (ret <= 0)
+ return 0;
+ GF_ASSERT (ret == sizeof (errno_priv));
+ }
+ errno = errno_priv;
+ return -1;
+}
+
+int
+runner_end_reuse (runner_t *runner)
+{
+ int i = 0;
+ int ret = -1;
+ int chstat = 0;
+
+ if (runner->chpid > 0) {
+ if (waitpid (runner->chpid, &chstat, 0) == runner->chpid)
+ ret = chstat;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (runner->chio[i]) {
+ fclose (runner->chio[i]);
+ runner->chio[i] = NULL;
+ }
+ }
+
+ return ret;
+}
+
+int
+runner_end (runner_t *runner)
+{
+ int i = 0;
+ int ret = -1;
+ char **p = NULL;
+
+ ret = runner_end_reuse (runner);
+
+ for (p = runner->argv; *p; p++)
+ GF_FREE (*p);
+ GF_FREE (runner->argv);
+ for (i = 0; i < 3; i++)
+ close (runner->chfd[i]);
+
+ return ret;
+}
+
+static int
+runner_run_generic (runner_t *runner, int (*rfin)(runner_t *runner))
+{
+ int ret = 0;
+
+ ret = runner_start (runner);
+ if (ret != 0)
+ return -1;
+
+ return rfin (runner) ? -1 : 0;
+}
+
+int
+runner_run (runner_t *runner)
+{
+ return runner_run_generic (runner, runner_end);
+}
+
+int
+runner_run_reuse (runner_t *runner)
+{
+ return runner_run_generic (runner, runner_end_reuse);
+}
+
+int
+runcmd (const char *arg, ...)
+{
+ runner_t runner;
+ va_list argp;
+
+ runinit (&runner);
+ /* ISO C requires a named argument before '...' */
+ runner_add_arg (&runner, arg);
+
+ va_start (argp, arg);
+ runner_va_add_args (&runner, argp);
+ va_end (argp);
+
+ return runner_run (&runner);
+}
+
+#ifdef RUN_DO_TESTS
+static void
+TBANNER (const char *txt)
+{
+ printf("######\n### testing %s\n", txt);
+}
+
+int
+main ()
+{
+ runner_t runner;
+ char buf[80];
+ char *wdbuf;;
+ int ret;
+ long pathmax = pathconf ("/", _PC_PATH_MAX);
+
+ wdbuf = malloc (pathmax);
+ assert (wdbuf);
+ getcwd (wdbuf, pathmax);
+
+ TBANNER ("basic functionality");
+ runcmd ("echo", "a", "b", NULL);
+
+ TBANNER ("argv extension");
+ runcmd ("echo", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
+ "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
+ "21", "22", "23", "24", "25", "26", "27", "28", "29", "30",
+ "31", "32", "33", "34", "35", "36", "37", "38", "39", "40",
+ "41", "42", "43", "44", "45", "46", "47", "48", "49", "50",
+ "51", "52", "53", "54", "55", "56", "57", "58", "59", "60",
+ "61", "62", "63", "64", "65", "66", "67", "68", "69", "70",
+ "71", "72", "73", "74", "75", "76", "77", "78", "79", "80",
+ "81", "82", "83", "84", "85", "86", "87", "88", "89", "90",
+ "91", "92", "93", "94", "95", "96", "97", "98", "99", "100", NULL);
+
+ TBANNER ("add_args, argprintf, log, and popen-style functionality");
+ runinit (&runner);
+ runner_add_args (&runner, "echo", "pid:", NULL);
+ runner_argprintf (&runner, "%d\n", getpid());
+ runner_add_arg (&runner, "wd:");
+ runner_add_arg (&runner, wdbuf);
+ runner_redir (&runner, 1, RUN_PIPE);
+ runner_start (&runner);
+ runner_log (&runner, "(x)", LOG_DEBUG, "starting program");
+ while (fgets (buf, sizeof(buf), runner_chio (&runner, 1)))
+ printf ("got: %s", buf);
+ runner_end (&runner);
+
+ TBANNER ("execve error reporting");
+ ret = runcmd ("bafflavvitty", NULL);
+ printf ("%d %d [%s]\n", ret, errno, strerror (errno));
+
+ return 0;
+}
+#endif
diff --git a/libglusterfs/src/run.h b/libglusterfs/src/run.h
new file mode 100644
index 00000000000..f0b8330f4c5
--- /dev/null
+++ b/libglusterfs/src/run.h
@@ -0,0 +1,197 @@
+/*
+ Copyright (c) 2011 Gluster, Inc. <http://www.gluster.com>
+ This file is part of GlusterFS.
+
+ GlusterFS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ GlusterFS is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see
+ <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __RUN_H__
+#define __RUN_H__
+
+#define RUN_PIPE -1
+
+struct runner {
+ char **argv;
+ unsigned argvlen;
+ int runerr;
+ pid_t chpid;
+ int chfd[3];
+ FILE *chio[3];
+};
+
+typedef struct runner runner_t;
+
+/**
+ * initialize runner_t instance.
+ *
+ * @param runner pointer to runner_t instance
+ */
+void runinit (runner_t *runner);
+
+/**
+ * get FILE pointer to which child's stdio is redirected.
+ *
+ * @param runner pointer to runner_t instance
+ * @param fd specifies which standard file descriptor is
+ * is asked for
+ *
+ * @see runner_redir()
+ */
+FILE *runner_chio (runner_t *runner, int fd);
+
+/**
+ * add an argument.
+ *
+ * 'arg' is duplicated.
+ *
+ * Errors are deferred, no error handling is necessary.
+ *
+ * @param runner pointer to runner_t instance
+ * @param arg command line argument
+ */
+void runner_add_arg (runner_t *runner, const char *arg);
+
+/**
+ * add a sequence of arguments.
+ *
+ * Variadic function, calls runner_add_arg() on each vararg.
+ * Argument sequence MUST be NULL terminated.
+ *
+ * Errors are deferred, no error handling is necessary.
+ *
+ * @param runner pointer to runner_t instance
+ *
+ * @see runner_add_arg()
+ */
+void runner_add_args (runner_t *runner, ...);
+
+/**
+ * add an argument with printf style formatting.
+ *
+ * Errors are deferred, no error handling is necessary.
+ *
+ * @param runner pointer to runner_t instance
+ * @param format printf style format specifier
+ */
+void runner_argprintf (runner_t *runner, const char *format, ...);
+
+/**
+ * log a message about the command to be run.
+ *
+ * @param runner pointer to runner_t instance
+ *
+ * @param dom log domain
+ * @param lvl log level
+ * @param msg message with which the command is prefixed in log
+ *
+ * @see gf_log()
+ */
+void runner_log (runner_t *runner, const char *dom, gf_loglevel_t lvl,
+ const char *msg);
+
+/**
+ * set up redirection for child.
+ *
+ * @param runner pointer to runner_t instance
+ *
+ * @param fd fd of child to redirect (0, 1, or 2)
+ * @param tgt_fd fd on parent side to redirect to.
+ * Note that runner_end() will close tgt_fd,
+ * if user needs it in another context it should
+ * be dup'd beforehand.
+ * RUN_PIPE can be used for requiring a
+ * pipe from child to parent. The FILE
+ * created for this purpose will be
+ * accessible via runner_chio() (after
+ * runner_start() has been invoked).
+ *
+ * @see runner_start(), dup(2), runner_chio(), runner_start()
+ */
+void
+runner_redir (runner_t *runner, int fd, int tgt_fd);
+
+/**
+ * spawn child with accumulated arg list.
+ *
+ * @param runner pointer to runner_t instance
+ *
+ * @return 0 on succesful spawn
+ * -1 on failure (either due to earlier errors or execve(2) failing)
+ *
+ * @see runner_cout()
+ */
+int runner_start (runner_t *runner);
+
+/**
+ * complete operation and free resources.
+ *
+ * If child exists, waits for it. Redirections will be closed.
+ * Dynamically allocated memory shall be freed.
+ *
+ * @param runner pointer to runner_t instance
+ *
+ * @return 0 if child terminated successfully
+ * -1 if there is no running child
+ * n > 0 if child failed; value to be interpreted as status
+ * in waitpid(2)
+ *
+ * @see waitpid(2)
+ */
+int runner_end (runner_t *runner);
+
+/**
+ * variant of runner_end() which does not free internal data
+ * so that the runner instance can be run again.
+ *
+ * @see runner_end()
+ */
+int runner_end_reuse (runner_t *runner);
+
+/**
+ * spawn and child, take it to completion and free resources.
+ *
+ * Essentially it's a concatenation of runner_start() and runner_end()
+ * with simplified return semantics.
+ *
+ * @param runner pointer to runner_t instance
+ *
+ * @return 0 on success
+ * -1 on failuire
+ *
+ * @see runner_start(), runner_end()
+ */
+int runner_run (runner_t *runner);
+
+/**
+ * variant of runner_run() which does not free internal data
+ * so that the runner instance can be run again.
+ *
+ * @see runner_run()
+ */
+int runner_run_reuse (runner_t *runner);
+
+/**
+ * run a command with args.
+ *
+ * Variadic function, child process is spawned with
+ * the given sequence of args and waited for.
+ * Argument sequence MUST be NULL terminated.
+ *
+ * @return 0 on success
+ * -1 on failure
+ */
+int runcmd (const char *arg, ...);
+
+#endif