Skip to content

Commit 8c1ba21

Browse files
committed
Merge branch 'jk/run-command-eacces' into maint
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
2 parents 731673b + 38f865c commit 8c1ba21

File tree

6 files changed

+86
-7
lines changed

6 files changed

+86
-7
lines changed

cache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,4 +1305,6 @@ extern struct startup_info *startup_info;
13051305
/* builtin/merge.c */
13061306
int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
13071307

1308+
int sane_execvp(const char *file, char *const argv[]);
1309+
13081310
#endif /* CACHE_H */

compat/mingw.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ static void mingw_execve(const char *cmd, char *const *argv, char *const *env)
10031003
}
10041004
}
10051005

1006-
void mingw_execvp(const char *cmd, char *const *argv)
1006+
int mingw_execvp(const char *cmd, char *const *argv)
10071007
{
10081008
char **path = get_path_split();
10091009
char *prog = path_lookup(cmd, path, 0);
@@ -1015,11 +1015,13 @@ void mingw_execvp(const char *cmd, char *const *argv)
10151015
errno = ENOENT;
10161016

10171017
free_path_split(path);
1018+
return -1;
10181019
}
10191020

1020-
void mingw_execv(const char *cmd, char *const *argv)
1021+
int mingw_execv(const char *cmd, char *const *argv)
10211022
{
10221023
mingw_execve(cmd, argv, environ);
1024+
return -1;
10231025
}
10241026

10251027
int mingw_kill(pid_t pid, int sig)

compat/mingw.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,9 @@ int mingw_utime(const char *file_name, const struct utimbuf *times);
274274
pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
275275
const char *dir,
276276
int fhin, int fhout, int fherr);
277-
void mingw_execvp(const char *cmd, char *const *argv);
277+
int mingw_execvp(const char *cmd, char *const *argv);
278278
#define execvp mingw_execvp
279-
void mingw_execv(const char *cmd, char *const *argv);
279+
int mingw_execv(const char *cmd, char *const *argv);
280280
#define execv mingw_execv
281281

282282
static inline unsigned int git_ntohl(unsigned int x)

exec_cmd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ int execv_git_cmd(const char **argv) {
134134
trace_argv_printf(nargv, "trace: exec:");
135135

136136
/* execvp() can only ever return if it fails */
137-
execvp("git", (char **)nargv);
137+
sane_execvp("git", (char **)nargv);
138138

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

run-command.c

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,68 @@ static inline void dup_devnull(int to)
7676
}
7777
#endif
7878

79+
static char *locate_in_PATH(const char *file)
80+
{
81+
const char *p = getenv("PATH");
82+
struct strbuf buf = STRBUF_INIT;
83+
84+
if (!p || !*p)
85+
return NULL;
86+
87+
while (1) {
88+
const char *end = strchrnul(p, ':');
89+
90+
strbuf_reset(&buf);
91+
92+
/* POSIX specifies an empty entry as the current directory. */
93+
if (end != p) {
94+
strbuf_add(&buf, p, end - p);
95+
strbuf_addch(&buf, '/');
96+
}
97+
strbuf_addstr(&buf, file);
98+
99+
if (!access(buf.buf, F_OK))
100+
return strbuf_detach(&buf, NULL);
101+
102+
if (!*end)
103+
break;
104+
p = end + 1;
105+
}
106+
107+
strbuf_release(&buf);
108+
return NULL;
109+
}
110+
111+
static int exists_in_PATH(const char *file)
112+
{
113+
char *r = locate_in_PATH(file);
114+
free(r);
115+
return r != NULL;
116+
}
117+
118+
int sane_execvp(const char *file, char * const argv[])
119+
{
120+
if (!execvp(file, argv))
121+
return 0; /* cannot happen ;-) */
122+
123+
/*
124+
* When a command can't be found because one of the directories
125+
* listed in $PATH is unsearchable, execvp reports EACCES, but
126+
* careful usability testing (read: analysis of occasional bug
127+
* reports) reveals that "No such file or directory" is more
128+
* intuitive.
129+
*
130+
* We avoid commands with "/", because execvp will not do $PATH
131+
* lookups in that case.
132+
*
133+
* The reassignment of EACCES to errno looks like a no-op below,
134+
* but we need to protect against exists_in_PATH overwriting errno.
135+
*/
136+
if (errno == EACCES && !strchr(file, '/'))
137+
errno = exists_in_PATH(file) ? EACCES : ENOENT;
138+
return -1;
139+
}
140+
79141
static const char **prepare_shell_cmd(const char **argv)
80142
{
81143
int argc, nargc = 0;
@@ -114,7 +176,7 @@ static int execv_shell_cmd(const char **argv)
114176
{
115177
const char **nargv = prepare_shell_cmd(argv);
116178
trace_argv_printf(nargv, "trace: exec:");
117-
execvp(nargv[0], (char **)nargv);
179+
sane_execvp(nargv[0], (char **)nargv);
118180
free(nargv);
119181
return -1;
120182
}
@@ -339,7 +401,7 @@ int start_command(struct child_process *cmd)
339401
} else if (cmd->use_shell) {
340402
execv_shell_cmd(cmd->argv);
341403
} else {
342-
execvp(cmd->argv[0], (char *const*) cmd->argv);
404+
sane_execvp(cmd->argv[0], (char *const*) cmd->argv);
343405
}
344406
if (errno == ENOENT) {
345407
if (!cmd->silent_exec_failure)

t/t0061-run-command.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,17 @@ test_expect_success POSIXPERM 'run_command reports EACCES' '
3434
grep "fatal: cannot exec.*hello.sh" err
3535
'
3636

37+
test_expect_success POSIXPERM 'unreadable directory in PATH' '
38+
mkdir local-command &&
39+
test_when_finished "chmod u+rwx local-command && rm -fr local-command" &&
40+
git config alias.nitfol "!echo frotz" &&
41+
chmod a-rx local-command &&
42+
(
43+
PATH=./local-command:$PATH &&
44+
git nitfol >actual
45+
) &&
46+
echo frotz >expect &&
47+
test_cmp expect actual
48+
'
49+
3750
test_done

0 commit comments

Comments
 (0)