Thread (5 messages) 5 messages, 4 authors, 2016-06-15

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

DORMANTno replies

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:51:04
Subsystem: the rest · Maintainer: Linus Torvalds

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 <redacted>
---

  Junio C Hamano [off-list ref] 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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help