diff options
Diffstat (limited to 'run-command.c')
-rw-r--r-- | run-command.c | 747 |
1 files changed, 410 insertions, 337 deletions
diff --git a/run-command.c b/run-command.c index 9b3a57d..0e74357 100644 --- a/run-command.c +++ b/run-command.c @@ -1,24 +1,31 @@ -#include "cache.h" +#include "git-compat-util.h" #include "run-command.h" +#include "environment.h" #include "exec-cmd.h" +#include "gettext.h" #include "sigchain.h" -#include "argv-array.h" +#include "strvec.h" +#include "symlinks.h" #include "thread-utils.h" #include "strbuf.h" #include "string-list.h" +#include "trace.h" +#include "trace2.h" #include "quote.h" +#include "config.h" +#include "packfile.h" +#include "compat/nonblock.h" void child_process_init(struct child_process *child) { - memset(child, 0, sizeof(*child)); - argv_array_init(&child->args); - argv_array_init(&child->env_array); + struct child_process blank = CHILD_PROCESS_INIT; + memcpy(child, &blank, sizeof(*child)); } void child_process_clear(struct child_process *child) { - argv_array_clear(&child->args); - argv_array_clear(&child->env_array); + strvec_clear(&child->args); + strvec_clear(&child->env); } struct child_to_clean { @@ -161,6 +168,7 @@ int is_executable(const char *name) return st.st_mode & S_IXUSR; } +#ifndef locate_in_PATH /* * Search $PATH for a command. This emulates the path search that * execvp would perform, without actually executing the command so it @@ -209,10 +217,11 @@ static char *locate_in_PATH(const char *file) strbuf_release(&buf); return NULL; } +#endif -static int exists_in_PATH(const char *file) +int exists_in_PATH(const char *command) { - char *r = locate_in_PATH(file); + char *r = locate_in_PATH(command); int found = r != NULL; free(r); return found; @@ -263,31 +272,31 @@ int sane_execvp(const char *file, char * const argv[]) return -1; } -static const char **prepare_shell_cmd(struct argv_array *out, const char **argv) +static const char **prepare_shell_cmd(struct strvec *out, const char **argv) { if (!argv[0]) BUG("shell command is empty"); if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) { #ifndef GIT_WINDOWS_NATIVE - argv_array_push(out, SHELL_PATH); + strvec_push(out, SHELL_PATH); #else - argv_array_push(out, "sh"); + strvec_push(out, "sh"); #endif - argv_array_push(out, "-c"); + strvec_push(out, "-c"); /* * 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]); + strvec_push(out, argv[0]); else - argv_array_pushf(out, "%s \"$@\"", argv[0]); + strvec_pushf(out, "%s \"$@\"", argv[0]); } - argv_array_pushv(out, argv); - return out->argv; + strvec_pushv(out, argv); + return out->v; } #ifndef GIT_WINDOWS_NATIVE @@ -298,7 +307,6 @@ enum child_errcode { CHILD_ERR_DUP2, CHILD_ERR_CLOSE, CHILD_ERR_SIGPROCMASK, - CHILD_ERR_ENOENT, CHILD_ERR_SILENT, CHILD_ERR_ERRNO }; @@ -338,28 +346,19 @@ static void child_close_pair(int fd[2]) child_close(fd[1]); } -/* - * parent will make it look like the child spewed a fatal error and died - * this is needed to prevent changes to t0061. - */ -static void fake_fatal(const char *err, va_list params) -{ - vreportf("fatal: ", err, params); -} - -static void child_error_fn(const char *err, va_list params) +static void child_error_fn(const char *err UNUSED, va_list params UNUSED) { const char msg[] = "error() should not be called in child\n"; xwrite(2, msg, sizeof(msg) - 1); } -static void child_warn_fn(const char *err, va_list params) +static void child_warn_fn(const char *err UNUSED, va_list params UNUSED) { const char msg[] = "warn() should not be called in child\n"; xwrite(2, msg, sizeof(msg) - 1); } -static void NORETURN child_die_fn(const char *err, va_list params) +static void NORETURN child_die_fn(const char *err UNUSED, va_list params UNUSED) { const char msg[] = "die() should not be called in child\n"; xwrite(2, msg, sizeof(msg) - 1); @@ -370,15 +369,16 @@ static void NORETURN child_die_fn(const char *err, va_list params) static void child_err_spew(struct child_process *cmd, struct child_err *cerr) { static void (*old_errfn)(const char *err, va_list params); + report_fn die_message_routine = get_die_message_routine(); old_errfn = get_error_routine(); - set_error_routine(fake_fatal); + set_error_routine(die_message_routine); errno = cerr->syserr; switch (cerr->err) { case CHILD_ERR_CHDIR: error_errno("exec '%s': cd to '%s' failed", - cmd->argv[0], cmd->dir); + cmd->args.v[0], cmd->dir); break; case CHILD_ERR_DUP2: error_errno("dup2() in child failed"); @@ -389,35 +389,32 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr) case CHILD_ERR_SIGPROCMASK: error_errno("sigprocmask failed restoring signals"); break; - case CHILD_ERR_ENOENT: - error_errno("cannot run %s", cmd->argv[0]); - break; case CHILD_ERR_SILENT: break; case CHILD_ERR_ERRNO: - error_errno("cannot exec '%s'", cmd->argv[0]); + error_errno("cannot exec '%s'", cmd->args.v[0]); break; } set_error_routine(old_errfn); } -static int prepare_cmd(struct argv_array *out, const struct child_process *cmd) +static int prepare_cmd(struct strvec *out, const struct child_process *cmd) { - if (!cmd->argv[0]) + if (!cmd->args.v[0]) BUG("command is empty"); /* * Add SHELL_PATH so in the event exec fails with ENOEXEC we can * attempt to interpret the command with 'sh'. */ - argv_array_push(out, SHELL_PATH); + strvec_push(out, SHELL_PATH); if (cmd->git_cmd) { - prepare_git_cmd(out, cmd->argv); + prepare_git_cmd(out, cmd->args.v); } else if (cmd->use_shell) { - prepare_shell_cmd(out, cmd->argv); + prepare_shell_cmd(out, cmd->args.v); } else { - argv_array_pushv(out, cmd->argv); + strvec_pushv(out, cmd->args.v); } /* @@ -426,13 +423,13 @@ static int prepare_cmd(struct argv_array *out, const struct child_process *cmd) * there are dir separator characters, we have exec attempt to invoke * the command directly. */ - if (!has_dir_sep(out->argv[1])) { - char *program = locate_in_PATH(out->argv[1]); + if (!has_dir_sep(out->v[1])) { + char *program = locate_in_PATH(out->v[1]); if (program) { - free((char *)out->argv[1]); - out->argv[1] = program; + free((char *)out->v[1]); + out->v[1] = program; } else { - argv_array_clear(out); + strvec_clear(out); errno = ENOENT; return -1; } @@ -550,17 +547,17 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal) while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) ; /* nothing */ - if (in_signal) - return 0; if (waiting < 0) { failed_errno = errno; - error_errno("waitpid for %s failed", argv0); + if (!in_signal) + error_errno("waitpid for %s failed", argv0); } else if (waiting != pid) { - error("waitpid is confused (%s)", argv0); + if (!in_signal) + error("waitpid is confused (%s)", argv0); } else if (WIFSIGNALED(status)) { code = WTERMSIG(status); - if (code != SIGINT && code != SIGQUIT && code != SIGPIPE) + if (!in_signal && code != SIGINT && code != SIGQUIT && code != SIGPIPE) error("%s died of signal %d", argv0, code); /* * This return value is chosen so that code & 0xff @@ -571,10 +568,12 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal) } else if (WIFEXITED(status)) { code = WEXITSTATUS(status); } else { - error("waitpid is confused (%s)", argv0); + if (!in_signal) + error("waitpid is confused (%s)", argv0); } - clear_child_for_cleanup(pid); + if (!in_signal) + clear_child_for_cleanup(pid); errno = failed_errno; return code; @@ -650,15 +649,10 @@ static void trace_run_command(const struct child_process *cp) sq_quote_buf_pretty(&buf, cp->dir); strbuf_addch(&buf, ';'); } - /* - * The caller is responsible for initializing cp->env from - * cp->env_array if needed. We only check one place. - */ - if (cp->env) - trace_add_env(&buf, cp->env); + trace_add_env(&buf, cp->env.v); if (cp->git_cmd) strbuf_addstr(&buf, " git"); - sq_quote_argv_pretty(&buf, cp->argv); + sq_quote_argv_pretty(&buf, cp->args.v); trace_printf("%s", buf.buf); strbuf_release(&buf); @@ -671,11 +665,6 @@ int start_command(struct child_process *cmd) int failed_errno; char *str; - if (!cmd->argv) - cmd->argv = cmd->args.argv; - if (!cmd->env) - cmd->env = cmd->env_array.argv; - /* * In case of errors we must keep the promise to close FDs * that have been passed in via ->in and ->out. @@ -724,7 +713,7 @@ int start_command(struct child_process *cmd) str = "standard error"; fail_pipe: error("cannot create %s pipe for %s: %s", - str, cmd->argv[0], strerror(failed_errno)); + str, cmd->args.v[0], strerror(failed_errno)); child_process_clear(cmd); errno = failed_errno; return -1; @@ -737,12 +726,15 @@ fail_pipe: fflush(NULL); + if (cmd->close_object_store) + close_object_store(the_repository->objects); + #ifndef GIT_WINDOWS_NATIVE { int notify_pipe[2]; int null_fd = -1; char **childenv; - struct argv_array argv = ARGV_ARRAY_INIT; + struct strvec argv = STRVEC_INIT; struct child_err cerr; struct atfork_state as; @@ -750,7 +742,7 @@ fail_pipe: failed_errno = errno; cmd->pid = -1; if (!cmd->silent_exec_failure) - error_errno("cannot run %s", cmd->argv[0]); + error_errno("cannot run %s", cmd->args.v[0]); goto end_of_spawn; } @@ -758,13 +750,11 @@ fail_pipe: notify_pipe[0] = notify_pipe[1] = -1; if (cmd->no_stdin || cmd->no_stdout || cmd->no_stderr) { - null_fd = open("/dev/null", O_RDWR | O_CLOEXEC); - if (null_fd < 0) - die_errno(_("open /dev/null failed")); + null_fd = xopen("/dev/null", O_RDWR | O_CLOEXEC); set_cloexec(null_fd); } - childenv = prep_childenv(cmd->env); + childenv = prep_childenv(cmd->env.v); atfork_prepare(&as); /* @@ -846,23 +836,19 @@ fail_pipe: * be used in the event exec failed with ENOEXEC at which point * we will try to interpret the command using 'sh'. */ - execve(argv.argv[1], (char *const *) argv.argv + 1, + execve(argv.v[1], (char *const *) argv.v + 1, (char *const *) childenv); if (errno == ENOEXEC) - execve(argv.argv[0], (char *const *) argv.argv, + execve(argv.v[0], (char *const *) argv.v, (char *const *) childenv); - if (errno == ENOENT) { - if (cmd->silent_exec_failure) - child_die(CHILD_ERR_SILENT); - child_die(CHILD_ERR_ENOENT); - } else { - child_die(CHILD_ERR_ERRNO); - } + if (cmd->silent_exec_failure && errno == ENOENT) + child_die(CHILD_ERR_SILENT); + child_die(CHILD_ERR_ERRNO); } atfork_parent(&as); if (cmd->pid < 0) - error_errno("cannot fork() for %s", cmd->argv[0]); + error_errno("cannot fork() for %s", cmd->args.v[0]); else if (cmd->clean_on_exit) mark_child_for_cleanup(cmd->pid, cmd); @@ -879,7 +865,7 @@ fail_pipe: * At this point we know that fork() succeeded, but exec() * failed. Errors have been reported to our stderr. */ - wait_or_whine(cmd->pid, cmd->argv[0], 0); + wait_or_whine(cmd->pid, cmd->args.v[0], 0); child_err_spew(cmd, &cerr); failed_errno = errno; cmd->pid = -1; @@ -888,7 +874,7 @@ fail_pipe: if (null_fd >= 0) close(null_fd); - argv_array_clear(&argv); + strvec_clear(&argv); free(childenv); } end_of_spawn: @@ -896,8 +882,8 @@ end_of_spawn: #else { int fhin = 0, fhout = 1, fherr = 2; - const char **sargv = cmd->argv; - struct argv_array nargv = ARGV_ARRAY_INIT; + const char **sargv = cmd->args.v; + struct strvec nargv = STRVEC_INIT; if (cmd->no_stdin) fhin = open("/dev/null", O_RDWR); @@ -923,20 +909,21 @@ end_of_spawn: fhout = dup(cmd->out); if (cmd->git_cmd) - cmd->argv = prepare_git_cmd(&nargv, cmd->argv); + cmd->args.v = prepare_git_cmd(&nargv, sargv); else if (cmd->use_shell) - cmd->argv = prepare_shell_cmd(&nargv, cmd->argv); + cmd->args.v = prepare_shell_cmd(&nargv, sargv); - cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env, - cmd->dir, fhin, fhout, fherr); + cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, + (char**) cmd->env.v, + cmd->dir, fhin, fhout, fherr); failed_errno = errno; if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) - error_errno("cannot spawn %s", cmd->argv[0]); + error_errno("cannot spawn %s", cmd->args.v[0]); if (cmd->clean_on_exit && cmd->pid >= 0) mark_child_for_cleanup(cmd->pid, cmd); - argv_array_clear(&nargv); - cmd->argv = sargv; + strvec_clear(&nargv); + cmd->args.v = sargv; if (fhin != 0) close(fhin); if (fhout != 1) @@ -986,16 +973,18 @@ end_of_spawn: int finish_command(struct child_process *cmd) { - int ret = wait_or_whine(cmd->pid, cmd->argv[0], 0); + int ret = wait_or_whine(cmd->pid, cmd->args.v[0], 0); trace2_child_exit(cmd, ret); child_process_clear(cmd); + invalidate_lstat_cache(); return ret; } int finish_command_in_signal(struct child_process *cmd) { - int ret = wait_or_whine(cmd->pid, cmd->argv[0], 1); - trace2_child_exit(cmd, ret); + int ret = wait_or_whine(cmd->pid, cmd->args.v[0], 1); + if (ret != -1) + trace2_child_exit(cmd, ret); return ret; } @@ -1013,38 +1002,6 @@ int run_command(struct child_process *cmd) return finish_command(cmd); } -int run_command_v_opt(const char **argv, int opt) -{ - return run_command_v_opt_cd_env(argv, opt, NULL, NULL); -} - -int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class) -{ - return run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class); -} - -int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env) -{ - return run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL); -} - -int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir, - const char *const *env, const char *tr2_class) -{ - struct child_process cmd = CHILD_PROCESS_INIT; - cmd.argv = argv; - cmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0; - cmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0; - cmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0; - cmd.silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0; - cmd.use_shell = opt & RUN_USING_SHELL ? 1 : 0; - cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0; - cmd.dir = dir; - cmd.env = env; - cmd.trace2_child_class = tr2_class; - return run_command(&cmd); -} - #ifndef NO_PTHREADS static pthread_t main_thread; static int main_thread_set; @@ -1060,7 +1017,7 @@ static void *run_thread(void *data) sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGPIPE); - if (pthread_sigmask(SIG_BLOCK, &mask, NULL) < 0) { + if (pthread_sigmask(SIG_BLOCK, &mask, NULL)) { ret = error("unable to block SIGPIPE in async thread"); return (void *)ret; } @@ -1073,7 +1030,9 @@ static void *run_thread(void *data) static NORETURN void die_async(const char *err, va_list params) { - vreportf("fatal: ", err, params); + report_fn die_message_fn = get_die_message_routine(); + + die_message_fn(err, params); if (in_async()) { struct async *async = pthread_getspecific(async_key); @@ -1090,7 +1049,7 @@ static NORETURN void die_async(const char *err, va_list params) static int async_die_is_recursing(void) { void *ret = pthread_getspecific(async_die_counter); - pthread_setspecific(async_die_counter, (void *)1); + pthread_setspecific(async_die_counter, &async_die_counter); /* set to any non-NULL valid pointer */ return ret != NULL; } @@ -1289,13 +1248,19 @@ error: int finish_async(struct async *async) { #ifdef NO_PTHREADS - return wait_or_whine(async->pid, "child process", 0); + int ret = wait_or_whine(async->pid, "child process", 0); + + invalidate_lstat_cache(); + + return ret; #else void *ret = (void *)(intptr_t)(-1); if (pthread_join(async->tid, &ret)) error("pthread_join failed"); + invalidate_lstat_cache(); return (int)(intptr_t)ret; + #endif } @@ -1308,72 +1273,6 @@ int async_with_fork(void) #endif } -const char *find_hook(const char *name) -{ - static struct strbuf path = STRBUF_INIT; - - strbuf_reset(&path); - strbuf_git_path(&path, "hooks/%s", name); - if (access(path.buf, X_OK) < 0) { - int err = errno; - -#ifdef STRIP_EXTENSION - strbuf_addstr(&path, STRIP_EXTENSION); - if (access(path.buf, X_OK) >= 0) - return path.buf; - if (errno == EACCES) - err = errno; -#endif - - if (err == EACCES && advice_ignored_hook) { - static struct string_list advise_given = STRING_LIST_INIT_DUP; - - if (!string_list_lookup(&advise_given, name)) { - string_list_insert(&advise_given, name); - advise(_("The '%s' hook was ignored because " - "it's not set as executable.\n" - "You can disable this warning with " - "`git config advice.ignoredHook false`."), - path.buf); - } - } - return NULL; - } - return path.buf; -} - -int run_hook_ve(const char *const *env, const char *name, va_list args) -{ - struct child_process hook = CHILD_PROCESS_INIT; - const char *p; - - p = find_hook(name); - if (!p) - return 0; - - argv_array_push(&hook.args, p); - while ((p = va_arg(args, const char *))) - argv_array_push(&hook.args, p); - hook.env = env; - hook.no_stdin = 1; - hook.stdout_to_stderr = 1; - hook.trace2_hook_name = name; - - return run_command(&hook); -} - -int run_hook_le(const char *const *env, const char *name, ...) -{ - va_list args; - int ret; - - va_start(args, name); - ret = run_hook_ve(env, name, args); - va_end(args); - - return ret; -} - struct io_pump { /* initialized by caller */ int fd; @@ -1429,12 +1328,25 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd) continue; if (io->type == POLLOUT) { - ssize_t len = xwrite(io->fd, - io->u.out.buf, io->u.out.len); + ssize_t len; + + /* + * Don't use xwrite() here. It loops forever on EAGAIN, + * and we're in our own poll() loop here. + * + * Note that we lose xwrite()'s handling of MAX_IO_SIZE + * and EINTR, so we have to implement those ourselves. + */ + len = write(io->fd, io->u.out.buf, + io->u.out.len <= MAX_IO_SIZE ? + io->u.out.len : MAX_IO_SIZE); if (len < 0) { - io->error = errno; - close(io->fd); - io->fd = -1; + if (errno != EINTR && errno != EAGAIN && + errno != ENOSPC) { + io->error = errno; + close(io->fd); + io->fd = -1; + } } else { io->u.out.buf += len; io->u.out.len -= len; @@ -1503,6 +1415,15 @@ int pipe_command(struct child_process *cmd, return -1; if (in) { + if (enable_pipe_nonblock(cmd->in) < 0) { + error_errno("unable to make pipe non-blocking"); + close(cmd->in); + if (out) + close(cmd->out); + if (err) + close(cmd->err); + return -1; + } io[nr].fd = cmd->in; io[nr].type = POLLOUT; io[nr].u.out.buf = in; @@ -1539,14 +1460,7 @@ enum child_state { }; 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; + size_t nr_processes; struct { enum child_state state; @@ -1562,91 +1476,78 @@ struct parallel_processes { unsigned shutdown : 1; - int output_owner; + size_t 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; -} +struct parallel_processes_for_signal { + const struct run_process_parallel_opts *opts; + const struct parallel_processes *pp; +}; -static int default_task_finished(int result, - struct strbuf *out, - void *pp_cb, - void *pp_task_cb) +static void kill_children(const struct parallel_processes *pp, + const struct run_process_parallel_opts *opts, + int signo) { - return 0; + for (size_t i = 0; i < opts->processes; i++) + if (pp->children[i].state == GIT_CP_WORKING) + kill(pp->children[i].process.pid, signo); } -static void kill_children(struct parallel_processes *pp, int signo) +static void kill_children_signal(const struct parallel_processes_for_signal *pp_sig, + 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); + kill_children(pp_sig->pp, pp_sig->opts, signo); } -static struct parallel_processes *pp_for_signal; +static struct parallel_processes_for_signal *pp_for_signal; static void handle_children_on_signal(int signo) { - kill_children(pp_for_signal, signo); + kill_children_signal(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) + const struct run_process_parallel_opts *opts, + struct parallel_processes_for_signal *pp_sig) { - int i; - - if (n < 1) - n = online_cpus(); + const size_t n = opts->processes; - pp->max_processes = n; + if (!n) + BUG("you must provide a non-zero number of processes!"); - trace_printf("run_processes_parallel: preparing to run up to %d tasks", n); + trace_printf("run_processes_parallel: preparing to run up to %"PRIuMAX" tasks", + (uintmax_t)n); - pp->data = data; - if (!get_next_task) + if (!opts->get_next_task) 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); + CALLOC_ARRAY(pp->children, n); + if (!opts->ungroup) + CALLOC_ARRAY(pp->pfd, n); - for (i = 0; i < n; i++) { + for (size_t 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; + if (pp->pfd) { + pp->pfd[i].events = POLLIN | POLLHUP; + pp->pfd[i].fd = -1; + } } - pp_for_signal = pp; + pp_sig->pp = pp; + pp_sig->opts = opts; + pp_for_signal = pp_sig; sigchain_push_common(handle_children_on_signal); } -static void pp_cleanup(struct parallel_processes *pp) +static void pp_cleanup(struct parallel_processes *pp, + const struct run_process_parallel_opts *opts) { - int i; - trace_printf("run_processes_parallel: done"); - for (i = 0; i < pp->max_processes; i++) { + for (size_t i = 0; i < opts->processes; i++) { strbuf_release(&pp->children[i].err); child_process_clear(&pp->children[i].process); } @@ -1671,35 +1572,55 @@ static void pp_cleanup(struct parallel_processes *pp) * <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) +static int pp_start_one(struct parallel_processes *pp, + const struct run_process_parallel_opts *opts) { - int i, code; + size_t i; + int code; - for (i = 0; i < pp->max_processes; i++) + for (i = 0; i < opts->processes; i++) if (pp->children[i].state == GIT_CP_FREE) break; - if (i == pp->max_processes) + if (i == opts->processes) BUG("bookkeeping is hard"); - code = pp->get_next_task(&pp->children[i].process, - &pp->children[i].err, - pp->data, - &pp->children[i].data); + /* + * By default, do not inherit stdin from the parent process - otherwise, + * all children would share stdin! Users may overwrite this to provide + * something to the child's stdin by having their 'get_next_task' + * callback assign 0 to .no_stdin and an appropriate integer to .in. + */ + pp->children[i].process.no_stdin = 1; + + code = opts->get_next_task(&pp->children[i].process, + opts->ungroup ? NULL : &pp->children[i].err, + opts->data, + &pp->children[i].data); if (!code) { - strbuf_addbuf(&pp->buffered_output, &pp->children[i].err); - strbuf_reset(&pp->children[i].err); + if (!opts->ungroup) { + 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 (!opts->ungroup) { + pp->children[i].process.err = -1; + pp->children[i].process.stdout_to_stderr = 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 (opts->start_failure) + code = opts->start_failure(opts->ungroup ? NULL : + &pp->children[i].err, + opts->data, + pp->children[i].data); + else + code = 0; + + if (!opts->ungroup) { + strbuf_addbuf(&pp->buffered_output, &pp->children[i].err); + strbuf_reset(&pp->children[i].err); + } if (code) pp->shutdown = 1; return code; @@ -1707,23 +1628,24 @@ static int pp_start_one(struct parallel_processes *pp) pp->nr_processes++; pp->children[i].state = GIT_CP_WORKING; - pp->pfd[i].fd = pp->children[i].process.err; + if (pp->pfd) + pp->pfd[i].fd = pp->children[i].process.err; return 0; } -static void pp_buffer_stderr(struct parallel_processes *pp, int output_timeout) +static void pp_buffer_stderr(struct parallel_processes *pp, + const struct run_process_parallel_opts *opts, + int output_timeout) { - int i; - - while ((i = poll(pp->pfd, pp->max_processes, output_timeout)) < 0) { + while (poll(pp->pfd, opts->processes, output_timeout) < 0) { if (errno == EINTR) continue; - pp_cleanup(pp); + pp_cleanup(pp, opts); die_errno("poll"); } /* Buffer output from all pipes. */ - for (i = 0; i < pp->max_processes; i++) { + for (size_t i = 0; i < opts->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, @@ -1738,9 +1660,10 @@ static void pp_buffer_stderr(struct parallel_processes *pp, int output_timeout) } } -static void pp_output(struct parallel_processes *pp) +static void pp_output(const struct parallel_processes *pp) { - int i = pp->output_owner; + size_t i = pp->output_owner; + if (pp->children[i].state == GIT_CP_WORKING && pp->children[i].err.len) { strbuf_write(&pp->children[i].err, stderr); @@ -1748,24 +1671,28 @@ static void pp_output(struct parallel_processes *pp) } } -static int pp_collect_finished(struct parallel_processes *pp) +static int pp_collect_finished(struct parallel_processes *pp, + const struct run_process_parallel_opts *opts) { - int i, code; - int n = pp->max_processes; + int code; + size_t i; int result = 0; while (pp->nr_processes > 0) { - for (i = 0; i < pp->max_processes; i++) + for (i = 0; i < opts->processes; i++) if (pp->children[i].state == GIT_CP_WAIT_CLEANUP) break; - if (i == pp->max_processes) + if (i == opts->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 (opts->task_finished) + code = opts->task_finished(code, opts->ungroup ? NULL : + &pp->children[i].err, opts->data, + pp->children[i].data); + else + code = 0; if (code) result = code; @@ -1774,13 +1701,18 @@ static int pp_collect_finished(struct parallel_processes *pp) pp->nr_processes--; pp->children[i].state = GIT_CP_FREE; - pp->pfd[i].fd = -1; + if (pp->pfd) + pp->pfd[i].fd = -1; child_process_init(&pp->children[i].process); - if (i != pp->output_owner) { + if (opts->ungroup) { + ; /* no strbuf_*() work to do here */ + } else if (i != pp->output_owner) { strbuf_addbuf(&pp->buffered_output, &pp->children[i].err); strbuf_reset(&pp->children[i].err); } else { + const size_t n = opts->processes; + strbuf_write(&pp->children[i].err, stderr); strbuf_reset(&pp->children[i].err); @@ -1805,75 +1737,216 @@ static int pp_collect_finished(struct parallel_processes *pp) 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) +void run_processes_parallel(const struct run_process_parallel_opts *opts) { 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); + struct parallel_processes_for_signal pp_sig; + struct parallel_processes pp = { + .buffered_output = STRBUF_INIT, + }; + /* options */ + const char *tr2_category = opts->tr2_category; + const char *tr2_label = opts->tr2_label; + const int do_trace2 = tr2_category && tr2_label; + + if (do_trace2) + trace2_region_enter_printf(tr2_category, tr2_label, NULL, + "max:%d", opts->processes); + + pp_init(&pp, opts, &pp_sig); while (1) { for (i = 0; i < spawn_cap && !pp.shutdown && - pp.nr_processes < pp.max_processes; + pp.nr_processes < opts->processes; i++) { - code = pp_start_one(&pp); + code = pp_start_one(&pp, opts); if (!code) continue; if (code < 0) { pp.shutdown = 1; - kill_children(&pp, -code); + kill_children(&pp, opts, -code); } break; } if (!pp.nr_processes) break; - pp_buffer_stderr(&pp, output_timeout); - pp_output(&pp); - code = pp_collect_finished(&pp); + if (opts->ungroup) { + for (size_t i = 0; i < opts->processes; i++) + pp.children[i].state = GIT_CP_WAIT_CLEANUP; + } else { + pp_buffer_stderr(&pp, opts, output_timeout); + pp_output(&pp); + } + code = pp_collect_finished(&pp, opts); if (code) { pp.shutdown = 1; if (code < 0) - kill_children(&pp, -code); + kill_children(&pp, opts,-code); } } - pp_cleanup(&pp); - return 0; + pp_cleanup(&pp, opts); + + if (do_trace2) + trace2_region_leave(tr2_category, tr2_label, NULL); } -int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task, - start_failure_fn start_failure, - task_finished_fn task_finished, void *pp_cb, - const char *tr2_category, const char *tr2_label) +int run_auto_maintenance(int quiet) { - int result; + int enabled; + struct child_process maint = CHILD_PROCESS_INIT; - trace2_region_enter_printf(tr2_category, tr2_label, NULL, "max:%d", - ((n < 1) ? online_cpus() : n)); + if (!git_config_get_bool("maintenance.auto", &enabled) && + !enabled) + return 0; - result = run_processes_parallel(n, get_next_task, start_failure, - task_finished, pp_cb); + maint.git_cmd = 1; + maint.close_object_store = 1; + strvec_pushl(&maint.args, "maintenance", "run", "--auto", NULL); + strvec_push(&maint.args, quiet ? "--quiet" : "--no-quiet"); - trace2_region_leave(tr2_category, tr2_label, NULL); + return run_command(&maint); +} - return result; +void prepare_other_repo_env(struct strvec *env, const char *new_git_dir) +{ + const char * const *var; + + for (var = local_repo_env; *var; var++) { + if (strcmp(*var, CONFIG_DATA_ENVIRONMENT) && + strcmp(*var, CONFIG_COUNT_ENVIRONMENT)) + strvec_push(env, *var); + } + strvec_pushf(env, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir); } -int run_auto_gc(int quiet) +enum start_bg_result start_bg_command(struct child_process *cmd, + start_bg_wait_cb *wait_cb, + void *cb_data, + unsigned int timeout_sec) { - struct argv_array argv_gc_auto = ARGV_ARRAY_INIT; - int status; + enum start_bg_result sbgr = SBGR_ERROR; + int ret; + int wait_status; + pid_t pid_seen; + time_t time_limit; + + /* + * We do not allow clean-on-exit because the child process + * should persist in the background and possibly/probably + * after this process exits. So we don't want to kill the + * child during our atexit routine. + */ + if (cmd->clean_on_exit) + BUG("start_bg_command() does not allow non-zero clean_on_exit"); + + if (!cmd->trace2_child_class) + cmd->trace2_child_class = "background"; - argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); - if (quiet) - argv_array_push(&argv_gc_auto, "--quiet"); - status = run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD); - argv_array_clear(&argv_gc_auto); - return status; + ret = start_command(cmd); + if (ret) { + /* + * We assume that if `start_command()` fails, we + * either get a complete `trace2_child_start() / + * trace2_child_exit()` pair or it fails before the + * `trace2_child_start()` is emitted, so we do not + * need to worry about it here. + * + * We also assume that `start_command()` does not add + * us to the cleanup list. And that it calls + * `child_process_clear()`. + */ + sbgr = SBGR_ERROR; + goto done; + } + + time(&time_limit); + time_limit += timeout_sec; + +wait: + pid_seen = waitpid(cmd->pid, &wait_status, WNOHANG); + + if (!pid_seen) { + /* + * The child is currently running. Ask the callback + * if the child is ready to do work or whether we + * should keep waiting for it to boot up. + */ + ret = (*wait_cb)(cmd, cb_data); + if (!ret) { + /* + * The child is running and "ready". + */ + trace2_child_ready(cmd, "ready"); + sbgr = SBGR_READY; + goto done; + } else if (ret > 0) { + /* + * The callback said to give it more time to boot up + * (subject to our timeout limit). + */ + time_t now; + + time(&now); + if (now < time_limit) + goto wait; + + /* + * Our timeout has expired. We don't try to + * kill the child, but rather let it continue + * (hopefully) trying to startup. + */ + trace2_child_ready(cmd, "timeout"); + sbgr = SBGR_TIMEOUT; + goto done; + } else { + /* + * The cb gave up on this child. It is still running, + * but our cb got an error trying to probe it. + */ + trace2_child_ready(cmd, "error"); + sbgr = SBGR_CB_ERROR; + goto done; + } + } + + else if (pid_seen == cmd->pid) { + int child_code = -1; + + /* + * The child started, but exited or was terminated + * before becoming "ready". + * + * We try to match the behavior of `wait_or_whine()` + * WRT the handling of WIFSIGNALED() and WIFEXITED() + * and convert the child's status to a return code for + * tracing purposes and emit the `trace2_child_exit()` + * event. + * + * We do not want the wait_or_whine() error message + * because we will be called by client-side library + * routines. + */ + if (WIFEXITED(wait_status)) + child_code = WEXITSTATUS(wait_status); + else if (WIFSIGNALED(wait_status)) + child_code = WTERMSIG(wait_status) + 128; + trace2_child_exit(cmd, child_code); + + sbgr = SBGR_DIED; + goto done; + } + + else if (pid_seen < 0 && errno == EINTR) + goto wait; + + trace2_child_exit(cmd, -1); + sbgr = SBGR_ERROR; + +done: + child_process_clear(cmd); + invalidate_lstat_cache(); + return sbgr; } |