Skip to content

Commit bd6f71d

Browse files
committed
Merge branch 'jk/run-command-eacces'
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 27da1cf + 38f865c commit bd6f71d

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
@@ -80,6 +80,68 @@ static inline void dup_devnull(int to)
8080
}
8181
#endif
8282

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