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

[PATCH v5 08/11] run-command: eliminate calls to error handling functions in child

From
BWBrandon Williams <bmwill@google.com>
Date
Apr 18, 2017, 23:18 UTC
Message-ID
<20170418231805.61835-9-bmwill@google.com>
In-Reply-To
<20170418231805.61835-1-bmwill@google.com>

All of our standard error handling paths have the potential to call malloc or take stdio locks; so we must avoid them inside the forked child.

Instead, the child only writes an 8 byte struct atomically to the parent through the notification pipe to propagate an error. All user-visible error reporting happens from the parent; even avoiding functions like atexit(3) and exit(3).

Helped-by: Eric Wong <e@80x24.org>
Signed-off-by: Brandon Williams <bmwill@google.com>
---
 run-command.c | 121 ++++++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 89 insertions(+), 32 deletions(-)
diff --git a/run-command.c b/run-command.c
index b3a35dd82..1f15714b1 100644
--- a/run-command.c
+++ b/run-command.c
@@ -211,14 +211,82 @@ static const char **prepare_shell_cmd(struct argv_array *out, const char **argv)
 #ifndef GIT_WINDOWS_NATIVE
 static int child_notifier = -1;
 
-static void notify_parent(void)
+enum child_errcode {
+	CHILD_ERR_CHDIR,
+	CHILD_ERR_ENOENT,
+	CHILD_ERR_SILENT,
+	CHILD_ERR_ERRNO
+};
+
+struct child_err {
+	enum child_errcode err;
+	int syserr; /* errno */
+};
+
+static void child_die(enum child_errcode err)
 {
-	/*
-	 * execvp failed.  If possible, we'd like to let start_command
-	 * know, so failures like ENOENT can be handled right away; but
-	 * otherwise, finish_command will still report the error.
-	 */
-	xwrite(child_notifier, "", 1);
+	struct child_err buf;
+
+	buf.err = err;
+	buf.syserr = errno;
+
+	/* write(2) on buf smaller than PIPE_BUF (min 512) is atomic: */
+	xwrite(child_notifier, &buf, sizeof(buf));
+	_exit(1);
+}
+
+/*
+ * parent will make it look like the child spewed a fatal error and died
+ * this is needed to prevent changes to t0061.
+ */
+static void fake_fatal(const char *err, va_list params)
+{
+	vreportf("fatal: ", err, params);
+}
+
+static void child_error_fn(const char *err, va_list params)
+{
+	const char msg[] = "error() should not be called in child\n";
+	xwrite(2, msg, sizeof(msg) - 1);
+}
+
+static void child_warn_fn(const char *err, va_list params)
+{
+	const char msg[] = "warn() should not be called in child\n";
+	xwrite(2, msg, sizeof(msg) - 1);
+}
+
+static void NORETURN child_die_fn(const char *err, va_list params)
+{
+	const char msg[] = "die() should not be called in child\n";
+	xwrite(2, msg, sizeof(msg) - 1);
+	_exit(2);
+}
+
+/* this runs in the parent process */
+static void child_err_spew(struct child_process *cmd, struct child_err *cerr)
+{
+	static void (*old_errfn)(const char *err, va_list params);
+
+	old_errfn = get_error_routine();
+	set_error_routine(fake_fatal);
+	errno = cerr->syserr;
+
+	switch (cerr->err) {
+	case CHILD_ERR_CHDIR:
+		error_errno("exec '%s': cd to '%s' failed",
+			    cmd->argv[0], cmd->dir);
+		break;
+	case CHILD_ERR_ENOENT:
+		error_errno("cannot run %s", cmd->argv[0]);
+		break;
+	case CHILD_ERR_SILENT:
+		break;
+	case CHILD_ERR_ERRNO:
+		error_errno("cannot exec '%s'", cmd->argv[0]);
+		break;
+	}
+	set_error_routine(old_errfn);
 }
 
 static void prepare_cmd(struct argv_array *out, const struct child_process *cmd)
@@ -341,13 +409,6 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal)
 		code += 128;
 	} else if (WIFEXITED(status)) {
 		code = WEXITSTATUS(status);
-		/*
-		 * Convert special exit code when execvp failed.
-		 */
-		if (code == 127) {
-			code = -1;
-			failed_errno = ENOENT;
-		}
 	} else {
 		error("waitpid is confused (%s)", argv0);
 	}
@@ -435,6 +496,7 @@ int start_command(struct child_process *cmd)
 	int null_fd = -1;
 	char **childenv;
 	struct argv_array argv = ARGV_ARRAY_INIT;
+	struct child_err cerr;
 
 	if (pipe(notify_pipe))
 		notify_pipe[0] = notify_pipe[1] = -1;
@@ -453,20 +515,16 @@ int start_command(struct child_process *cmd)
 	failed_errno = errno;
 	if (!cmd->pid) {
 		/*
-		 * Redirect the channel to write syscall error messages to
-		 * before redirecting the process's stderr so that all die()
-		 * in subsequent call paths use the parent's stderr.
+		 * Ensure the default die/error/warn routines do not get
+		 * called, they can take stdio locks and malloc.
 		 */
-		if (cmd->no_stderr || need_err) {
-			int child_err = dup(2);
-			set_cloexec(child_err);
-			set_error_handle(fdopen(child_err, "w"));
-		}
+		set_die_routine(child_die_fn);
+		set_error_routine(child_error_fn);
+		set_warn_routine(child_warn_fn);
 
 		close(notify_pipe[0]);
 		set_cloexec(notify_pipe[1]);
 		child_notifier = notify_pipe[1];
-		atexit(notify_parent);
 
 		if (cmd->no_stdin)
 			dup2(null_fd, 0);
@@ -501,8 +559,7 @@ int start_command(struct child_process *cmd)
 		}
 
 		if (cmd->dir && chdir(cmd->dir))
-			die_errno("exec '%s': cd to '%s' failed", cmd->argv[0],
-			    cmd->dir);
+			child_die(CHILD_ERR_CHDIR);
 
 		/*
 		 * Attempt to exec using the command and arguments starting at
@@ -517,12 +574,11 @@ int start_command(struct child_process *cmd)
 			       (char *const *) childenv);
 
 		if (errno == ENOENT) {
-			if (!cmd->silent_exec_failure)
-				error("cannot run %s: %s", cmd->argv[0],
-					strerror(ENOENT));
-			exit(127);
+			if (cmd->silent_exec_failure)
+				child_die(CHILD_ERR_SILENT);
+			child_die(CHILD_ERR_ENOENT);
 		} else {
-			die_errno("cannot exec '%s'", cmd->argv[0]);
+			child_die(CHILD_ERR_ERRNO);
 		}
 	}
 	if (cmd->pid < 0)
@@ -533,17 +589,18 @@ int start_command(struct child_process *cmd)
 	/*
 	 * Wait for child's exec. If the exec succeeds (or if fork()
 	 * failed), EOF is seen immediately by the parent. Otherwise, the
-	 * child process sends a single byte.
+	 * child process sends a child_err struct.
 	 * Note that use of this infrastructure is completely advisory,
 	 * therefore, we keep error checks minimal.
 	 */
 	close(notify_pipe[1]);
-	if (read(notify_pipe[0], &notify_pipe[1], 1) == 1) {
+	if (xread(notify_pipe[0], &cerr, sizeof(cerr)) == sizeof(cerr)) {
 		/*
 		 * At this point we know that fork() succeeded, but exec()
 		 * failed. Errors have been reported to our stderr.
 		 */
 		wait_or_whine(cmd->pid, cmd->argv[0], 0);
+		child_err_spew(cmd, &cerr);
 		failed_errno = errno;
 		cmd->pid = -1;
 	}
-- 
2.12.2.816.g2cccc81164-goog
Previous: Brandon WilliamsNext: Brandon Williams
Message 92 of 140 in “forking and threading”
  1. 0/5 forking and threadingBrandon Williams, Apr 10, 2017
  2. 1/5 run-command: convert sane_execvp to sane_execvpeBrandon Williams, Apr 10, 2017
  3. Brandon WilliamsApr 12, 2017
  4. 2/5 run-command: prepare argv before forkingBrandon Williams, Apr 10, 2017
  5. 5/5 run-command: add note about forking and threadingBrandon Williams, Apr 10, 2017
  6. Jonathan NiederApr 11, 2017
  7. Eric WongApr 11, 2017
  8. Jonathan NiederApr 11, 2017
  9. Brandon WilliamsApr 11, 2017
  10. Eric WongApr 11, 2017
  11. 4/5 run-command: prepare child environment before forkingBrandon Williams, Apr 10, 2017
  12. Jonathan NiederApr 11, 2017
  13. Brandon WilliamsApr 11, 2017
  14. Jonathan NiederApr 11, 2017
  15. 3/5 run-command: allocate child_err before forkingBrandon Williams, Apr 10, 2017
  16. 6/5 run-command: avoid potential dangers in forked childEric Wong, Apr 11, 2017
  17. Brandon WilliamsApr 11, 2017
  18. Eric WongApr 11, 2017
  19. Brandon WilliamsApr 11, 2017
  20. Jonathan NiederApr 11, 2017
  21. Brandon WilliamsApr 11, 2017
  22. 0/6 forking and threadingBrandon Williams, Apr 13, 2017
  23. 4/6 run-command: don't die in child when duping /dev/nullBrandon Williams, Apr 13, 2017
  24. Eric WongApr 13, 2017
  25. Brandon WilliamsApr 13, 2017
  26. 1/6 t5550: use write_script to generate post-update hookBrandon Williams, Apr 13, 2017
  27. Jonathan NiederApr 13, 2017
  28. Eric WongApr 13, 2017
  29. Brandon WilliamsApr 13, 2017
  30. Eric WongApr 13, 2017
  31. 5/6 run-command: eliminate calls to error handling functions in childBrandon Williams, Apr 13, 2017
  32. 2/6 run-command: prepare command before forkingBrandon Williams, Apr 13, 2017
  33. Jonathan NiederApr 13, 2017
  34. Brandon WilliamsApr 13, 2017
  35. 6/6 run-command: add note about forking and threadingBrandon Williams, Apr 13, 2017
  36. 3/6 run-command: prepare child environment before forkingBrandon Williams, Apr 13, 2017
  37. Jonathan NiederApr 13, 2017
  38. Brandon WilliamsApr 13, 2017
  39. 7/6 run-command: block signals between fork and execveEric Wong, Apr 13, 2017
  40. Brandon WilliamsApr 13, 2017
  41. Brandon WilliamsApr 14, 2017
  42. Eric WongApr 14, 2017
  43. Eric WongApr 14, 2017
  44. 00/10 forking and threadingBrandon Williams, Apr 14, 2017
  45. 01/10 t5550: use write_script to generate post-update hookBrandon Williams, Apr 14, 2017
  46. 02/10 t0061: run_command executes scripts without a #! lineBrandon Williams, Apr 14, 2017
  47. 03/10 run-command: prepare command before forkingBrandon Williams, Apr 14, 2017
  48. 07/10 run-command: eliminate calls to error handling functions in childBrandon Williams, Apr 14, 2017
  49. Eric WongApr 14, 2017
  50. Brandon WilliamsApr 14, 2017
  51. 05/10 run-command: prepare child environment before forkingBrandon Williams, Apr 14, 2017
  52. 09/10 run-command: add note about forking and threadingBrandon Williams, Apr 14, 2017
  53. 08/10 run-command: handle dup2 and close errors in childBrandon Williams, Apr 14, 2017
  54. 10/10 run-command: block signals between fork and execveBrandon Williams, Apr 14, 2017
  55. Brandon WilliamsApr 14, 2017
  56. Eric WongApr 14, 2017
  57. 06/10 run-command: don't die in child when duping /dev/nullBrandon Williams, Apr 14, 2017
  58. Eric WongApr 14, 2017
  59. Brandon WilliamsApr 14, 2017
  60. 04/10 run-command: use the async-signal-safe execv instead of execvpBrandon Williams, Apr 14, 2017
  61. 00/10 forking and threadingBrandon Williams, Apr 17, 2017
  62. 01/10 t5550: use write_script to generate post-update hookBrandon Williams, Apr 17, 2017
  63. 03/10 run-command: prepare command before forkingBrandon Williams, Apr 17, 2017
  64. 02/10 t0061: run_command executes scripts without a #! lineBrandon Williams, Apr 17, 2017
  65. 04/10 run-command: use the async-signal-safe execv instead of execvpBrandon Williams, Apr 17, 2017
  66. 07/10 run-command: eliminate calls to error handling functions in childBrandon Williams, Apr 17, 2017
  67. 06/10 run-command: don't die in child when duping /dev/nullBrandon Williams, Apr 17, 2017
  68. 10/10 run-command: block signals between fork and execveBrandon Williams, Apr 17, 2017
  69. 09/10 run-command: add note about forking and threadingBrandon Williams, Apr 17, 2017
  70. 05/10 run-command: prepare child environment before forkingBrandon Williams, Apr 17, 2017
  71. Eric WongApr 18, 2017
  72. Brandon WilliamsApr 18, 2017
  73. 08/10 run-command: handle dup2 and close errors in childBrandon Williams, Apr 17, 2017
  74. 00/11 forking and threadingBrandon Williams, Apr 18, 2017
  75. 01/11 t5550: use write_script to generate post-update hookBrandon Williams, Apr 18, 2017
  76. 03/11 run-command: prepare command before forkingBrandon Williams, Apr 18, 2017
  77. 02/11 t0061: run_command executes scripts without a #! lineBrandon Williams, Apr 18, 2017
  78. Johannes SixtApr 19, 2017
  79. Johannes SixtApr 19, 2017
  80. Brandon WilliamsApr 19, 2017
  81. Johannes SixtApr 19, 2017
  82. Johannes SchindelinApr 20, 2017
  83. Brandon WilliamsApr 20, 2017
  84. Johannes SchindelinApr 20, 2017
  85. Brandon WilliamsApr 20, 2017
  86. 05/11 string-list: add string_list_remove functionBrandon Williams, Apr 18, 2017
  87. Stefan BellerApr 18, 2017
  88. Brandon WilliamsApr 18, 2017
  89. Stefan BellerApr 18, 2017
  90. 07/11 run-command: don't die in child when duping /dev/nullBrandon Williams, Apr 18, 2017
  91. 04/11 run-command: use the async-signal-safe execv instead of execvpBrandon Williams, Apr 18, 2017
  92. 08/11 run-command: eliminate calls to error handling functions in childBrandon Williams, Apr 18, 2017
  93. 10/11 run-command: add note about forking and threadingBrandon Williams, Apr 18, 2017
  94. 09/11 run-command: handle dup2 and close errors in childBrandon Williams, Apr 18, 2017
  95. 11/11 run-command: block signals between fork and execveBrandon Williams, Apr 18, 2017
  96. Johannes SixtApr 19, 2017
  97. Eric WongApr 19, 2017
  98. Brandon WilliamsApr 19, 2017
  99. 06/11 run-command: prepare child environment before forkingBrandon Williams, Apr 18, 2017
  100. 00/11 forking and threadingBrandon Williams, Apr 19, 2017
  101. 01/11 t5550: use write_script to generate post-update hookBrandon Williams, Apr 19, 2017
  102. 02/11 t0061: run_command executes scripts without a #! lineBrandon Williams, Apr 19, 2017
  103. Johannes SchindelinApr 20, 2017
  104. Brandon WilliamsApr 20, 2017
  105. 04/11 run-command: use the async-signal-safe execv instead of execvpBrandon Williams, Apr 19, 2017
  106. Junio C HamanoMay 17, 2017
  107. Jeff KingMay 17, 2017
  108. Jeff KingMay 17, 2017
  109. Junio C HamanoMay 17, 2017
  110. Brandon WilliamsMay 17, 2017
  111. 05/11 string-list: add string_list_remove functionBrandon Williams, Apr 19, 2017
  112. 08/11 run-command: eliminate calls to error handling functions in childBrandon Williams, Apr 19, 2017
  113. 06/11 run-command: prepare child environment before forkingBrandon Williams, Apr 19, 2017
  114. 03/11 run-command: prepare command before forkingBrandon Williams, Apr 19, 2017
  115. 07/11 run-command: don't die in child when duping /dev/nullBrandon Williams, Apr 19, 2017
  116. 09/11 run-command: handle dup2 and close errors in childBrandon Williams, Apr 19, 2017
  117. 10/11 run-command: add note about forking and threadingBrandon Williams, Apr 19, 2017
  118. 11/11 run-command: block signals between fork and execveBrandon Williams, Apr 19, 2017
  119. Brandon WilliamsApr 24, 2017
  120. 12/11 run-command: don't try to execute directoriesBrandon Williams, Apr 24, 2017
  121. Jonathan NiederApr 25, 2017
  122. Junio C HamanoApr 25, 2017
  123. Jonathan NiederApr 25, 2017
  124. Jeff KingApr 25, 2017
  125. Junio C HamanoApr 25, 2017
  126. Jonathan NiederApr 25, 2017
  127. 1/2 exec_cmd: expose is_executable functionBrandon Williams, Apr 25, 2017
  128. 2/2 run-command: don't try to execute directoriesBrandon Williams, Apr 25, 2017
  129. Jonathan NiederApr 25, 2017
  130. Brandon WilliamsApr 25, 2017
  131. Jonathan NiederApr 25, 2017
  132. Johannes SixtApr 25, 2017
  133. Brandon WilliamsApr 25, 2017
  134. 1/2 run-command: expose is_executable functionBrandon Williams, Apr 25, 2017
  135. 2/2 run-command: restrict PATH search to executable filesBrandon Williams, Apr 25, 2017
  136. Jonathan NiederApr 25, 2017
  137. Junio C HamanoApr 26, 2017
  138. 2/2 run-command: restrict PATH search to executable filesBrandon Williams, Apr 26, 2017
  139. Junio C HamanoApr 27, 2017
  140. Jonathan NiederApr 25, 2017

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.