run-command: eliminate calls to error handling functions in child
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> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Brandon Williams committed
Apr 19, 2017 at 16:13 UTC
79319b1949f0055bd42bac7fa398fca8c2f26116
1 file changed
+89
-32
run-command.c
+89
-32
@@ -211,14 +211,82 @@ static const char **prepare_shell_cmd(struct argv_array *out, const char **argv)
211
#ifndef GIT_WINDOWS_NATIVE
212
static int child_notifier = -1;
213
214
-static void notify_parent(void)
214
+enum child_errcode {
215
+ CHILD_ERR_CHDIR,
216
+ CHILD_ERR_ENOENT,
217
+ CHILD_ERR_SILENT,
218
+ CHILD_ERR_ERRNO
219
+};
220
+
221
+struct child_err {
222
+ enum child_errcode err;
223
+ int syserr; /* errno */
224
+};
225
+
226
+static void child_die(enum child_errcode err)
227
{
216
- /*
217
- * execvp failed. If possible, we'd like to let start_command
218
- * know, so failures like ENOENT can be handled right away; but
219
- * otherwise, finish_command will still report the error.
220
- */
221
- xwrite(child_notifier, "", 1);
228
+ struct child_err buf;
229
+
230
+ buf.err = err;
231
+ buf.syserr = errno;
232
+
233
+ /* write(2) on buf smaller than PIPE_BUF (min 512) is atomic: */
234
+ xwrite(child_notifier, &buf, sizeof(buf));
235
+ _exit(1);
236
+}
237
+
238
+/*
239
+ * parent will make it look like the child spewed a fatal error and died
240
+ * this is needed to prevent changes to t0061.
241
+ */
242
+static void fake_fatal(const char *err, va_list params)
243
+{
244
+ vreportf("fatal: ", err, params);
245
+}
246
+
247
+static void child_error_fn(const char *err, va_list params)
248
+{
249
+ const char msg[] = "error() should not be called in child\n";
250
+ xwrite(2, msg, sizeof(msg) - 1);
251
+}
252
+
253
+static void child_warn_fn(const char *err, va_list params)
254
+{
255
+ const char msg[] = "warn() should not be called in child\n";
256
+ xwrite(2, msg, sizeof(msg) - 1);
257
+}
258
+
259
+static void NORETURN child_die_fn(const char *err, va_list params)
260
+{
261
+ const char msg[] = "die() should not be called in child\n";
262
+ xwrite(2, msg, sizeof(msg) - 1);
263
+ _exit(2);
264
+}
265
+
266
+/* this runs in the parent process */
267
+static void child_err_spew(struct child_process *cmd, struct child_err *cerr)
268
+{
269
+ static void (*old_errfn)(const char *err, va_list params);
270
+
271
+ old_errfn = get_error_routine();
272
+ set_error_routine(fake_fatal);
273
+ errno = cerr->syserr;
274
+
275
+ switch (cerr->err) {
276
+ case CHILD_ERR_CHDIR:
277
+ error_errno("exec '%s': cd to '%s' failed",
278
+ cmd->argv[0], cmd->dir);
279
+ break;
280
+ case CHILD_ERR_ENOENT:
281
+ error_errno("cannot run %s", cmd->argv[0]);
282
+ break;
283
+ case CHILD_ERR_SILENT:
284
+ break;
285
+ case CHILD_ERR_ERRNO:
286
+ error_errno("cannot exec '%s'", cmd->argv[0]);
287
+ break;
288
+ }
289
+ set_error_routine(old_errfn);
290
}
291
292
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)
409
code += 128;
410
} else if (WIFEXITED(status)) {
411
code = WEXITSTATUS(status);
344
- /*
345
- * Convert special exit code when execvp failed.
346
- */
347
- if (code == 127) {
348
- code = -1;
349
- failed_errno = ENOENT;
350
- }
412
} else {
413
error("waitpid is confused (%s)", argv0);
414
}
@@ -435,6 +496,7 @@ fail_pipe:
496
int null_fd = -1;
497
char **childenv;
498
struct argv_array argv = ARGV_ARRAY_INIT;
499
+ struct child_err cerr;
500
501
if (pipe(notify_pipe))
502
notify_pipe[0] = notify_pipe[1] = -1;
@@ -453,20 +515,16 @@ fail_pipe:
515
failed_errno = errno;
516
if (!cmd->pid) {
517
/*
456
- * Redirect the channel to write syscall error messages to
457
- * before redirecting the process's stderr so that all die()
458
- * in subsequent call paths use the parent's stderr.
518
+ * Ensure the default die/error/warn routines do not get
519
+ * called, they can take stdio locks and malloc.
520
*/
460
- if (cmd->no_stderr || need_err) {
461
- int child_err = dup(2);
462
- set_cloexec(child_err);
463
- set_error_handle(fdopen(child_err, "w"));
464
- }
521
+ set_die_routine(child_die_fn);
522
+ set_error_routine(child_error_fn);
523
+ set_warn_routine(child_warn_fn);
524
525
close(notify_pipe[0]);
526
set_cloexec(notify_pipe[1]);
527
child_notifier = notify_pipe[1];
469
- atexit(notify_parent);
528
529
if (cmd->no_stdin)
530
dup2(null_fd, 0);
@@ -501,8 +559,7 @@ fail_pipe:
559
}
560
561
if (cmd->dir && chdir(cmd->dir))
504
- die_errno("exec '%s': cd to '%s' failed", cmd->argv[0],
505
- cmd->dir);
562
+ child_die(CHILD_ERR_CHDIR);
563
564
/*
565
* Attempt to exec using the command and arguments starting at
@@ -517,12 +574,11 @@ fail_pipe:
574
(char *const *) childenv);
575
576
if (errno == ENOENT) {
520
- if (!cmd->silent_exec_failure)
521
- error("cannot run %s: %s", cmd->argv[0],
522
- strerror(ENOENT));
523
- exit(127);
577
+ if (cmd->silent_exec_failure)
578
+ child_die(CHILD_ERR_SILENT);
579
+ child_die(CHILD_ERR_ENOENT);
580
} else {
525
- die_errno("cannot exec '%s'", cmd->argv[0]);
581
+ child_die(CHILD_ERR_ERRNO);
582
}
583
}
584
if (cmd->pid < 0)
@@ -533,17 +589,18 @@ fail_pipe:
589
/*
590
* Wait for child's exec. If the exec succeeds (or if fork()
591
* failed), EOF is seen immediately by the parent. Otherwise, the
536
- * child process sends a single byte.
592
+ * child process sends a child_err struct.
593
* Note that use of this infrastructure is completely advisory,
594
* therefore, we keep error checks minimal.
595
*/
596
close(notify_pipe[1]);
541
- if (read(notify_pipe[0], ¬ify_pipe[1], 1) == 1) {
597
+ if (xread(notify_pipe[0], &cerr, sizeof(cerr)) == sizeof(cerr)) {
598
/*
599
* At this point we know that fork() succeeded, but exec()
600
* failed. Errors have been reported to our stderr.
601
*/
602
wait_or_whine(cmd->pid, cmd->argv[0], 0);
603
+ child_err_spew(cmd, &cerr);
604
failed_errno = errno;
605
cmd->pid = -1;
606
}