You can add your own custom subcommand 'frotz' to the system by adding 'git-frotz' in a directory somewhere in your $PATH environment variable. When you ask "git frotz" from the command line, "git-frotz" is run via execvp(3). Three plausible scenarios that the execvp(3) would fail for us are: * The first 'git-frotz' found in a directory on $PATH was not a proper executable binary, and we got "Exec format error" (ENOEXEC); * The only 'git-frotz' found in the directories listed on $PATH were not marked with executable bit, and we got "Permission denied" (EACCES); or * No 'git-frotz' was found in the directories listed on $PATH, but one of the directories were unreadable, and we got EACCES. The first one is easy to understand and to rectify. Most likely, the user made a typo, either on the command line, or when creating the custom subcommand. However, the latter two cases are harder to notice, as we do not report 'git-frotz' in which directory we had trouble with. We could do better if we implemented the command search behaviour of execvp(3) ourselves. Add an internal function sane_execvp() that emulates execvp(3), skipping ENOENT and EACCES while remembering a path that resulted in EACCES while trying later directories on $PATH. When failing the request at the end, report the path that we had trouble with, and use it when reporting the error. Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> --- Junio C Hamano <gitster@xxxxxxxxx> writes: >> The following is a tangent that was brought up at $work. > ... > We would need to emulate what execvp() does ourselves (i.e. split $PATH, > prefix each component and try execv(), ignoring ENOENT or EACCES while > trying next component in $PATH), plus note the first path that got EACCES > so that we can report which script (including its leading directories) had > trouble executing. Perhaps a simple enough task for beginners. run-command.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 47 insertions(+), 1 deletions(-) diff --git a/run-command.c b/run-command.c index f91e446..4c95f50 100644 --- a/run-command.c +++ b/run-command.c @@ -135,6 +135,52 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) return code; } +static const char *sane_execvp(const char *arg0, const char **argv) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf failed_path = STRBUF_INIT; + char *path = getenv("PATH"); + char *next; + + if (!path) + path = ""; + + for (;;) { + next = strchrnul(path, ':'); + if (path < next) + strbuf_add(&sb, path, next - path); + else + strbuf_addch(&sb, '.'); + if (sb.len && sb.buf[sb.len - 1] != '/') + strbuf_addch(&sb, '/'); + strbuf_addstr(&sb, arg0); + execv(sb.buf, (char * const*) argv); + + /* + * execvp() skips EACCES and ENOENT and goes on to try + * the next entry in the $PATH, but sets errno to EACCES + * when it fails at the end. + */ + if (errno == EACCES && !failed_path.len) + strbuf_add(&failed_path, sb.buf, sb.len); + if (errno != ENOENT) { + strbuf_release(&failed_path); + return strbuf_detach(&sb, NULL); + } + strbuf_release(&sb); + if (!*next) + break; + path = next + 1; + } + if (failed_path.len) { + errno = EACCES; + return strbuf_detach(&failed_path, NULL); + } + strbuf_release(&sb); + strbuf_release(&failed_path); + return arg0; +} + int start_command(struct child_process *cmd) { int need_in, need_out, need_err; @@ -278,7 +324,7 @@ fail_pipe: } else if (cmd->use_shell) { execv_shell_cmd(cmd->argv); } else { - execvp(cmd->argv[0], (char *const*) cmd->argv); + cmd->argv[0] = sane_execvp(cmd->argv[0], cmd->argv); } /* * Do not check for cmd->silent_exec_failure; the parent -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html