static int launch_specified_editor(const char *editor, const char *path, struct strbuf *buffer, const char *const *env) { if (!editor) return error("Terminal is dumb, but EDITOR unset"); if (strcmp(editor, ":")) { const char *args[] = { editor, real_path(path), NULL }; struct child_process p = CHILD_PROCESS_INIT; int ret, sig; int print_waiting_for_editor = advice_waiting_for_editor && isatty(2); if (print_waiting_for_editor) { /* * A dumb terminal cannot erase the line later on. Add a * newline to separate the hint from subsequent output. * * Make sure that our message is separated with a whitespace * from further cruft that may be written by the editor. */ const char term = is_terminal_dumb() ? '\n' : ' '; fprintf(stderr, _("hint: Waiting for your editor to close the file...%c"), term); fflush(stderr); } p.argv = args; p.env = env; p.use_shell = 1; if (start_command(&p) < 0) return error("unable to start editor '%s'", editor); sigchain_push(SIGINT, SIG_IGN); sigchain_push(SIGQUIT, SIG_IGN); ret = finish_command(&p); sig = ret - 128; sigchain_pop(SIGINT); sigchain_pop(SIGQUIT); if (sig == SIGINT || sig == SIGQUIT) raise(sig); if (ret) return error("There was a problem with the editor '%s'.", editor); if (print_waiting_for_editor && !is_terminal_dumb()) /* * Go back to the beginning and erase the entire line to * avoid wasting the vertical space. */ fputs("\r\033[K", stderr); } if (!buffer) return 0; if (strbuf_read_file(buffer, path, 0) < 0) return error_errno("could not read file '%s'", path); return 0; }
void sigchain_push_common(sigchain_fun f) { sigchain_push(SIGINT, f); sigchain_push(SIGHUP, f); sigchain_push(SIGTERM, f); sigchain_push(SIGQUIT, f); sigchain_push(SIGPIPE, f); }
int run_rewrite_hook(struct rewritten *list, const char *name) { struct strbuf buf = STRBUF_INIT; struct child_process proc = CHILD_PROCESS_INIT; const char *argv[3]; int code, i; argv[0] = find_hook("post-rewrite"); if (!argv[0]) return 0; argv[1] = name; argv[2] = NULL; proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; code = start_command(&proc); if (code) return code; for (i = 0; i < list->nr; i++) { struct rewritten_item *item = &list->items[i]; strbuf_addf(&buf, "%s %s\n", sha1_to_hex(item->from), sha1_to_hex(item->to)); } sigchain_push(SIGPIPE, SIG_IGN); write_in_full(proc.in, buf.buf, buf.len); close(proc.in); sigchain_pop(SIGPIPE); return finish_command(&proc); }
/* * We would want to bypass the object transfer altogether if * everything we are going to fetch already exists and is connected * locally. * * The refs we are going to fetch are in ref_map. If running * * $ git rev-list --objects --stdin --not --all * * (feeding all the refs in ref_map on its standard input) * does not error out, that means everything reachable from the * refs we are going to fetch exists and is connected to some of * our existing refs. */ static int quickfetch(struct ref *ref_map) { struct child_process revlist; struct ref *ref; int err; const char *argv[] = {"rev-list", "--quiet", "--objects", "--stdin", "--not", "--all", NULL}; /* * If we are deepening a shallow clone we already have these * objects reachable. Running rev-list here will return with * a good (0) exit status and we'll bypass the fetch that we * really need to perform. Claiming failure now will ensure * we perform the network exchange to deepen our history. */ if (depth) return -1; if (!ref_map) return 0; memset(&revlist, 0, sizeof(revlist)); revlist.argv = argv; revlist.git_cmd = 1; revlist.no_stdout = 1; revlist.no_stderr = 1; revlist.in = -1; err = start_command(&revlist); if (err) { error("could not run rev-list"); return err; } /* * If rev-list --stdin encounters an unknown commit, it terminates, * which will cause SIGPIPE in the write loop below. */ sigchain_push(SIGPIPE, SIG_IGN); for (ref = ref_map; ref; ref = ref->next) { if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || write_str_in_full(revlist.in, "\n") < 0) { if (errno != EPIPE && errno != EINVAL) error("failed write to rev-list: %s", strerror(errno)); err = -1; break; } } if (close(revlist.in)) { error("failed to close rev-list's stdin: %s", strerror(errno)); err = -1; } sigchain_pop(SIGPIPE); return finish_command(&revlist) || err; }
static void subprocess_exit_handler(struct child_process *process) { sigchain_push(SIGPIPE, SIG_IGN); /* Closing the pipe signals the subprocess to initiate a shutdown. */ close(process->in); close(process->out); sigchain_pop(SIGPIPE); /* Finish command will wait until the shutdown is complete. */ finish_command(process); }
/* * Run "gpg" to see if the payload matches the detached signature. * gpg_output, when set, receives the diagnostic output from GPG. * gpg_status, when set, receives the status output from GPG. */ int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status) { struct child_process gpg = CHILD_PROCESS_INIT; const char *args_gpg[] = {NULL, "--status-fd=1", "--verify", "FILE", "-", NULL}; char path[PATH_MAX]; int fd, ret; struct strbuf buf = STRBUF_INIT; struct strbuf *pbuf = &buf; args_gpg[0] = gpg_program; fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX"); if (fd < 0) return error(_("could not create temporary file '%s': %s"), path, strerror(errno)); if (write_in_full(fd, signature, signature_size) < 0) return error(_("failed writing detached signature to '%s': %s"), path, strerror(errno)); close(fd); gpg.argv = args_gpg; gpg.in = -1; gpg.out = -1; if (gpg_output) gpg.err = -1; args_gpg[3] = path; if (start_command(&gpg)) { unlink(path); return error(_("could not run gpg.")); } sigchain_push(SIGPIPE, SIG_IGN); write_in_full(gpg.in, payload, payload_size); close(gpg.in); if (gpg_output) { strbuf_read(gpg_output, gpg.err, 0); close(gpg.err); } if (gpg_status) pbuf = gpg_status; strbuf_read(pbuf, gpg.out, 0); close(gpg.out); ret = finish_command(&gpg); sigchain_pop(SIGPIPE); unlink_or_warn(path); ret |= !strstr(pbuf->buf, "\n[GNUPG:] GOODSIG "); strbuf_release(&buf); /* no matter it was used or not */ return ret; }
static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state) { struct child_process proc = CHILD_PROCESS_INIT; struct async muxer; const char *argv[2]; int code; argv[0] = find_hook(hook_name); if (!argv[0]) return 0; argv[1] = NULL; proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; if (use_sideband) { memset(&muxer, 0, sizeof(muxer)); muxer.proc = copy_to_sideband; muxer.in = -1; code = start_async(&muxer); if (code) return code; proc.err = muxer.in; } prepare_push_cert_sha1(&proc); code = start_command(&proc); if (code) { if (use_sideband) finish_async(&muxer); return code; } sigchain_push(SIGPIPE, SIG_IGN); while (1) { const char *buf; size_t n; if (feed(feed_state, &buf, &n)) break; if (write_in_full(proc.in, buf, n) != n) break; } close(proc.in); if (use_sideband) finish_async(&muxer); sigchain_pop(SIGPIPE); return finish_command(&proc); }
/* * Create a detached signature for the contents of "buffer" and append * it after "signature"; "buffer" and "signature" can be the same * strbuf instance, which would cause the detached signature appended * at the end. */ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) { struct child_process gpg; const char *args[4]; ssize_t len; size_t i, j, bottom; memset(&gpg, 0, sizeof(gpg)); gpg.argv = args; gpg.in = -1; gpg.out = -1; args[0] = gpg_program; args[1] = "-bsau"; args[2] = signing_key; args[3] = NULL; if (start_command(&gpg)) return error(_("could not run gpg.")); /* * When the username signingkey is bad, program could be terminated * because gpg exits without reading and then write gets SIGPIPE. */ sigchain_push(SIGPIPE, SIG_IGN); if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) { close(gpg.in); close(gpg.out); finish_command(&gpg); return error(_("gpg did not accept the data")); } close(gpg.in); bottom = signature->len; len = strbuf_read(signature, gpg.out, 1024); close(gpg.out); sigchain_pop(SIGPIPE); if (finish_command(&gpg) || !len || len < 0) return error(_("gpg failed to sign the data")); /* Strip CR from the line endings, in case we are on Windows. */ for (i = j = bottom; i < signature->len; i++) if (signature->buf[i] != '\r') { if (i != j) signature->buf[j] = signature->buf[i]; j++; } strbuf_setlen(signature, j); return 0; }
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env) { const char *editor = git_editor(); if (!editor) return error("Terminal is dumb, but EDITOR unset"); if (strcmp(editor, ":")) { const char *args[] = { editor, path, NULL }; struct child_process p; int ret, sig; memset(&p, 0, sizeof(p)); p.argv = args; p.env = env; p.use_shell = 1; if (start_command(&p) < 0) return error("unable to start editor '%s'", editor); sigchain_push(SIGINT, SIG_IGN); sigchain_push(SIGQUIT, SIG_IGN); ret = finish_command(&p); sig = ret + 128; sigchain_pop(SIGINT); sigchain_pop(SIGQUIT); if (sig == SIGINT || sig == SIGQUIT) raise(sig); if (ret) return error("There was a problem with the editor '%s'.", editor); } if (!buffer) return 0; if (strbuf_read_file(buffer, path, 0) < 0) return error("could not read file '%s': %s", path, strerror(errno)); return 0; }
/* * If we feed all the commits we want to verify to this command * * $ git rev-list --objects --stdin --not --all * * and if it does not error out, that means everything reachable from * these commits locally exists and is connected to our existing refs. * Note that this does _not_ validate the individual objects. * * Returns 0 if everything is connected, non-zero otherwise. */ int check_everything_connected(sha1_iterate_fn fn, int quiet, void *cb_data) { struct child_process rev_list; const char *argv[] = {"rev-list", "--objects", "--stdin", "--not", "--all", NULL, NULL}; char commit[41]; unsigned char sha1[20]; int err = 0; if (fn(cb_data, sha1)) return err; if (quiet) argv[5] = "--quiet"; memset(&rev_list, 0, sizeof(rev_list)); rev_list.argv = argv; rev_list.git_cmd = 1; rev_list.in = -1; rev_list.no_stdout = 1; rev_list.no_stderr = quiet; if (start_command(&rev_list)) return error(_("Could not run 'git rev-list'")); sigchain_push(SIGPIPE, SIG_IGN); commit[40] = '\n'; do { memcpy(commit, sha1_to_hex(sha1), 40); if (write_in_full(rev_list.in, commit, 41) < 0) { if (errno != EPIPE && errno != EINVAL) error(_("failed write to rev-list: %s"), strerror(errno)); err = -1; break; } } while (!fn(cb_data, sha1)); if (close(rev_list.in)) { error(_("failed to close rev-list's stdin: %s"), strerror(errno)); err = -1; } sigchain_pop(SIGPIPE); return finish_command(&rev_list) || err; }
int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status) { struct child_process gpg = CHILD_PROCESS_INIT; struct tempfile *temp; int ret; struct strbuf buf = STRBUF_INIT; temp = mks_tempfile_t(".git_vtag_tmpXXXXXX"); if (!temp) return error_errno(_("could not create temporary file")); if (write_in_full(temp->fd, signature, signature_size) < 0 || close_tempfile_gently(temp) < 0) { error_errno(_("failed writing detached signature to '%s'"), temp->filename.buf); delete_tempfile(&temp); return -1; } argv_array_pushl(&gpg.args, gpg_program, "--status-fd=1", "--keyid-format=long", "--verify", temp->filename.buf, "-", NULL); if (!gpg_status) gpg_status = &buf; sigchain_push(SIGPIPE, SIG_IGN); ret = pipe_command(&gpg, payload, payload_size, gpg_status, 0, gpg_output, 0); sigchain_pop(SIGPIPE); delete_tempfile(&temp); ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG "); strbuf_release(&buf); /* no matter it was used or not */ return ret; }
int subprocess_handshake(struct subprocess_entry *entry, const char *welcome_prefix, int *versions, int *chosen_version, struct subprocess_capability *capabilities, unsigned int *supported_capabilities) { int retval; struct child_process *process = &entry->process; sigchain_push(SIGPIPE, SIG_IGN); retval = handshake_version(process, welcome_prefix, versions, chosen_version) || handshake_capabilities(process, capabilities, supported_capabilities); sigchain_pop(SIGPIPE); return retval; }
static int run_credential_helper(struct credential *c, const char *cmd, int want_output) { struct child_process helper = CHILD_PROCESS_INIT; const char *argv[] = { NULL, NULL }; FILE *fp; argv[0] = cmd; helper.argv = argv; helper.use_shell = 1; helper.in = -1; if (want_output) helper.out = -1; else helper.no_stdout = 1; if (start_command(&helper) < 0) return -1; fp = xfdopen(helper.in, "w"); sigchain_push(SIGPIPE, SIG_IGN); credential_write(c, fp); fclose(fp); sigchain_pop(SIGPIPE); if (want_output) { int r; fp = xfdopen(helper.out, "r"); r = credential_read(c, fp); fclose(fp); if (r < 0) { finish_command(&helper); return -1; } } if (finish_command(&helper)) return -1; return 0; }
int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) { struct child_process gpg = CHILD_PROCESS_INIT; int ret; size_t i, j, bottom; struct strbuf gpg_status = STRBUF_INIT; argv_array_pushl(&gpg.args, gpg_program, "--status-fd=2", "-bsau", signing_key, NULL); bottom = signature->len; /* * When the username signingkey is bad, program could be terminated * because gpg exits without reading and then write gets SIGPIPE. */ sigchain_push(SIGPIPE, SIG_IGN); ret = pipe_command(&gpg, buffer->buf, buffer->len, signature, 1024, &gpg_status, 0); sigchain_pop(SIGPIPE); ret |= !strstr(gpg_status.buf, "\n[GNUPG:] SIG_CREATED "); strbuf_release(&gpg_status); if (ret) return error(_("gpg failed to sign the data")); /* Strip CR from the line endings, in case we are on Windows. */ for (i = j = bottom; i < signature->len; i++) if (signature->buf[i] != '\r') { if (i != j) signature->buf[j] = signature->buf[i]; j++; } strbuf_setlen(signature, j); return 0; }
static int run_pre_push_hook(struct transport *transport, struct ref *remote_refs) { int ret = 0, x; struct ref *r; struct child_process proc = CHILD_PROCESS_INIT; struct strbuf buf; const char *argv[4]; if (!(argv[0] = find_hook("pre-push"))) return 0; argv[1] = transport->remote->name; argv[2] = transport->url; argv[3] = NULL; proc.argv = argv; proc.in = -1; if (start_command(&proc)) { finish_command(&proc); return -1; } sigchain_push(SIGPIPE, SIG_IGN); strbuf_init(&buf, 256); for (r = remote_refs; r; r = r->next) { if (!r->peer_ref) continue; if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue; if (r->status == REF_STATUS_REJECT_STALE) continue; if (r->status == REF_STATUS_UPTODATE) continue; strbuf_reset(&buf); strbuf_addf( &buf, "%s %s %s %s\n", r->peer_ref->name, oid_to_hex(&r->new_oid), r->name, oid_to_hex(&r->old_oid)); if (write_in_full(proc.in, buf.buf, buf.len) < 0) { /* We do not mind if a hook does not read all refs. */ if (errno != EPIPE) ret = -1; break; } } strbuf_release(&buf); x = close(proc.in); if (!ret) ret = x; sigchain_pop(SIGPIPE); x = finish_command(&proc); if (!ret) ret = x; return ret; }
static void check_non_tip(void) { static const char *argv[] = { "rev-list", "--stdin", NULL, }; static struct child_process cmd = CHILD_PROCESS_INIT; struct object *o; char namebuf[42]; /* ^ + SHA-1 + LF */ int i; /* In the normal in-process case non-tip request can never happen */ if (!stateless_rpc) goto error; cmd.argv = argv; cmd.git_cmd = 1; cmd.no_stderr = 1; cmd.in = -1; cmd.out = -1; if (start_command(&cmd)) goto error; /* * If rev-list --stdin encounters an unknown commit, it * terminates, which will cause SIGPIPE in the write loop * below. */ sigchain_push(SIGPIPE, SIG_IGN); namebuf[0] = '^'; namebuf[41] = '\n'; for (i = get_max_object_index(); 0 < i; ) { o = get_indexed_object(--i); if (!o) continue; if (!is_our_ref(o)) continue; memcpy(namebuf + 1, sha1_to_hex(o->sha1), 40); if (write_in_full(cmd.in, namebuf, 42) < 0) goto error; } namebuf[40] = '\n'; for (i = 0; i < want_obj.nr; i++) { o = want_obj.objects[i].item; if (is_our_ref(o)) continue; memcpy(namebuf, sha1_to_hex(o->sha1), 40); if (write_in_full(cmd.in, namebuf, 41) < 0) goto error; } close(cmd.in); sigchain_pop(SIGPIPE); /* * The commits out of the rev-list are not ancestors of * our ref. */ i = read_in_full(cmd.out, namebuf, 1); if (i) goto error; close(cmd.out); /* * rev-list may have died by encountering a bad commit * in the history, in which case we do want to bail out * even when it showed no commit. */ if (finish_command(&cmd)) goto error; /* All the non-tip ones are ancestors of what we advertised */ return; error: /* Pick one of them (we know there at least is one) */ for (i = 0; i < want_obj.nr; i++) { o = want_obj.objects[i].item; if (!is_our_ref(o)) die("git upload-pack: not our ref %s", sha1_to_hex(o->sha1)); } }
static int run_and_feed_hook(const char *hook_name, feed_fn feed, struct receive_hook_feed_state *feed_state) { struct child_process proc = CHILD_PROCESS_INIT; struct async muxer; const char *argv[2]; int code; argv[0] = find_hook(hook_name); if (!argv[0]) return 0; argv[1] = NULL; proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; if (feed_state->push_options) { int i; for (i = 0; i < feed_state->push_options->nr; i++) argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_%d=%s", i, feed_state->push_options->items[i].string); argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", feed_state->push_options->nr); } else argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); if (use_sideband) { memset(&muxer, 0, sizeof(muxer)); muxer.proc = copy_to_sideband; muxer.in = -1; code = start_async(&muxer); if (code) return code; proc.err = muxer.in; } prepare_push_cert_sha1(&proc); code = start_command(&proc); if (code) { if (use_sideband) finish_async(&muxer); return code; } sigchain_push(SIGPIPE, SIG_IGN); while (1) { const char *buf; size_t n; if (feed(feed_state, &buf, &n)) break; if (write_in_full(proc.in, buf, n) != n) break; } close(proc.in); if (use_sideband) finish_async(&muxer); sigchain_pop(SIGPIPE); return finish_command(&proc); }
/* * If we feed all the commits we want to verify to this command * * $ git rev-list --objects --stdin --not --all * * and if it does not error out, that means everything reachable from * these commits locally exists and is connected to our existing refs. * Note that this does _not_ validate the individual objects. * * Returns 0 if everything is connected, non-zero otherwise. */ int check_connected(sha1_iterate_fn fn, void *cb_data, struct check_connected_options *opt) { struct child_process rev_list = CHILD_PROCESS_INIT; struct check_connected_options defaults = CHECK_CONNECTED_INIT; char commit[41]; unsigned char sha1[20]; int err = 0; struct packed_git *new_pack = NULL; struct transport *transport; size_t base_len; if (!opt) opt = &defaults; transport = opt->transport; if (fn(cb_data, sha1)) { if (opt->err_fd) close(opt->err_fd); return err; } if (transport && transport->smart_options && transport->smart_options->self_contained_and_connected && transport->pack_lockfile && strip_suffix(transport->pack_lockfile, ".keep", &base_len)) { struct strbuf idx_file = STRBUF_INIT; strbuf_add(&idx_file, transport->pack_lockfile, base_len); strbuf_addstr(&idx_file, ".idx"); new_pack = add_packed_git(idx_file.buf, idx_file.len, 1); strbuf_release(&idx_file); } if (opt->shallow_file) { argv_array_push(&rev_list.args, "--shallow-file"); argv_array_push(&rev_list.args, opt->shallow_file); } argv_array_push(&rev_list.args,"rev-list"); argv_array_push(&rev_list.args, "--objects"); argv_array_push(&rev_list.args, "--stdin"); argv_array_push(&rev_list.args, "--not"); argv_array_push(&rev_list.args, "--all"); argv_array_push(&rev_list.args, "--quiet"); if (opt->progress) argv_array_pushf(&rev_list.args, "--progress=%s", _("Checking connectivity")); rev_list.git_cmd = 1; rev_list.env = opt->env; rev_list.in = -1; rev_list.no_stdout = 1; if (opt->err_fd) rev_list.err = opt->err_fd; else rev_list.no_stderr = opt->quiet; if (start_command(&rev_list)) return error(_("Could not run 'git rev-list'")); sigchain_push(SIGPIPE, SIG_IGN); commit[40] = '\n'; do { /* * If index-pack already checked that: * - there are no dangling pointers in the new pack * - the pack is self contained * Then if the updated ref is in the new pack, then we * are sure the ref is good and not sending it to * rev-list for verification. */ if (new_pack && find_pack_entry_one(sha1, new_pack)) continue; memcpy(commit, sha1_to_hex(sha1), 40); if (write_in_full(rev_list.in, commit, 41) < 0) { if (errno != EPIPE && errno != EINVAL) error_errno(_("failed write to rev-list")); err = -1; break; } } while (!fn(cb_data, sha1)); if (close(rev_list.in)) err = error_errno(_("failed to close rev-list's stdin")); sigchain_pop(SIGPIPE); return finish_command(&rev_list) || err; }
/* * on successful case, it's up to the caller to close cmd->out */ static int do_reachable_revlist(struct child_process *cmd, struct object_array *src, struct object_array *reachable) { static const char *argv[] = { "rev-list", "--stdin", NULL, }; struct object *o; char namebuf[GIT_MAX_HEXSZ + 2]; /* ^ + hash + LF */ int i; cmd->argv = argv; cmd->git_cmd = 1; cmd->no_stderr = 1; cmd->in = -1; cmd->out = -1; /* * If the next rev-list --stdin encounters an unknown commit, * it terminates, which will cause SIGPIPE in the write loop * below. */ sigchain_push(SIGPIPE, SIG_IGN); if (start_command(cmd)) goto error; namebuf[0] = '^'; namebuf[GIT_SHA1_HEXSZ + 1] = '\n'; for (i = get_max_object_index(); 0 < i; ) { o = get_indexed_object(--i); if (!o) continue; if (reachable && o->type == OBJ_COMMIT) o->flags &= ~TMP_MARK; if (!is_our_ref(o)) continue; memcpy(namebuf + 1, oid_to_hex(&o->oid), GIT_SHA1_HEXSZ); if (write_in_full(cmd->in, namebuf, GIT_SHA1_HEXSZ + 2) < 0) goto error; } namebuf[GIT_SHA1_HEXSZ] = '\n'; for (i = 0; i < src->nr; i++) { o = src->objects[i].item; if (is_our_ref(o)) { if (reachable) add_object_array(o, NULL, reachable); continue; } if (reachable && o->type == OBJ_COMMIT) o->flags |= TMP_MARK; memcpy(namebuf, oid_to_hex(&o->oid), GIT_SHA1_HEXSZ); if (write_in_full(cmd->in, namebuf, GIT_SHA1_HEXSZ + 1) < 0) goto error; } close(cmd->in); cmd->in = -1; sigchain_pop(SIGPIPE); return 0; error: sigchain_pop(SIGPIPE); if (cmd->in >= 0) close(cmd->in); if (cmd->out >= 0) close(cmd->out); return -1; }