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

[PATCH 1/2] run-command: Add checks after execvp fails with EACCES

From
Frans Klaver <fransklaver@gmail.com>
Date
Dec 13, 2011, 15:08 UTC
Message-ID
<1323788917-4141-2-git-send-email-fransklaver@gmail.com>
In-Reply-To
<1323788917-4141-1-git-send-email-fransklaver@gmail.com>

execvp returns ENOENT if a command was not found after searching PATH. If path contains a directory that current user has insufficient privileges to, EACCES is returned. This may still mean the program wasn't found and may cause confusion to the user, especially when the file mentioned doesn't exist -- that is, the user would expect NOENT to be returned -- and the user was actually hoping for an alias to be executed.

To help users track down the core issue more easily, perform some checks on the path and file permissions involved. Output errors when paths or files don't have enough permissions.

Signed-off-by: Frans Klaver <fransklaver@gmail.com>
---
 run-command.c          |   79 ++++++++++++++++++++++++++++++++++++++++++++++++
 t/t0061-run-command.sh |   16 +++++++++-
 2 files changed, 94 insertions(+), 1 deletions(-)
diff --git a/run-command.c b/run-command.c
index 1c51043..3f136f4 100644
--- a/run-command.c
+++ b/run-command.c
@@ -2,6 +2,7 @@
 #include "run-command.h"
 #include "exec_cmd.h"
 #include "argv-array.h"
+#include "dir.h"
 
 static inline void close_pair(int fd[2])
 {
@@ -134,6 +135,80 @@ static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
 	return code;
 }
 
+#ifndef WIN32
+static int have_read_execute_permissions(const char *path)
+{
+	if (access(path, R_OK|X_OK) == 0)
+		return 1;
+
+	if (errno == EACCES)
+		return 0;
+
+	trace_printf("could not determine permissions for '%s': %s\n", path,
+				strerror(errno));
+	return 0;
+}
+
+static void diagnose_execvp_eacces(const char *cmd, const char **argv)
+{
+	/*
+	 * man 2 execve states that EACCES is returned for:
+	 * - Search permission is denied on a component of the path prefix
+	 *   of cmd or the name of a script interpreter
+	 * - The file or script interpreter is not a regular file
+	 * - Execute permission is denied for the file, script or ELF
+	 *   interpreter
+	 * - The file system is mounted noexec
+	 */
+	struct strbuf sb = STRBUF_INIT;
+	char *path;
+	char *next;
+
+	if (strchr(cmd, '/')) {
+		if (!have_read_execute_permissions(cmd))
+			error("no read/execute permissions on '%s'\n", cmd);
+		return;
+	}
+
+	path = getenv("PATH");
+	while (path) {
+		next = strchrnul(path, ':');
+		if (path < next)
+			strbuf_add(&sb, path, next - path);
+		else
+			strbuf_addch(&sb, '.');
+
+		if (!*next)
+			path = NULL;
+		else
+			path = next + 1;
+
+		if (!have_read_execute_permissions(sb.buf)) {
+			error("no read/execute permissions on '%s'\n", sb.buf);
+			strbuf_release(&sb);
+			continue;
+		}
+
+		if (sb.len && sb.buf[sb.len - 1] != '/')
+			strbuf_addch(&sb, '/');
+		strbuf_addstr(&sb, cmd);
+
+		if (file_exists(sb.buf)) {
+			if (!have_read_execute_permissions(sb.buf))
+				error("no read/execute permissions on '%s'\n",
+						sb.buf);
+			else
+				warning("file '%s' exists and permissions "
+				"seem OK.\nIf this is a script, see if you "
+				"have sufficient privileges to run the "
+				"interpreter", sb.buf);
+		}
+
+		strbuf_release(&sb);
+	}
+}
+#endif
+
 int start_command(struct child_process *cmd)
 {
 	int need_in, need_out, need_err;
@@ -285,6 +360,10 @@ fail_pipe:
 				error("cannot run %s: %s", cmd->argv[0],
 					strerror(ENOENT));
 			exit(127);
+		} else if (errno == EACCES) {
+			diagnose_execvp_eacces(cmd->argv[0], cmd->argv);
+			die("cannot exec '%s': %s", cmd->argv[0],
+				strerror(EACCES));
 		} else {
 			die_errno("cannot exec '%s'", cmd->argv[0]);
 		}
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
index 8d4938f..b39bd16 100755
--- a/t/t0061-run-command.sh
+++ b/t/t0061-run-command.sh
@@ -26,7 +26,7 @@ test_expect_success 'run_command can run a command' '
 	test_cmp empty err
 '
 
-test_expect_success POSIXPERM 'run_command reports EACCES' '
+test_expect_success POSIXPERM 'run_command reports EACCES, file permissions' '
 	cat hello-script >hello.sh &&
 	chmod -x hello.sh &&
 	test_must_fail test-run-command run-command ./hello.sh 2>err &&
@@ -34,4 +34,18 @@ test_expect_success POSIXPERM 'run_command reports EACCES' '
 	grep "fatal: cannot exec.*hello.sh" err
 '
 
+test_expect_success POSIXPERM 'run_command reports EACCES, search path permisions' '
+	mkdir -p inaccessible &&
+	PATH=$(pwd)/inaccessible:$PATH &&
+	export PATH &&
+
+	cat hello-script >inaccessible/hello.sh &&
+	chmod 400 inaccessible &&
+	test_must_fail test-run-command run-command hello.sh 2>err &&
+	chmod 755 inaccessible &&
+
+	grep "fatal: cannot exec.*hello.sh" err &&
+	grep "no read/execute permissions on" err
+'
+
 test_done
-- 
1.7.8
Previous: Frans KlaverNext: Junio C Hamano
Message 21 of 25 in “run-command.c: Accept EACCES as command not found”
  1. run-command.c: Accept EACCES as command not foundFrans Klaver, Nov 21, 2011
  2. Junio C HamanoNov 21, 2011
  3. Frans KlaverNov 21, 2011
  4. Junio C HamanoNov 21, 2011
  5. Frans KlaverNov 22, 2011
  6. Frans KlaverNov 23, 2011
  7. Nguyen Thai Ngoc DuyNov 23, 2011
  8. Frans KlaverNov 23, 2011
  9. Frans KlaverNov 23, 2011
  10. 0/2 run-command: Add EACCES diagnosticsFrans Klaver, Dec 6, 2011
  11. 1/2 run-command: Add checks after execvp fails with EACCESFrans Klaver, Dec 6, 2011
  12. Junio C HamanoDec 6, 2011
  13. Frans KlaverDec 7, 2011
  14. Frans KlaverDec 8, 2011
  15. Junio C HamanoDec 9, 2011
  16. Frans KlaverDec 9, 2011
  17. 2/2 run-command: Add interpreter permissions checkFrans Klaver, Dec 6, 2011
  18. Junio C HamanoDec 6, 2011
  19. Frans KlaverDec 7, 2011
  20. 0/2 run-command: Add eacces diagnosticsFrans Klaver, Dec 13, 2011
  21. 1/2 run-command: Add checks after execvp fails with EACCESFrans Klaver, Dec 13, 2011
  22. Junio C HamanoDec 13, 2011
  23. Frans KlaverDec 14, 2011
  24. Frans KlaverDec 14, 2011
  25. 2/2 run-command: Add interpreter permissions checkFrans Klaver, Dec 13, 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.