Merge branch 'jk/run-command-eacces' into maint
authorJunio C Hamano <gitster@pobox.com>
Thu, 26 Apr 2012 17:51:40 +0000 (10:51 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 26 Apr 2012 17:51:41 +0000 (10:51 -0700)
When PATH contains an unreadable directory, alias expansion code did
not kick in, and failed with an error that said "git-subcmd" was not
found.

By Jeff King (1) and Ramsay Jones (1)
* jk/run-command-eacces:
run-command: treat inaccessible directories as ENOENT
compat/mingw.[ch]: Change return type of exec functions to int

1  2 
cache.h
run-command.c
diff --combined cache.h
index 806bf2b76fbbf7b30b70bad65220d5255a67c69e,5067226c2f70743a7ff61ec136501b829920f250..5e6eef9b4d0fca96fb0f90265f5f68d640f1583a
+++ b/cache.h
@@@ -432,7 -432,6 +432,7 @@@ extern char *git_work_tree_cfg
  extern int is_inside_work_tree(void);
  extern int have_git_dir(void);
  extern const char *get_git_dir(void);
 +extern int is_git_directory(const char *path);
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
@@@ -708,19 -707,6 +708,19 @@@ static inline void hashclr(unsigned cha
  #define EMPTY_TREE_SHA1_BIN \
         ((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
  
 +#define EMPTY_BLOB_SHA1_HEX \
 +      "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
 +#define EMPTY_BLOB_SHA1_BIN_LITERAL \
 +      "\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
 +      "\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
 +#define EMPTY_BLOB_SHA1_BIN \
 +      ((const unsigned char *) EMPTY_BLOB_SHA1_BIN_LITERAL)
 +
 +static inline int is_empty_blob_sha1(const unsigned char *sha1)
 +{
 +      return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
 +}
 +
  int git_mkstemp(char *path, size_t n, const char *template);
  
  int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
@@@ -941,22 -927,6 +941,22 @@@ extern const char *fmt_name(const char 
  extern const char *git_editor(void);
  extern const char *git_pager(int stdout_is_tty);
  
 +struct ident_split {
 +      const char *name_begin;
 +      const char *name_end;
 +      const char *mail_begin;
 +      const char *mail_end;
 +      const char *date_begin;
 +      const char *date_end;
 +      const char *tz_begin;
 +      const char *tz_end;
 +};
 +/*
 + * Signals an success with 0, but time part of the result may be NULL
 + * if the input lacks timestamp and zone
 + */
 +extern int split_ident_line(struct ident_split *, const char *, int);
 +
  struct checkout {
        const char *base_dir;
        int base_dir_len;
@@@ -979,9 -949,7 +979,9 @@@ struct cache_def 
  extern int has_symlink_leading_path(const char *name, int len);
  extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
  extern int check_leading_path(const char *name, int len);
 +extern int threaded_check_leading_path(struct cache_def *cache, const char *name, int len);
  extern int has_dirs_only_path(const char *name, int len, int prefix_len);
 +extern int threaded_has_dirs_only_path(struct cache_def *cache, const char *name, int len, int prefix_len);
  extern void schedule_dir_for_removal(const char *name, int len);
  extern void remove_scheduled_dirs(void);
  
@@@ -1069,7 -1037,6 +1069,7 @@@ struct extra_have_objects 
  };
  extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
  extern int server_supports(const char *feature);
 +extern const char *parse_feature_request(const char *features, const char *feature);
  
  extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
  
@@@ -1146,8 -1113,6 +1146,8 @@@ extern int git_config_from_file(config_
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern int git_config(config_fn_t fn, void *);
 +extern int git_config_with_options(config_fn_t fn, void *,
 +                                 const char *filename, int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
@@@ -1163,7 -1128,6 +1163,7 @@@ extern int git_config_parse_key(const c
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
 +extern int git_config_rename_section_in_file(const char *, const char *, const char *);
  extern const char *git_etc_gitconfig(void);
  extern int check_repository_format_version(const char *var, const char *value, void *cb);
  extern int git_env_bool(const char *, int);
@@@ -1174,13 -1138,7 +1174,13 @@@ extern const char *get_commit_output_en
  
  extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
  
 -extern const char *config_exclusive_filename;
 +struct config_include_data {
 +      int depth;
 +      config_fn_t fn;
 +      void *data;
 +};
 +#define CONFIG_INCLUDE_INIT { 0 }
 +extern int git_config_include(const char *name, const char *value, void *data);
  
  #define MAX_GITNAME (1000)
  extern char git_default_email[MAX_GITNAME];
@@@ -1217,8 -1175,6 +1217,8 @@@ extern void setup_pager(void)
  extern const char *pager_program;
  extern int pager_in_use(void);
  extern int pager_use_color;
 +extern int term_columns(void);
 +extern int decimal_width(int);
  
  extern const char *editor_program;
  extern const char *askpass_program;
@@@ -1305,4 -1261,6 +1305,6 @@@ extern struct startup_info *startup_inf
  /* builtin/merge.c */
  int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
  
+ int sane_execvp(const char *file, char *const argv[]);
  #endif /* CACHE_H */
diff --combined run-command.c
index 1db8abf9843516576f30f8105bbfdd66487db6e1,805d41f93d66b9147b837fb51f365e85f8f44fda..9c5a564ece033e8584be14c640f6a9995ecd1cff
@@@ -1,66 -1,8 +1,66 @@@
  #include "cache.h"
  #include "run-command.h"
  #include "exec_cmd.h"
 +#include "sigchain.h"
  #include "argv-array.h"
  
 +struct child_to_clean {
 +      pid_t pid;
 +      struct child_to_clean *next;
 +};
 +static struct child_to_clean *children_to_clean;
 +static int installed_child_cleanup_handler;
 +
 +static void cleanup_children(int sig)
 +{
 +      while (children_to_clean) {
 +              struct child_to_clean *p = children_to_clean;
 +              children_to_clean = p->next;
 +              kill(p->pid, sig);
 +              free(p);
 +      }
 +}
 +
 +static void cleanup_children_on_signal(int sig)
 +{
 +      cleanup_children(sig);
 +      sigchain_pop(sig);
 +      raise(sig);
 +}
 +
 +static void cleanup_children_on_exit(void)
 +{
 +      cleanup_children(SIGTERM);
 +}
 +
 +static void mark_child_for_cleanup(pid_t pid)
 +{
 +      struct child_to_clean *p = xmalloc(sizeof(*p));
 +      p->pid = pid;
 +      p->next = children_to_clean;
 +      children_to_clean = p;
 +
 +      if (!installed_child_cleanup_handler) {
 +              atexit(cleanup_children_on_exit);
 +              sigchain_push_common(cleanup_children_on_signal);
 +              installed_child_cleanup_handler = 1;
 +      }
 +}
 +
 +static void clear_child_for_cleanup(pid_t pid)
 +{
 +      struct child_to_clean **last, *p;
 +
 +      last = &children_to_clean;
 +      for (p = children_to_clean; p; p = p->next) {
 +              if (p->pid == pid) {
 +                      *last = p->next;
 +                      free(p);
 +                      return;
 +              }
 +      }
 +}
 +
  static inline void close_pair(int fd[2])
  {
        close(fd[0]);
@@@ -76,6 -18,68 +76,68 @@@ static inline void dup_devnull(int to
  }
  #endif
  
+ static char *locate_in_PATH(const char *file)
+ {
+       const char *p = getenv("PATH");
+       struct strbuf buf = STRBUF_INIT;
+       if (!p || !*p)
+               return NULL;
+       while (1) {
+               const char *end = strchrnul(p, ':');
+               strbuf_reset(&buf);
+               /* POSIX specifies an empty entry as the current directory. */
+               if (end != p) {
+                       strbuf_add(&buf, p, end - p);
+                       strbuf_addch(&buf, '/');
+               }
+               strbuf_addstr(&buf, file);
+               if (!access(buf.buf, F_OK))
+                       return strbuf_detach(&buf, NULL);
+               if (!*end)
+                       break;
+               p = end + 1;
+       }
+       strbuf_release(&buf);
+       return NULL;
+ }
+ static int exists_in_PATH(const char *file)
+ {
+       char *r = locate_in_PATH(file);
+       free(r);
+       return r != NULL;
+ }
+ int sane_execvp(const char *file, char * const argv[])
+ {
+       if (!execvp(file, argv))
+               return 0; /* cannot happen ;-) */
+       /*
+        * When a command can't be found because one of the directories
+        * listed in $PATH is unsearchable, execvp reports EACCES, but
+        * careful usability testing (read: analysis of occasional bug
+        * reports) reveals that "No such file or directory" is more
+        * intuitive.
+        *
+        * We avoid commands with "/", because execvp will not do $PATH
+        * lookups in that case.
+        *
+        * The reassignment of EACCES to errno looks like a no-op below,
+        * but we need to protect against exists_in_PATH overwriting errno.
+        */
+       if (errno == EACCES && !strchr(file, '/'))
+               errno = exists_in_PATH(file) ? EACCES : ENOENT;
+       return -1;
+ }
  static const char **prepare_shell_cmd(const char **argv)
  {
        int argc, nargc = 0;
@@@ -114,7 -118,7 +176,7 @@@ static int execv_shell_cmd(const char *
  {
        const char **nargv = prepare_shell_cmd(argv);
        trace_argv_printf(nargv, "trace: exec:");
-       execvp(nargv[0], (char **)nargv);
+       sane_execvp(nargv[0], (char **)nargv);
        free(nargv);
        return -1;
  }
@@@ -188,9 -192,6 +250,9 @@@ static int wait_or_whine(pid_t pid, con
        } else {
                error("waitpid is confused (%s)", argv0);
        }
 +
 +      clear_child_for_cleanup(pid);
 +
        errno = failed_errno;
        return code;
  }
@@@ -339,7 -340,7 +401,7 @@@ fail_pipe
                } else if (cmd->use_shell) {
                        execv_shell_cmd(cmd->argv);
                } else {
-                       execvp(cmd->argv[0], (char *const*) cmd->argv);
+                       sane_execvp(cmd->argv[0], (char *const*) cmd->argv);
                }
                if (errno == ENOENT) {
                        if (!cmd->silent_exec_failure)
        if (cmd->pid < 0)
                error("cannot fork() for %s: %s", cmd->argv[0],
                        strerror(failed_errno = errno));
 +      else if (cmd->clean_on_exit)
 +              mark_child_for_cleanup(cmd->pid);
  
        /*
         * Wait for child's execvp. If the execvp succeeds (or if fork()
                cmd->pid = -1;
        }
        close(notify_pipe[0]);
 +
  }
  #else
  {
        failed_errno = errno;
        if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
                error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
 +      if (cmd->clean_on_exit && cmd->pid >= 0)
 +              mark_child_for_cleanup(cmd->pid);
  
        if (cmd->env)
                free_environ(env);
@@@ -497,7 -493,6 +559,7 @@@ static void prepare_run_command_v_opt(s
        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;
  }
  
  int run_command_v_opt(const char **argv, int opt)
@@@ -607,8 -602,6 +669,8 @@@ int start_async(struct async *async
                exit(!!async->proc(proc_in, proc_out, async->data));
        }
  
 +      mark_child_for_cleanup(async->pid);
 +
        if (need_in)
                close(fdin[0]);
        else if (async->in)