submodule recursing: do not write a config variable twice
[gitweb.git] / run-command.c
index 84e4ce66e9d3da22be318813d75cbb3a94b614e4..574b81d3e82bbe6de31b141c3951ab408daad5e6 100644 (file)
@@ -3,6 +3,8 @@
 #include "exec_cmd.h"
 #include "sigchain.h"
 #include "argv-array.h"
+#include "thread-utils.h"
+#include "strbuf.h"
 
 void child_process_init(struct child_process *child)
 {
@@ -19,6 +21,7 @@ void child_process_clear(struct child_process *child)
 
 struct child_to_clean {
        pid_t pid;
+       struct child_process *process;
        struct child_to_clean *next;
 };
 static struct child_to_clean *children_to_clean;
@@ -26,10 +29,41 @@ static int installed_child_cleanup_handler;
 
 static void cleanup_children(int sig, int in_signal)
 {
+       struct child_to_clean *children_to_wait_for = NULL;
+
        while (children_to_clean) {
                struct child_to_clean *p = children_to_clean;
                children_to_clean = p->next;
+
+               if (p->process && !in_signal) {
+                       struct child_process *process = p->process;
+                       if (process->clean_on_exit_handler) {
+                               trace_printf(
+                                       "trace: run_command: running exit handler for pid %"
+                                       PRIuMAX, (uintmax_t)p->pid
+                               );
+                               process->clean_on_exit_handler(process);
+                       }
+               }
+
                kill(p->pid, sig);
+
+               if (p->process && p->process->wait_after_clean) {
+                       p->next = children_to_wait_for;
+                       children_to_wait_for = p;
+               } else {
+                       if (!in_signal)
+                               free(p);
+               }
+       }
+
+       while (children_to_wait_for) {
+               struct child_to_clean *p = children_to_wait_for;
+               children_to_wait_for = p->next;
+
+               while (waitpid(p->pid, NULL, 0) < 0 && errno == EINTR)
+                       ; /* spin waiting for process exit or error */
+
                if (!in_signal)
                        free(p);
        }
@@ -47,10 +81,11 @@ static void cleanup_children_on_exit(void)
        cleanup_children(SIGTERM, 0);
 }
 
-static void mark_child_for_cleanup(pid_t pid)
+static void mark_child_for_cleanup(pid_t pid, struct child_process *process)
 {
        struct child_to_clean *p = xmalloc(sizeof(*p));
        p->pid = pid;
+       p->process = process;
        p->next = children_to_clean;
        children_to_clean = p;
 
@@ -158,50 +193,41 @@ int sane_execvp(const char *file, char * const argv[])
        return -1;
 }
 
-static const char **prepare_shell_cmd(const char **argv)
+static const char **prepare_shell_cmd(struct argv_array *out, const char **argv)
 {
-       int argc, nargc = 0;
-       const char **nargv;
-
-       for (argc = 0; argv[argc]; argc++)
-               ; /* just counting */
-       /* +1 for NULL, +3 for "sh -c" plus extra $0 */
-       nargv = xmalloc(sizeof(*nargv) * (argc + 1 + 3));
-
-       if (argc < 1)
+       if (!argv[0])
                die("BUG: shell command is empty");
 
        if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
 #ifndef GIT_WINDOWS_NATIVE
-               nargv[nargc++] = SHELL_PATH;
+               argv_array_push(out, SHELL_PATH);
 #else
-               nargv[nargc++] = "sh";
+               argv_array_push(out, "sh");
 #endif
-               nargv[nargc++] = "-c";
-
-               if (argc < 2)
-                       nargv[nargc++] = argv[0];
-               else {
-                       struct strbuf arg0 = STRBUF_INIT;
-                       strbuf_addf(&arg0, "%s \"$@\"", argv[0]);
-                       nargv[nargc++] = strbuf_detach(&arg0, NULL);
-               }
-       }
+               argv_array_push(out, "-c");
 
-       for (argc = 0; argv[argc]; argc++)
-               nargv[nargc++] = argv[argc];
-       nargv[nargc] = NULL;
+               /*
+                * If we have no extra arguments, we do not even need to
+                * bother with the "$@" magic.
+                */
+               if (!argv[1])
+                       argv_array_push(out, argv[0]);
+               else
+                       argv_array_pushf(out, "%s \"$@\"", argv[0]);
+       }
 
-       return nargv;
+       argv_array_pushv(out, argv);
+       return out->argv;
 }
 
 #ifndef GIT_WINDOWS_NATIVE
 static int execv_shell_cmd(const char **argv)
 {
-       const char **nargv = prepare_shell_cmd(argv);
-       trace_argv_printf(nargv, "trace: exec:");
-       sane_execvp(nargv[0], (char **)nargv);
-       free(nargv);
+       struct argv_array nargv = ARGV_ARRAY_INIT;
+       prepare_shell_cmd(&nargv, argv);
+       trace_argv_printf(nargv.argv, "trace: exec:");
+       sane_execvp(nargv.argv[0], (char **)nargv.argv);
+       argv_array_clear(&nargv);
        return -1;
 }
 #endif
@@ -240,12 +266,12 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal)
 
        if (waiting < 0) {
                failed_errno = errno;
-               error("waitpid for %s failed: %s", argv0, strerror(errno));
+               error_errno("waitpid for %s failed", argv0);
        } else if (waiting != pid) {
                error("waitpid is confused (%s)", argv0);
        } else if (WIFSIGNALED(status)) {
                code = WTERMSIG(status);
-               if (code != SIGINT && code != SIGQUIT)
+               if (code != SIGINT && code != SIGQUIT && code != SIGPIPE)
                        error("%s died of signal %d", argv0, code);
                /*
                 * This return value is chosen so that code & 0xff
@@ -427,10 +453,9 @@ int start_command(struct child_process *cmd)
                }
        }
        if (cmd->pid < 0)
-               error("cannot fork() for %s: %s", cmd->argv[0],
-                       strerror(errno));
+               error_errno("cannot fork() for %s", cmd->argv[0]);
        else if (cmd->clean_on_exit)
-               mark_child_for_cleanup(cmd->pid);
+               mark_child_for_cleanup(cmd->pid, cmd);
 
        /*
         * Wait for child's execvp. If the execvp succeeds (or if fork()
@@ -455,6 +480,7 @@ int start_command(struct child_process *cmd)
 {
        int fhin = 0, fhout = 1, fherr = 2;
        const char **sargv = cmd->argv;
+       struct argv_array nargv = ARGV_ARRAY_INIT;
 
        if (cmd->no_stdin)
                fhin = open("/dev/null", O_RDWR);
@@ -480,21 +506,19 @@ int start_command(struct child_process *cmd)
                fhout = dup(cmd->out);
 
        if (cmd->git_cmd)
-               cmd->argv = prepare_git_cmd(cmd->argv);
+               cmd->argv = prepare_git_cmd(&nargv, cmd->argv);
        else if (cmd->use_shell)
-               cmd->argv = prepare_shell_cmd(cmd->argv);
+               cmd->argv = prepare_shell_cmd(&nargv, cmd->argv);
 
        cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env,
                        cmd->dir, fhin, fhout, fherr);
        failed_errno = errno;
        if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
-               error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
+               error_errno("cannot spawn %s", cmd->argv[0]);
        if (cmd->clean_on_exit && cmd->pid >= 0)
-               mark_child_for_cleanup(cmd->pid);
-
-       if (cmd->git_cmd)
-               free(cmd->argv);
+               mark_child_for_cleanup(cmd->pid, cmd);
 
+       argv_array_clear(&nargv);
        cmd->argv = sargv;
        if (fhin != 0)
                close(fhin);
@@ -598,6 +622,16 @@ static void *run_thread(void *data)
        struct async *async = data;
        intptr_t ret;
 
+       if (async->isolate_sigpipe) {
+               sigset_t mask;
+               sigemptyset(&mask);
+               sigaddset(&mask, SIGPIPE);
+               if (pthread_sigmask(SIG_BLOCK, &mask, NULL) < 0) {
+                       ret = error("unable to block SIGPIPE in async thread");
+                       return (void *)ret;
+               }
+       }
+
        pthread_setspecific(async_key, async);
        ret = async->proc(async->proc_in, async->proc_out, async->data);
        return (void *)ret;
@@ -607,7 +641,7 @@ static NORETURN void die_async(const char *err, va_list params)
 {
        vreportf("fatal: ", err, params);
 
-       if (!pthread_equal(main_thread, pthread_self())) {
+       if (in_async()) {
                struct async *async = pthread_getspecific(async_key);
                if (async->proc_in >= 0)
                        close(async->proc_in);
@@ -626,6 +660,18 @@ static int async_die_is_recursing(void)
        return ret != NULL;
 }
 
+int in_async(void)
+{
+       if (!main_thread_set)
+               return 0; /* no asyncs started yet */
+       return !pthread_equal(main_thread, pthread_self());
+}
+
+static void NORETURN async_exit(int code)
+{
+       pthread_exit((void *)(intptr_t)code);
+}
+
 #else
 
 static struct {
@@ -665,8 +711,32 @@ int git_atexit(void (*handler)(void))
 }
 #define atexit git_atexit
 
+static int process_is_async;
+int in_async(void)
+{
+       return process_is_async;
+}
+
+static void NORETURN async_exit(int code)
+{
+       exit(code);
+}
+
 #endif
 
+void check_pipe(int err)
+{
+       if (err == EPIPE) {
+               if (in_async())
+                       async_exit(141);
+
+               signal(SIGPIPE, SIG_DFL);
+               raise(SIGPIPE);
+               /* Should never happen, but just in case... */
+               exit(141);
+       }
+}
+
 int start_async(struct async *async)
 {
        int need_in, need_out;
@@ -678,7 +748,7 @@ int start_async(struct async *async)
                if (pipe(fdin) < 0) {
                        if (async->out > 0)
                                close(async->out);
-                       return error("cannot create pipe: %s", strerror(errno));
+                       return error_errno("cannot create pipe");
                }
                async->in = fdin[1];
        }
@@ -690,7 +760,7 @@ int start_async(struct async *async)
                                close_pair(fdin);
                        else if (async->in)
                                close(async->in);
-                       return error("cannot create pipe: %s", strerror(errno));
+                       return error_errno("cannot create pipe");
                }
                async->out = fdout[0];
        }
@@ -715,7 +785,7 @@ int start_async(struct async *async)
 
        async->pid = fork();
        if (async->pid < 0) {
-               error("fork (async) failed: %s", strerror(errno));
+               error_errno("fork (async) failed");
                goto error;
        }
        if (!async->pid) {
@@ -724,10 +794,11 @@ int start_async(struct async *async)
                if (need_out)
                        close(fdout[0]);
                git_atexit_clear();
+               process_is_async = 1;
                exit(!!async->proc(proc_in, proc_out, async->data));
        }
 
-       mark_child_for_cleanup(async->pid);
+       mark_child_for_cleanup(async->pid, NULL);
 
        if (need_in)
                close(fdin[0]);
@@ -761,7 +832,7 @@ int start_async(struct async *async)
        {
                int err = pthread_create(&async->tid, NULL, run_thread, async);
                if (err) {
-                       error("cannot create thread: %s", strerror(err));
+                       error_errno("cannot create thread");
                        goto error;
                }
        }
@@ -800,8 +871,14 @@ const char *find_hook(const char *name)
 
        strbuf_reset(&path);
        strbuf_git_path(&path, "hooks/%s", name);
-       if (access(path.buf, X_OK) < 0)
+       if (access(path.buf, X_OK) < 0) {
+#ifdef STRIP_EXTENSION
+               strbuf_addstr(&path, STRIP_EXTENSION);
+               if (access(path.buf, X_OK) >= 0)
+                       return path.buf;
+#endif
                return NULL;
+       }
        return path.buf;
 }
 
@@ -836,18 +913,475 @@ int run_hook_le(const char *const *env, const char *name, ...)
        return ret;
 }
 
-int capture_command(struct child_process *cmd, struct strbuf *buf, size_t hint)
+struct io_pump {
+       /* initialized by caller */
+       int fd;
+       int type; /* POLLOUT or POLLIN */
+       union {
+               struct {
+                       const char *buf;
+                       size_t len;
+               } out;
+               struct {
+                       struct strbuf *buf;
+                       size_t hint;
+               } in;
+       } u;
+
+       /* returned by pump_io */
+       int error; /* 0 for success, otherwise errno */
+
+       /* internal use */
+       struct pollfd *pfd;
+};
+
+static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)
+{
+       int pollsize = 0;
+       int i;
+
+       for (i = 0; i < nr; i++) {
+               struct io_pump *io = &slots[i];
+               if (io->fd < 0)
+                       continue;
+               pfd[pollsize].fd = io->fd;
+               pfd[pollsize].events = io->type;
+               io->pfd = &pfd[pollsize++];
+       }
+
+       if (!pollsize)
+               return 0;
+
+       if (poll(pfd, pollsize, -1) < 0) {
+               if (errno == EINTR)
+                       return 1;
+               die_errno("poll failed");
+       }
+
+       for (i = 0; i < nr; i++) {
+               struct io_pump *io = &slots[i];
+
+               if (io->fd < 0)
+                       continue;
+
+               if (!(io->pfd->revents & (POLLOUT|POLLIN|POLLHUP|POLLERR|POLLNVAL)))
+                       continue;
+
+               if (io->type == POLLOUT) {
+                       ssize_t len = xwrite(io->fd,
+                                            io->u.out.buf, io->u.out.len);
+                       if (len < 0) {
+                               io->error = errno;
+                               close(io->fd);
+                               io->fd = -1;
+                       } else {
+                               io->u.out.buf += len;
+                               io->u.out.len -= len;
+                               if (!io->u.out.len) {
+                                       close(io->fd);
+                                       io->fd = -1;
+                               }
+                       }
+               }
+
+               if (io->type == POLLIN) {
+                       ssize_t len = strbuf_read_once(io->u.in.buf,
+                                                      io->fd, io->u.in.hint);
+                       if (len < 0)
+                               io->error = errno;
+                       if (len <= 0) {
+                               close(io->fd);
+                               io->fd = -1;
+                       }
+               }
+       }
+
+       return 1;
+}
+
+static int pump_io(struct io_pump *slots, int nr)
+{
+       struct pollfd *pfd;
+       int i;
+
+       for (i = 0; i < nr; i++)
+               slots[i].error = 0;
+
+       ALLOC_ARRAY(pfd, nr);
+       while (pump_io_round(slots, nr, pfd))
+               ; /* nothing */
+       free(pfd);
+
+       /* There may be multiple errno values, so just pick the first. */
+       for (i = 0; i < nr; i++) {
+               if (slots[i].error) {
+                       errno = slots[i].error;
+                       return -1;
+               }
+       }
+       return 0;
+}
+
+
+int pipe_command(struct child_process *cmd,
+                const char *in, size_t in_len,
+                struct strbuf *out, size_t out_hint,
+                struct strbuf *err, size_t err_hint)
 {
-       cmd->out = -1;
+       struct io_pump io[3];
+       int nr = 0;
+
+       if (in)
+               cmd->in = -1;
+       if (out)
+               cmd->out = -1;
+       if (err)
+               cmd->err = -1;
+
        if (start_command(cmd) < 0)
                return -1;
 
-       if (strbuf_read(buf, cmd->out, hint) < 0) {
-               close(cmd->out);
+       if (in) {
+               io[nr].fd = cmd->in;
+               io[nr].type = POLLOUT;
+               io[nr].u.out.buf = in;
+               io[nr].u.out.len = in_len;
+               nr++;
+       }
+       if (out) {
+               io[nr].fd = cmd->out;
+               io[nr].type = POLLIN;
+               io[nr].u.in.buf = out;
+               io[nr].u.in.hint = out_hint;
+               nr++;
+       }
+       if (err) {
+               io[nr].fd = cmd->err;
+               io[nr].type = POLLIN;
+               io[nr].u.in.buf = err;
+               io[nr].u.in.hint = err_hint;
+               nr++;
+       }
+
+       if (pump_io(io, nr) < 0) {
                finish_command(cmd); /* throw away exit code */
                return -1;
        }
 
-       close(cmd->out);
        return finish_command(cmd);
 }
+
+enum child_state {
+       GIT_CP_FREE,
+       GIT_CP_WORKING,
+       GIT_CP_WAIT_CLEANUP,
+};
+
+struct parallel_processes {
+       void *data;
+
+       int max_processes;
+       int nr_processes;
+
+       get_next_task_fn get_next_task;
+       start_failure_fn start_failure;
+       task_finished_fn task_finished;
+
+       struct {
+               enum child_state state;
+               struct child_process process;
+               struct strbuf err;
+               void *data;
+       } *children;
+       /*
+        * The struct pollfd is logically part of *children,
+        * but the system call expects it as its own array.
+        */
+       struct pollfd *pfd;
+
+       unsigned shutdown : 1;
+
+       int output_owner;
+       struct strbuf buffered_output; /* of finished children */
+};
+
+static int default_start_failure(struct strbuf *out,
+                                void *pp_cb,
+                                void *pp_task_cb)
+{
+       return 0;
+}
+
+static int default_task_finished(int result,
+                                struct strbuf *out,
+                                void *pp_cb,
+                                void *pp_task_cb)
+{
+       return 0;
+}
+
+static void kill_children(struct parallel_processes *pp, int signo)
+{
+       int i, n = pp->max_processes;
+
+       for (i = 0; i < n; i++)
+               if (pp->children[i].state == GIT_CP_WORKING)
+                       kill(pp->children[i].process.pid, signo);
+}
+
+static struct parallel_processes *pp_for_signal;
+
+static void handle_children_on_signal(int signo)
+{
+       kill_children(pp_for_signal, signo);
+       sigchain_pop(signo);
+       raise(signo);
+}
+
+static void pp_init(struct parallel_processes *pp,
+                   int n,
+                   get_next_task_fn get_next_task,
+                   start_failure_fn start_failure,
+                   task_finished_fn task_finished,
+                   void *data)
+{
+       int i;
+
+       if (n < 1)
+               n = online_cpus();
+
+       pp->max_processes = n;
+
+       trace_printf("run_processes_parallel: preparing to run up to %d tasks", n);
+
+       pp->data = data;
+       if (!get_next_task)
+               die("BUG: you need to specify a get_next_task function");
+       pp->get_next_task = get_next_task;
+
+       pp->start_failure = start_failure ? start_failure : default_start_failure;
+       pp->task_finished = task_finished ? task_finished : default_task_finished;
+
+       pp->nr_processes = 0;
+       pp->output_owner = 0;
+       pp->shutdown = 0;
+       pp->children = xcalloc(n, sizeof(*pp->children));
+       pp->pfd = xcalloc(n, sizeof(*pp->pfd));
+       strbuf_init(&pp->buffered_output, 0);
+
+       for (i = 0; i < n; i++) {
+               strbuf_init(&pp->children[i].err, 0);
+               child_process_init(&pp->children[i].process);
+               pp->pfd[i].events = POLLIN | POLLHUP;
+               pp->pfd[i].fd = -1;
+       }
+
+       pp_for_signal = pp;
+       sigchain_push_common(handle_children_on_signal);
+}
+
+static void pp_cleanup(struct parallel_processes *pp)
+{
+       int i;
+
+       trace_printf("run_processes_parallel: done");
+       for (i = 0; i < pp->max_processes; i++) {
+               strbuf_release(&pp->children[i].err);
+               child_process_clear(&pp->children[i].process);
+       }
+
+       free(pp->children);
+       free(pp->pfd);
+
+       /*
+        * When get_next_task added messages to the buffer in its last
+        * iteration, the buffered output is non empty.
+        */
+       strbuf_write(&pp->buffered_output, stderr);
+       strbuf_release(&pp->buffered_output);
+
+       sigchain_pop_common();
+}
+
+/* returns
+ *  0 if a new task was started.
+ *  1 if no new jobs was started (get_next_task ran out of work, non critical
+ *    problem with starting a new command)
+ * <0 no new job was started, user wishes to shutdown early. Use negative code
+ *    to signal the children.
+ */
+static int pp_start_one(struct parallel_processes *pp)
+{
+       int i, code;
+
+       for (i = 0; i < pp->max_processes; i++)
+               if (pp->children[i].state == GIT_CP_FREE)
+                       break;
+       if (i == pp->max_processes)
+               die("BUG: bookkeeping is hard");
+
+       code = pp->get_next_task(&pp->children[i].process,
+                                &pp->children[i].err,
+                                pp->data,
+                                &pp->children[i].data);
+       if (!code) {
+               strbuf_addbuf(&pp->buffered_output, &pp->children[i].err);
+               strbuf_reset(&pp->children[i].err);
+               return 1;
+       }
+       pp->children[i].process.err = -1;
+       pp->children[i].process.stdout_to_stderr = 1;
+       pp->children[i].process.no_stdin = 1;
+
+       if (start_command(&pp->children[i].process)) {
+               code = pp->start_failure(&pp->children[i].err,
+                                        pp->data,
+                                        &pp->children[i].data);
+               strbuf_addbuf(&pp->buffered_output, &pp->children[i].err);
+               strbuf_reset(&pp->children[i].err);
+               if (code)
+                       pp->shutdown = 1;
+               return code;
+       }
+
+       pp->nr_processes++;
+       pp->children[i].state = GIT_CP_WORKING;
+       pp->pfd[i].fd = pp->children[i].process.err;
+       return 0;
+}
+
+static void pp_buffer_stderr(struct parallel_processes *pp, int output_timeout)
+{
+       int i;
+
+       while ((i = poll(pp->pfd, pp->max_processes, output_timeout)) < 0) {
+               if (errno == EINTR)
+                       continue;
+               pp_cleanup(pp);
+               die_errno("poll");
+       }
+
+       /* Buffer output from all pipes. */
+       for (i = 0; i < pp->max_processes; i++) {
+               if (pp->children[i].state == GIT_CP_WORKING &&
+                   pp->pfd[i].revents & (POLLIN | POLLHUP)) {
+                       int n = strbuf_read_once(&pp->children[i].err,
+                                                pp->children[i].process.err, 0);
+                       if (n == 0) {
+                               close(pp->children[i].process.err);
+                               pp->children[i].state = GIT_CP_WAIT_CLEANUP;
+                       } else if (n < 0)
+                               if (errno != EAGAIN)
+                                       die_errno("read");
+               }
+       }
+}
+
+static void pp_output(struct parallel_processes *pp)
+{
+       int i = pp->output_owner;
+       if (pp->children[i].state == GIT_CP_WORKING &&
+           pp->children[i].err.len) {
+               strbuf_write(&pp->children[i].err, stderr);
+               strbuf_reset(&pp->children[i].err);
+       }
+}
+
+static int pp_collect_finished(struct parallel_processes *pp)
+{
+       int i, code;
+       int n = pp->max_processes;
+       int result = 0;
+
+       while (pp->nr_processes > 0) {
+               for (i = 0; i < pp->max_processes; i++)
+                       if (pp->children[i].state == GIT_CP_WAIT_CLEANUP)
+                               break;
+               if (i == pp->max_processes)
+                       break;
+
+               code = finish_command(&pp->children[i].process);
+
+               code = pp->task_finished(code,
+                                        &pp->children[i].err, pp->data,
+                                        &pp->children[i].data);
+
+               if (code)
+                       result = code;
+               if (code < 0)
+                       break;
+
+               pp->nr_processes--;
+               pp->children[i].state = GIT_CP_FREE;
+               pp->pfd[i].fd = -1;
+               child_process_init(&pp->children[i].process);
+
+               if (i != pp->output_owner) {
+                       strbuf_addbuf(&pp->buffered_output, &pp->children[i].err);
+                       strbuf_reset(&pp->children[i].err);
+               } else {
+                       strbuf_write(&pp->children[i].err, stderr);
+                       strbuf_reset(&pp->children[i].err);
+
+                       /* Output all other finished child processes */
+                       strbuf_write(&pp->buffered_output, stderr);
+                       strbuf_reset(&pp->buffered_output);
+
+                       /*
+                        * Pick next process to output live.
+                        * NEEDSWORK:
+                        * For now we pick it randomly by doing a round
+                        * robin. Later we may want to pick the one with
+                        * the most output or the longest or shortest
+                        * running process time.
+                        */
+                       for (i = 0; i < n; i++)
+                               if (pp->children[(pp->output_owner + i) % n].state == GIT_CP_WORKING)
+                                       break;
+                       pp->output_owner = (pp->output_owner + i) % n;
+               }
+       }
+       return result;
+}
+
+int run_processes_parallel(int n,
+                          get_next_task_fn get_next_task,
+                          start_failure_fn start_failure,
+                          task_finished_fn task_finished,
+                          void *pp_cb)
+{
+       int i, code;
+       int output_timeout = 100;
+       int spawn_cap = 4;
+       struct parallel_processes pp;
+
+       pp_init(&pp, n, get_next_task, start_failure, task_finished, pp_cb);
+       while (1) {
+               for (i = 0;
+                   i < spawn_cap && !pp.shutdown &&
+                   pp.nr_processes < pp.max_processes;
+                   i++) {
+                       code = pp_start_one(&pp);
+                       if (!code)
+                               continue;
+                       if (code < 0) {
+                               pp.shutdown = 1;
+                               kill_children(&pp, -code);
+                       }
+                       break;
+               }
+               if (!pp.nr_processes)
+                       break;
+               pp_buffer_stderr(&pp, output_timeout);
+               pp_output(&pp);
+               code = pp_collect_finished(&pp);
+               if (code) {
+                       pp.shutdown = 1;
+                       if (code < 0)
+                               kill_children(&pp, -code);
+               }
+       }
+
+       pp_cleanup(&pp);
+       return 0;
+}