]> git.ipfire.org Git - thirdparty/git.git/blobdiff - run-command.c
run-command API: remove "env" member, always use "env_array"
[thirdparty/git.git] / run-command.c
index 2961f7e55efdc1dc7b675fa8ff2da3e5b35d5442..ebade086700d0ccc411d642c21486ef7209ab4c5 100644 (file)
@@ -8,6 +8,8 @@
 #include "string-list.h"
 #include "quote.h"
 #include "config.h"
+#include "packfile.h"
+#include "hook.h"
 
 void child_process_init(struct child_process *child)
 {
@@ -210,9 +212,9 @@ static char *locate_in_PATH(const char *file)
        return NULL;
 }
 
-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;
@@ -378,7 +380,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)
        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");
@@ -390,12 +392,12 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)
                error_errno("sigprocmask failed restoring signals");
                break;
        case CHILD_ERR_ENOENT:
-               error_errno("cannot run %s", cmd->argv[0]);
+               error_errno("cannot run %s", cmd->args.v[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);
@@ -403,7 +405,7 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)
 
 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");
 
        /*
@@ -413,11 +415,11 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
        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 {
-               strvec_pushv(out, cmd->argv);
+               strvec_pushv(out, cmd->args.v);
        }
 
        /*
@@ -653,15 +655,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_array.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);
@@ -674,11 +671,6 @@ int start_command(struct child_process *cmd)
        int failed_errno;
        char *str;
 
-       if (!cmd->argv)
-               cmd->argv = cmd->args.v;
-       if (!cmd->env)
-               cmd->env = cmd->env_array.v;
-
        /*
         * In case of errors we must keep the promise to close FDs
         * that have been passed in via ->in and ->out.
@@ -727,7 +719,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;
@@ -740,6 +732,9 @@ fail_pipe:
 
        fflush(NULL);
 
+       if (cmd->close_object_store)
+               close_object_store(the_repository->objects);
+
 #ifndef GIT_WINDOWS_NATIVE
 {
        int notify_pipe[2];
@@ -753,7 +748,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;
        }
 
@@ -765,7 +760,7 @@ fail_pipe:
                set_cloexec(null_fd);
        }
 
-       childenv = prep_childenv(cmd->env);
+       childenv = prep_childenv(cmd->env_array.v);
        atfork_prepare(&as);
 
        /*
@@ -863,7 +858,7 @@ fail_pipe:
        }
        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);
 
@@ -880,7 +875,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;
@@ -897,7 +892,7 @@ end_of_spawn:
 #else
 {
        int fhin = 0, fhout = 1, fherr = 2;
-       const char **sargv = cmd->argv;
+       const char **sargv = cmd->args.v;
        struct strvec nargv = STRVEC_INIT;
 
        if (cmd->no_stdin)
@@ -924,20 +919,20 @@ 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->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v, (char**) cmd->env_array.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);
 
        strvec_clear(&nargv);
-       cmd->argv = sargv;
+       cmd->args.v = sargv;
        if (fhin != 0)
                close(fhin);
        if (fhout != 1)
@@ -987,7 +982,7 @@ 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();
@@ -996,7 +991,7 @@ int finish_command(struct child_process *cmd)
 
 int finish_command_in_signal(struct child_process *cmd)
 {
-       int ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);
+       int ret = wait_or_whine(cmd->pid, cmd->args.v[0], 1);
        trace2_child_exit(cmd, ret);
        return ret;
 }
@@ -1034,7 +1029,7 @@ 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;
+       strvec_pushv(&cmd.args, 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;
@@ -1042,8 +1037,10 @@ int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
        cmd.use_shell = opt & RUN_USING_SHELL ? 1 : 0;
        cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
        cmd.wait_after_clean = opt & RUN_WAIT_AFTER_CLEAN ? 1 : 0;
+       cmd.close_object_store = opt & RUN_CLOSE_OBJECT_STORE ? 1 : 0;
        cmd.dir = dir;
-       cmd.env = env;
+       if (env)
+               strvec_pushv(&cmd.env_array, (const char **)env);
        cmd.trace2_child_class = tr2_class;
        return run_command(&cmd);
 }
@@ -1093,7 +1090,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;
 }
 
@@ -1317,40 +1314,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;
@@ -1363,7 +1326,8 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)
        strvec_push(&hook.args, p);
        while ((p = va_arg(args, const char *)))
                strvec_push(&hook.args, p);
-       hook.env = env;
+       if (env)
+               strvec_pushv(&hook.env_array, (const char **)env);
        hook.no_stdin = 1;
        hook.stdout_to_stderr = 1;
        hook.trace2_hook_name = name;
@@ -1884,6 +1848,7 @@ int run_auto_maintenance(int quiet)
                return 0;
 
        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");
 
@@ -1901,3 +1866,132 @@ void prepare_other_repo_env(struct strvec *env_array, const char *new_git_dir)
        }
        strvec_pushf(env_array, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir);
 }
+
+enum start_bg_result start_bg_command(struct child_process *cmd,
+                                     start_bg_wait_cb *wait_cb,
+                                     void *cb_data,
+                                     unsigned int timeout_sec)
+{
+       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";
+
+       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
+                * 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;
+}