git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] report which $PATH entry had trouble running execvp(3)

From
Junio C Hamano <gitster@pobox.com>
Date
Apr 20, 2011, 04:01 UTC
Message-ID
<7vaafl371q.fsf_-_@alter.siamese.dyndns.org>
In-Reply-To
<7vipub6r3s.fsf@alter.siamese.dyndns.org>

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@pobox.com>
---
  Junio C Hamano <gitster@pobox.com> 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
Previous: Junio C HamanoNext: Jeff King
Message 16 of 20 in “[REGRESSION] git-wrapper to run-commands codepath regression”
  1. Junio C HamanoApr 18, 2011
  2. Jeff KingApr 18, 2011
  3. Jeff KingApr 18, 2011
  4. Junio C HamanoApr 18, 2011
  5. Jeff KingApr 18, 2011
  6. Junio C HamanoApr 18, 2011
  7. Andreas SchwabApr 18, 2011
  8. Junio C HamanoApr 18, 2011
  9. Jonathan NiederApr 18, 2011
  10. run-command: write full error message in die_childJonathan Nieder, Apr 19, 2011
  11. Johannes SixtApr 20, 2011
  12. 0/2 Re: run-command: write full error message in die_childJonathan Nieder, Apr 20, 2011
  13. 1/2 tests: check error message from run_commandJonathan Nieder, Apr 20, 2011
  14. 2/2 run-command: handle short writes and EINTR in die_childJonathan Nieder, Apr 20, 2011
  15. Junio C HamanoApr 19, 2011
  16. report which $PATH entry had trouble running execvp(3)Junio C Hamano, Apr 20, 2011
  17. Jeff KingApr 20, 2011
  18. Junio C HamanoApr 21, 2011
  19. Johannes SixtApr 20, 2011
  20. Jonathan NiederApr 20, 2011

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.