Skip to content

Commit ea1fd48

Browse files
committed
Merge branch 'jk/run-command-capture'
The run-command interface was easy to abuse and make a pipe for us to read from the process, wait for the process to finish and then attempt to read its output, which is a pattern that lead to a deadlock. Fix such uses by introducing a helper to do this correctly (i.e. we need to read first and then wait the process to finish) and also add code to prevent such abuse in the run-command helper. * jk/run-command-capture: run-command: forbid using run_command with piped output trailer: use capture_command submodule: use capture_command wt-status: use capture_command run-command: introduce capture_command helper wt_status: fix signedness mismatch in strbuf_read call wt-status: don't flush before running "submodule status"
2 parents d78374e + c29b396 commit ea1fd48

File tree

5 files changed

+44
-24
lines changed

5 files changed

+44
-24
lines changed

run-command.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,12 @@ int finish_command(struct child_process *cmd)
557557

558558
int run_command(struct child_process *cmd)
559559
{
560-
int code = start_command(cmd);
560+
int code;
561+
562+
if (cmd->out < 0 || cmd->err < 0)
563+
die("BUG: run_command with a pipe can cause deadlock");
564+
565+
code = start_command(cmd);
561566
if (code)
562567
return code;
563568
return finish_command(cmd);
@@ -829,3 +834,19 @@ int run_hook_le(const char *const *env, const char *name, ...)
829834

830835
return ret;
831836
}
837+
838+
int capture_command(struct child_process *cmd, struct strbuf *buf, size_t hint)
839+
{
840+
cmd->out = -1;
841+
if (start_command(cmd) < 0)
842+
return -1;
843+
844+
if (strbuf_read(buf, cmd->out, hint) < 0) {
845+
close(cmd->out);
846+
finish_command(cmd); /* throw away exit code */
847+
return -1;
848+
}
849+
850+
close(cmd->out);
851+
return finish_command(cmd);
852+
}

run-command.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,19 @@ int run_command_v_opt(const char **argv, int opt);
7171
*/
7272
int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
7373

74+
/**
75+
* Execute the given command, capturing its stdout in the given strbuf.
76+
* Returns -1 if starting the command fails or reading fails, and otherwise
77+
* returns the exit code of the command. The output collected in the
78+
* buffer is kept even if the command returns a non-zero exit. The hint field
79+
* gives a starting size for the strbuf allocation.
80+
*
81+
* The fields of "cmd" should be set up as they would for a normal run_command
82+
* invocation. But note that there is no need to set cmd->out; the function
83+
* sets it up for the caller.
84+
*/
85+
int capture_command(struct child_process *cmd, struct strbuf *buf, size_t hint);
86+
7487
/*
7588
* The purpose of the following functions is to feed a pipe by running
7689
* a function asynchronously and providing output that the caller reads.

submodule.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -576,12 +576,10 @@ static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
576576
cp.env = local_repo_env;
577577
cp.git_cmd = 1;
578578
cp.no_stdin = 1;
579-
cp.out = -1;
580579
cp.dir = path;
581-
if (!run_command(&cp) && !strbuf_read(&buf, cp.out, 1024))
580+
if (!capture_command(&cp, &buf, 1024) && !buf.len)
582581
is_present = 1;
583582

584-
close(cp.out);
585583
strbuf_release(&buf);
586584
}
587585
return is_present;

trailer.c

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,6 @@ static struct trailer_item *remove_first(struct trailer_item **first)
214214
return item;
215215
}
216216

217-
static int read_from_command(struct child_process *cp, struct strbuf *buf)
218-
{
219-
if (run_command(cp))
220-
return error("running trailer command '%s' failed", cp->argv[0]);
221-
if (strbuf_read(buf, cp->out, 1024) < 1)
222-
return error("reading from trailer command '%s' failed", cp->argv[0]);
223-
strbuf_trim(buf);
224-
return 0;
225-
}
226-
227217
static const char *apply_command(const char *command, const char *arg)
228218
{
229219
struct strbuf cmd = STRBUF_INIT;
@@ -240,14 +230,16 @@ static const char *apply_command(const char *command, const char *arg)
240230
cp.argv = argv;
241231
cp.env = local_repo_env;
242232
cp.no_stdin = 1;
243-
cp.out = -1;
244233
cp.use_shell = 1;
245234

246-
if (read_from_command(&cp, &buf)) {
235+
if (capture_command(&cp, &buf, 1024)) {
236+
error("running trailer command '%s' failed", cmd.buf);
247237
strbuf_release(&buf);
248238
result = xstrdup("");
249-
} else
239+
} else {
240+
strbuf_trim(&buf);
250241
result = strbuf_detach(&buf, NULL);
242+
}
251243

252244
strbuf_release(&cmd);
253245
return result;

wt-status.c

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,6 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt
729729
struct strbuf cmd_stdout = STRBUF_INIT;
730730
struct strbuf summary = STRBUF_INIT;
731731
char *summary_content;
732-
size_t len;
733732

734733
argv_array_pushf(&sm_summary.env_array, "GIT_INDEX_FILE=%s",
735734
s->index_file);
@@ -745,15 +744,11 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt
745744

746745
sm_summary.git_cmd = 1;
747746
sm_summary.no_stdin = 1;
748-
fflush(s->fp);
749-
sm_summary.out = -1;
750747

751-
run_command(&sm_summary);
752-
753-
len = strbuf_read(&cmd_stdout, sm_summary.out, 1024);
748+
capture_command(&sm_summary, &cmd_stdout, 1024);
754749

755750
/* prepend header, only if there's an actual output */
756-
if (len) {
751+
if (cmd_stdout.len) {
757752
if (uncommitted)
758753
strbuf_addstr(&summary, _("Submodules changed but not updated:"));
759754
else
@@ -764,6 +759,7 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt
764759
strbuf_release(&cmd_stdout);
765760

766761
if (s->display_comment_prefix) {
762+
size_t len;
767763
summary_content = strbuf_detach(&summary, &len);
768764
strbuf_add_commented_lines(&summary, summary_content, len);
769765
free(summary_content);

0 commit comments

Comments
 (0)