Skip to content

Commit

Permalink
Merge branch 'jk/run-command-eacces'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gitster committed Apr 20, 2012
2 parents 27da1cf + 38f865c commit bd6f71d
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 7 deletions.
2 changes: 2 additions & 0 deletions cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -1305,4 +1305,6 @@ extern struct startup_info *startup_info;
/* 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 */
6 changes: 4 additions & 2 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ static void mingw_execve(const char *cmd, char *const *argv, char *const *env)
}
}

void mingw_execvp(const char *cmd, char *const *argv)
int mingw_execvp(const char *cmd, char *const *argv)
{
char **path = get_path_split();
char *prog = path_lookup(cmd, path, 0);
Expand All @@ -1015,11 +1015,13 @@ void mingw_execvp(const char *cmd, char *const *argv)
errno = ENOENT;

free_path_split(path);
return -1;
}

void mingw_execv(const char *cmd, char *const *argv)
int mingw_execv(const char *cmd, char *const *argv)
{
mingw_execve(cmd, argv, environ);
return -1;
}

int mingw_kill(pid_t pid, int sig)
Expand Down
4 changes: 2 additions & 2 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,9 @@ int mingw_utime(const char *file_name, const struct utimbuf *times);
pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
const char *dir,
int fhin, int fhout, int fherr);
void mingw_execvp(const char *cmd, char *const *argv);
int mingw_execvp(const char *cmd, char *const *argv);
#define execvp mingw_execvp
void mingw_execv(const char *cmd, char *const *argv);
int mingw_execv(const char *cmd, char *const *argv);
#define execv mingw_execv

static inline unsigned int git_ntohl(unsigned int x)
Expand Down
2 changes: 1 addition & 1 deletion exec_cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ int execv_git_cmd(const char **argv) {
trace_argv_printf(nargv, "trace: exec:");

/* execvp() can only ever return if it fails */
execvp("git", (char **)nargv);
sane_execvp("git", (char **)nargv);

trace_printf("trace: exec failed: %s\n", strerror(errno));

Expand Down
66 changes: 64 additions & 2 deletions run-command.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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;
Expand Down Expand Up @@ -118,7 +180,7 @@ static int execv_shell_cmd(const char **argv)
{
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;
}
Expand Down Expand Up @@ -343,7 +405,7 @@ int start_command(struct child_process *cmd)
} 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)
Expand Down
13 changes: 13 additions & 0 deletions t/t0061-run-command.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,17 @@ test_expect_success POSIXPERM 'run_command reports EACCES' '
grep "fatal: cannot exec.*hello.sh" err
'

test_expect_success POSIXPERM 'unreadable directory in PATH' '
mkdir local-command &&
test_when_finished "chmod u+rwx local-command && rm -fr local-command" &&
git config alias.nitfol "!echo frotz" &&
chmod a-rx local-command &&
(
PATH=./local-command:$PATH &&
git nitfol >actual
) &&
echo frotz >expect &&
test_cmp expect actual
'

test_done

0 comments on commit bd6f71d

Please sign in to comment.