From: Junio C Hamano Date: Sat, 6 Feb 2010 05:08:53 +0000 (-0800) Subject: Merge branch 'sp/maint-push-sideband' into sp/push-sideband X-Git-Tag: v1.7.1-rc0~140^2~1 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/76d44c8cfda9be1db78884580ab045c421b083fe?ds=inline;hp=-c Merge branch 'sp/maint-push-sideband' into sp/push-sideband * sp/maint-push-sideband: receive-pack: Send hook output over side band #2 receive-pack: Wrap status reports inside side-band-64k receive-pack: Refactor how capabilities are shown to the client send-pack: demultiplex a sideband stream with status data run-command: support custom fd-set in async run-command: Allow stderr to be a caller supplied pipe Update git fsck --full short description to mention packs Conflicts: run-command.c --- 76d44c8cfda9be1db78884580ab045c421b083fe diff --combined builtin-receive-pack.c index 4320c93e70,da1c26b6be..b704fbd5b5 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@@ -2,6 -2,7 +2,7 @@@ #include "pack.h" #include "refs.h" #include "pkt-line.h" + #include "sideband.h" #include "run-command.h" #include "exec_cmd.h" #include "commit.h" @@@ -27,11 -28,12 +28,12 @@@ static int receive_unpack_limit = -1 static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; + static int use_sideband; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; static const char *head_name; - static char *capabilities_to_send; + static int sent_capabilities; static enum deny_action parse_deny_action(const char *var, const char *value) { @@@ -105,19 -107,21 +107,21 @@@ static int receive_pack_config(const ch static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { - if (!capabilities_to_send) + if (sent_capabilities) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); else - packet_write(1, "%s %s%c%s\n", - sha1_to_hex(sha1), path, 0, capabilities_to_send); - capabilities_to_send = NULL; + packet_write(1, "%s %s%c%s%s\n", + sha1_to_hex(sha1), path, 0, + " report-status delete-refs side-band-64k", + prefer_ofs_delta ? " ofs-delta" : ""); + sent_capabilities = 1; return 0; } static void write_head_info(void) { for_each_ref(show_ref, NULL); - if (capabilities_to_send) + if (!sent_capabilities) show_ref("capabilities^{}", null_sha1, 0, NULL); } @@@ -135,11 -139,25 +139,25 @@@ static struct command *commands static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; + static int copy_to_sideband(int in, int out, void *arg) + { + char data[128]; + while (1) { + ssize_t sz = xread(in, data, sizeof(data)); + if (sz <= 0) + break; + send_sideband(1, 2, data, sz, use_sideband); + } + close(in); + return 0; + } + static int run_receive_hook(const char *hook_name) { static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; struct command *cmd; struct child_process proc; + struct async muxer; const char *argv[2]; int have_input = 0, code; @@@ -159,9 -177,23 +177,23 @@@ 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; + } + code = start_command(&proc); - if (code) + if (code) { + if (use_sideband) + finish_async(&muxer); return code; + } + for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) { size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n", @@@ -173,6 -205,8 +205,8 @@@ } } close(proc.in); + if (use_sideband) + finish_async(&muxer); return finish_command(&proc); } @@@ -180,6 -214,8 +214,8 @@@ static int run_update_hook(struct comma { static const char update_hook[] = "hooks/update"; const char *argv[5]; + struct child_process proc; + int code; if (access(update_hook, X_OK) < 0) return 0; @@@ -190,8 -226,18 +226,18 @@@ argv[3] = sha1_to_hex(cmd->new_sha1); argv[4] = NULL; - return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | - RUN_COMMAND_STDOUT_TO_STDERR); + memset(&proc, 0, sizeof(proc)); + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.argv = argv; + + code = start_command(&proc); + if (code) + return code; + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + return finish_command(&proc); } static int is_ref_checked_out(const char *ref) @@@ -204,47 -250,59 +250,47 @@@ return !strcmp(head_name, ref); } -static char *warn_unconfigured_deny_msg[] = { - "Updating the currently checked out branch may cause confusion,", - "as the index and work tree do not reflect changes that are in HEAD.", - "As a result, you may see the changes you just pushed into it", - "reverted when you run 'git diff' over there, and you may want", - "to run 'git reset --hard' before starting to work to recover.", +static char *refuse_unconfigured_deny_msg[] = { + "By default, updating the current branch in a non-bare repository", + "is denied, because it will make the index and work tree inconsistent", + "with what you pushed, and will require 'git reset --hard' to match", + "the work tree to HEAD.", "", "You can set 'receive.denyCurrentBranch' configuration variable to", - "'refuse' in the remote repository to forbid pushing into its", - "current branch." + "'ignore' or 'warn' in the remote repository to allow pushing into", + "its current branch; however, this is not recommended unless you", + "arranged to update its work tree to match what you pushed in some", + "other way.", "", - "To allow pushing into the current branch, you can set it to 'ignore';", - "but this is not recommended unless you arranged to update its work", - "tree to match what you pushed in some other way.", - "", - "To squelch this message, you can set it to 'warn'.", - "", - "Note that the default will change in a future version of git", - "to refuse updating the current branch unless you have the", - "configuration variable set to either 'ignore' or 'warn'." + "To squelch this message and still keep the default behaviour, set", + "'receive.denyCurrentBranch' configuration variable to 'refuse'." }; -static void warn_unconfigured_deny(void) +static void refuse_unconfigured_deny(void) { int i; - for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++) - warning("%s", warn_unconfigured_deny_msg[i]); + for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++) + error("%s", refuse_unconfigured_deny_msg[i]); } -static char *warn_unconfigured_deny_delete_current_msg[] = { - "Deleting the current branch can cause confusion by making the next", - "'git clone' not check out any file.", +static char *refuse_unconfigured_deny_delete_current_msg[] = { + "By default, deleting the current branch is denied, because the next", + "'git clone' won't result in any file checked out, causing confusion.", "", "You can set 'receive.denyDeleteCurrent' configuration variable to", - "'refuse' in the remote repository to disallow deleting the current", - "branch.", - "", - "You can set it to 'ignore' to allow such a delete without a warning.", + "'warn' or 'ignore' in the remote repository to allow deleting the", + "current branch, with or without a warning message.", "", - "To make this warning message less loud, you can set it to 'warn'.", - "", - "Note that the default will change in a future version of git", - "to refuse deleting the current branch unless you have the", - "configuration variable set to either 'ignore' or 'warn'." + "To squelch this message, you can set it to 'refuse'." }; -static void warn_unconfigured_deny_delete_current(void) +static void refuse_unconfigured_deny_delete_current(void) { int i; for (i = 0; - i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg); + i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg); i++) - warning("%s", warn_unconfigured_deny_delete_current_msg[i]); + error("%s", refuse_unconfigured_deny_delete_current_msg[i]); } static const char *update(struct command *cmd) @@@ -264,14 -322,14 +310,14 @@@ switch (deny_current_branch) { case DENY_IGNORE: break; - case DENY_UNCONFIGURED: case DENY_WARN: warning("updating the current branch"); - if (deny_current_branch == DENY_UNCONFIGURED) - warn_unconfigured_deny(); break; case DENY_REFUSE: + case DENY_UNCONFIGURED: error("refusing to update checked out branch: %s", name); + if (deny_current_branch == DENY_UNCONFIGURED) + refuse_unconfigured_deny(); return "branch is currently checked out"; } } @@@ -293,12 -351,12 +339,12 @@@ case DENY_IGNORE: break; case DENY_WARN: - case DENY_UNCONFIGURED: - if (deny_delete_current == DENY_UNCONFIGURED) - warn_unconfigured_deny_delete_current(); warning("deleting the current branch"); break; case DENY_REFUSE: + case DENY_UNCONFIGURED: + if (deny_delete_current == DENY_UNCONFIGURED) + refuse_unconfigured_deny_delete_current(); error("refusing to delete the current branch: %s", name); return "deletion of the current branch prohibited"; } @@@ -368,8 -426,9 +414,9 @@@ static char update_post_hook[] = "hooks static void run_update_post_hook(struct command *cmd) { struct command *cmd_p; - int argc, status; + int argc; const char **argv; + struct child_process proc; for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) { if (cmd_p->error_string) @@@ -391,8 -450,18 +438,18 @@@ argc++; } argv[argc] = NULL; - status = run_command_v_opt(argv, RUN_COMMAND_NO_STDIN - | RUN_COMMAND_STDOUT_TO_STDERR); + + memset(&proc, 0, sizeof(proc)); + proc.no_stdin = 1; + proc.stdout_to_stderr = 1; + proc.err = use_sideband ? -1 : 0; + proc.argv = argv; + + if (!start_command(&proc)) { + if (use_sideband) + copy_to_sideband(proc.err, -1, NULL); + finish_command(&proc); + } } static void execute_commands(const char *unpacker_error) @@@ -452,6 -521,8 +509,8 @@@ static void read_head_info(void if (reflen + 82 < len) { if (strstr(refname + reflen + 1, "report-status")) report_status = 1; + if (strstr(refname + reflen + 1, "side-band-64k")) + use_sideband = LARGE_PACKET_MAX; } cmd = xmalloc(sizeof(struct command) + len - 80); hashcpy(cmd->old_sha1, old_sha1); @@@ -551,17 -622,25 +610,25 @@@ static const char *unpack(void static void report(const char *unpack_status) { struct command *cmd; - packet_write(1, "unpack %s\n", - unpack_status ? unpack_status : "ok"); + struct strbuf buf = STRBUF_INIT; + + packet_buf_write(&buf, "unpack %s\n", + unpack_status ? unpack_status : "ok"); for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) - packet_write(1, "ok %s\n", - cmd->ref_name); + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); else - packet_write(1, "ng %s %s\n", - cmd->ref_name, cmd->error_string); + packet_buf_write(&buf, "ng %s %s\n", + cmd->ref_name, cmd->error_string); } - packet_flush(1); + packet_buf_flush(&buf); + + if (use_sideband) + send_sideband(1, 1, buf.buf, buf.len, use_sideband); + else + safe_write(1, buf.buf, buf.len); + strbuf_release(&buf); } static int delete_only(struct command *cmd) @@@ -658,10 -737,6 +725,6 @@@ int cmd_receive_pack(int argc, const ch else if (0 <= receive_unpack_limit) unpack_limit = receive_unpack_limit; - capabilities_to_send = (prefer_ofs_delta) ? - " report-status delete-refs ofs-delta " : - " report-status delete-refs "; - if (advertise_refs || !stateless_rpc) { add_alternate_refs(); write_head_info(); @@@ -695,5 -770,7 +758,7 @@@ if (auto_update_server_info) update_server_info(0); } + if (use_sideband) + packet_flush(1); return 0; } diff --combined builtin-send-pack.c index 76c72065de,2478e1851a..2183a47052 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@@ -372,6 -372,14 +372,14 @@@ static void print_helper_status(struct strbuf_release(&buf); } + static int sideband_demux(int in, int out, void *data) + { + int *fd = data; + int ret = recv_sideband("send-pack", fd[0], out); + close(out); + return ret; + } + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, @@@ -382,18 -390,22 +390,22 @@@ struct strbuf req_buf = STRBUF_INIT; struct ref *ref; int new_refs; - int ask_for_status_report = 0; int allow_deleting_refs = 0; - int expect_status_report = 0; + int status_report = 0; + int use_sideband = 0; + unsigned cmds_sent = 0; int ret; + struct async demux; /* Does the other end support the reporting? */ if (server_supports("report-status")) - ask_for_status_report = 1; + status_report = 1; if (server_supports("delete-refs")) allow_deleting_refs = 1; if (server_supports("ofs-delta")) args->use_ofs_delta = 1; + if (server_supports("side-band-64k")) + use_sideband = 1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" @@@ -406,48 -418,80 +418,50 @@@ */ new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { - - if (ref->peer_ref) - hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - else if (!args->send_mirror) + if (!ref->peer_ref && !args->send_mirror) continue; - ref->deletion = is_null_sha1(ref->new_sha1); - if (ref->deletion && !allow_deleting_refs) { - ref->status = REF_STATUS_REJECT_NODELETE; - continue; - } - if (!ref->deletion && - !hashcmp(ref->old_sha1, ref->new_sha1)) { - ref->status = REF_STATUS_UPTODATE; + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_UPTODATE: continue; + default: + ; /* do nothing */ } - /* This part determines what can overwrite what. - * The rules are: - * - * (0) you can always use --force or +A:B notation to - * selectively force individual ref pairs. - * - * (1) if the old thing does not exist, it is OK. - * - * (2) if you do not have the old thing, you are not allowed - * to overwrite it; you would not know what you are losing - * otherwise. - * - * (3) if both new and old are commit-ish, and new is a - * descendant of old, it is OK. - * - * (4) regardless of all of the above, removing :B is - * always allowed. - */ - - ref->nonfastforward = - !ref->deletion && - !is_null_sha1(ref->old_sha1) && - (!has_sha1_file(ref->old_sha1) - || !ref_newer(ref->new_sha1, ref->old_sha1)); - - if (ref->nonfastforward && !ref->force && !args->force_update) { - ref->status = REF_STATUS_REJECT_NONFASTFORWARD; + if (ref->deletion && !allow_deleting_refs) { + ref->status = REF_STATUS_REJECT_NODELETE; continue; } if (!ref->deletion) new_refs++; - if (!args->dry_run) { + if (args->dry_run) { + ref->status = REF_STATUS_OK; + } else { char *old_hex = sha1_to_hex(ref->old_sha1); char *new_hex = sha1_to_hex(ref->new_sha1); - if (ask_for_status_report) { - packet_buf_write(&req_buf, "%s %s %s%c%s", + if (!cmds_sent && (status_report || use_sideband)) { + packet_buf_write(&req_buf, "%s %s %s%c%s%s", old_hex, new_hex, ref->name, 0, - "report-status"); - ask_for_status_report = 0; - expect_status_report = 1; + status_report ? " report-status" : "", + use_sideband ? " side-band-64k" : ""); } else packet_buf_write(&req_buf, "%s %s %s", old_hex, new_hex, ref->name); + ref->status = status_report ? + REF_STATUS_EXPECTING_REPORT : + REF_STATUS_OK; + cmds_sent++; } - ref->status = expect_status_report ? - REF_STATUS_EXPECTING_REPORT : - REF_STATUS_OK; } if (args->stateless_rpc) { - if (!args->dry_run) { + if (!args->dry_run && cmds_sent) { packet_buf_flush(&req_buf); send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); } @@@ -457,23 -501,43 +471,43 @@@ } strbuf_release(&req_buf); - if (new_refs && !args->dry_run) { + if (use_sideband && cmds_sent) { + memset(&demux, 0, sizeof(demux)); + demux.proc = sideband_demux; + demux.data = fd; + demux.out = -1; + if (start_async(&demux)) + die("receive-pack: unable to fork off sideband demultiplexer"); + in = demux.out; + } + + if (new_refs && cmds_sent) { if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; + if (use_sideband) + finish_async(&demux); return -1; } } - if (args->stateless_rpc && !args->dry_run) + if (args->stateless_rpc && cmds_sent) packet_flush(out); - if (expect_status_report) + if (status_report && cmds_sent) ret = receive_status(in, remote_refs); else ret = 0; if (args->stateless_rpc) packet_flush(out); + if (use_sideband && cmds_sent) { + if (finish_async(&demux)) { + error("error in sideband demultiplexer"); + ret = -1; + } + close(demux.out); + } + if (ret < 0) return ret; for (ref = remote_refs; ref; ref = ref->next) { @@@ -643,9 -707,6 +677,9 @@@ int cmd_send_pack(int argc, const char if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) return -1; + set_ref_status_for_push(remote_refs, args.send_mirror, + args.force_update); + ret = send_pack(&args, fd, conn, remote_refs, &extra_have); if (helper_status) diff --combined convert.c index 27acce58bc,e70ee094a7..4f8fcb7bbb --- a/convert.c +++ b/convert.c @@@ -241,7 -241,7 +241,7 @@@ struct filter_params const char *cmd; }; - static int filter_buffer(int fd, void *data) + static int filter_buffer(int in, int out, void *data) { /* * Spawn cmd and feed the buffer contents through its stdin. @@@ -249,13 -249,12 +249,13 @@@ struct child_process child_process; struct filter_params *params = (struct filter_params *)data; int write_err, status; - const char *argv[] = { "sh", "-c", params->cmd, NULL }; + const char *argv[] = { params->cmd, NULL }; memset(&child_process, 0, sizeof(child_process)); child_process.argv = argv; + child_process.use_shell = 1; child_process.in = -1; - child_process.out = fd; + child_process.out = out; if (start_command(&child_process)) return error("cannot fork to run external filter %s", params->cmd); @@@ -292,6 -291,7 +292,7 @@@ static int apply_filter(const char *pat memset(&async, 0, sizeof(async)); async.proc = filter_buffer; async.data = ¶ms; + async.out = -1; params.src = src; params.size = len; params.cmd = cmd; @@@ -378,9 -378,9 +379,9 @@@ static void setup_convert_check(struct static struct git_attr *attr_filter; if (!attr_crlf) { - attr_crlf = git_attr("crlf", 4); - attr_ident = git_attr("ident", 5); - attr_filter = git_attr("filter", 6); + attr_crlf = git_attr("crlf"); + attr_ident = git_attr("ident"); + attr_filter = git_attr("filter"); user_convert_tail = &user_convert; git_config(read_convert_config, NULL); } diff --combined remote-curl.c index a904164e42,6bb3366264..d388120851 --- a/remote-curl.c +++ b/remote-curl.c @@@ -184,13 -184,13 +184,13 @@@ static struct discovery* discover_refs( return last; } - static int write_discovery(int fd, void *data) + static int write_discovery(int in, int out, void *data) { struct discovery *heads = data; int err = 0; - if (write_in_full(fd, heads->buf, heads->len) != heads->len) + if (write_in_full(out, heads->buf, heads->len) != heads->len) err = 1; - close(fd); + close(out); return err; } @@@ -202,6 -202,7 +202,7 @@@ static struct ref *parse_git_refs(struc memset(&async, 0, sizeof(async)); async.proc = write_discovery; async.data = heads; + async.out = -1; if (start_async(&async)) die("cannot start thread to parse advertised refs"); @@@ -304,7 -305,6 +305,7 @@@ struct rpc_state int out; struct strbuf result; unsigned gzip_request : 1; + unsigned initial_buffer : 1; }; static size_t rpc_out(void *ptr, size_t eltsize, @@@ -315,7 -315,6 +316,7 @@@ size_t avail = rpc->len - rpc->pos; if (!avail) { + rpc->initial_buffer = 0; avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); if (!avail) return 0; @@@ -330,29 -329,6 +331,29 @@@ return avail; } +#ifndef NO_CURL_IOCTL +static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp) +{ + struct rpc_state *rpc = clientp; + + switch (cmd) { + case CURLIOCMD_NOP: + return CURLIOE_OK; + + case CURLIOCMD_RESTARTREAD: + if (rpc->initial_buffer) { + rpc->pos = 0; + return CURLIOE_OK; + } + fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n"); + return CURLIOE_FAILRESTART; + + default: + return CURLIOE_UNKNOWNCMD; + } +} +#endif + static size_t rpc_in(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_) { @@@ -409,13 -385,8 +410,13 @@@ static int post_rpc(struct rpc_state *r */ headers = curl_slist_append(headers, "Expect: 100-continue"); headers = curl_slist_append(headers, "Transfer-Encoding: chunked"); + rpc->initial_buffer = 1; curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out); curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc); +#endif if (options.verbosity > 1) { fprintf(stderr, "POST %s (chunked)\n", rpc->service_name); fflush(stderr); diff --combined run-command.c index 2feb493951,0d95340833..0cd7f02ffe --- a/run-command.c +++ b/run-command.c @@@ -8,130 -8,12 +8,130 @@@ static inline void close_pair(int fd[2] close(fd[1]); } +#ifndef WIN32 static inline void dup_devnull(int to) { int fd = open("/dev/null", O_RDWR); dup2(fd, to); close(fd); } +#endif + +static const char **prepare_shell_cmd(const char **argv) +{ + int argc, nargc = 0; + const char **nargv; + + for (argc = 0; argv[argc]; argc++) + ; /* just counting */ + /* +1 for NULL, +3 for "sh -c" plus extra $0 */ + nargv = xmalloc(sizeof(*nargv) * (argc + 1 + 3)); + + if (argc < 1) + die("BUG: shell command is empty"); + + if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) { + nargv[nargc++] = "sh"; + nargv[nargc++] = "-c"; + + if (argc < 2) + nargv[nargc++] = argv[0]; + else { + struct strbuf arg0 = STRBUF_INIT; + strbuf_addf(&arg0, "%s \"$@\"", argv[0]); + nargv[nargc++] = strbuf_detach(&arg0, NULL); + } + } + + for (argc = 0; argv[argc]; argc++) + nargv[nargc++] = argv[argc]; + nargv[nargc] = NULL; + + return nargv; +} + +#ifndef WIN32 +static int execv_shell_cmd(const char **argv) +{ + const char **nargv = prepare_shell_cmd(argv); + trace_argv_printf(nargv, "trace: exec:"); + execvp(nargv[0], (char **)nargv); + free(nargv); + return -1; +} +#endif + +#ifndef WIN32 +static int child_err = 2; +static int child_notifier = -1; + +static void notify_parent(void) +{ + write(child_notifier, "", 1); +} + +static NORETURN void die_child(const char *err, va_list params) +{ + char msg[4096]; + int len = vsnprintf(msg, sizeof(msg), err, params); + if (len > sizeof(msg)) + len = sizeof(msg); + + write(child_err, "fatal: ", 7); + write(child_err, msg, len); + write(child_err, "\n", 1); + exit(128); +} + +static inline void set_cloexec(int fd) +{ + int flags = fcntl(fd, F_GETFD); + if (flags >= 0) + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} +#endif + +static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) +{ + int status, code = -1; + pid_t waiting; + int failed_errno = 0; + + while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) + ; /* nothing */ + + if (waiting < 0) { + failed_errno = errno; + error("waitpid for %s failed: %s", argv0, strerror(errno)); + } else if (waiting != pid) { + error("waitpid is confused (%s)", argv0); + } else if (WIFSIGNALED(status)) { + code = WTERMSIG(status); + error("%s died of signal %d", argv0, code); + /* + * This return value is chosen so that code & 0xff + * mimics the exit code that a POSIX shell would report for + * a program that died from this 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; + if (!silent_exec_failure) + error("cannot run %s: %s", argv0, + strerror(ENOENT)); + } + } else { + error("waitpid is confused (%s)", argv0); + } + errno = failed_errno; + return code; +} int start_command(struct child_process *cmd) { @@@ -194,30 -76,9 +194,30 @@@ fail_pipe trace_argv_printf(cmd->argv, "trace: run_command:"); #ifndef WIN32 +{ + int notify_pipe[2]; + if (pipe(notify_pipe)) + notify_pipe[0] = notify_pipe[1] = -1; + fflush(NULL); cmd->pid = fork(); 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. + */ + if (cmd->no_stderr || need_err) { + child_err = dup(2); + set_cloexec(child_err); + } + set_die_routine(die_child); + + close(notify_pipe[0]); + set_cloexec(notify_pipe[1]); + child_notifier = notify_pipe[1]; + atexit(notify_parent); + if (cmd->no_stdin) dup_devnull(0); else if (need_in) { @@@ -233,6 -94,9 +233,9 @@@ else if (need_err) { dup2(fderr[1], 2); close_pair(fderr); + } else if (cmd->err > 1) { + dup2(cmd->err, 2); + close(cmd->err); } if (cmd->no_stdout) @@@ -258,82 -122,61 +261,84 @@@ unsetenv(*cmd->env); } } - if (cmd->preexec_cb) + if (cmd->preexec_cb) { + /* + * We cannot predict what the pre-exec callback does. + * Forgo parent notification. + */ + close(child_notifier); + child_notifier = -1; + cmd->preexec_cb(); + } if (cmd->git_cmd) { execv_git_cmd(cmd->argv); + } else if (cmd->use_shell) { + execv_shell_cmd(cmd->argv); } else { execvp(cmd->argv[0], (char *const*) cmd->argv); } - trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0], - strerror(errno)); - exit(127); + /* + * Do not check for cmd->silent_exec_failure; the parent + * process will check it when it sees this exit code. + */ + if (errno == ENOENT) + exit(127); + else + die_errno("cannot exec '%s'", cmd->argv[0]); } if (cmd->pid < 0) error("cannot fork() for %s: %s", cmd->argv[0], strerror(failed_errno = errno)); + + /* + * Wait for child's execvp. If the execvp succeeds (or if fork() + * failed), EOF is seen immediately by the parent. Otherwise, the + * child process sends a single byte. + * Note that use of this infrastructure is completely advisory, + * therefore, we keep error checks minimal. + */ + close(notify_pipe[1]); + if (read(notify_pipe[0], ¬ify_pipe[1], 1) == 1) { + /* + * At this point we know that fork() succeeded, but execvp() + * failed. Errors have been reported to our stderr. + */ + wait_or_whine(cmd->pid, cmd->argv[0], + cmd->silent_exec_failure); + failed_errno = errno; + cmd->pid = -1; + } + close(notify_pipe[0]); +} #else { - int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */ + int fhin = 0, fhout = 1, fherr = 2; const char **sargv = cmd->argv; char **env = environ; - if (cmd->no_stdin) { - s0 = dup(0); - dup_devnull(0); - } else if (need_in) { - s0 = dup(0); - dup2(fdin[0], 0); - } else if (cmd->in) { - s0 = dup(0); - dup2(cmd->in, 0); - } - - if (cmd->no_stderr) { - s2 = dup(2); - dup_devnull(2); - } else if (need_err) { - s2 = dup(2); - dup2(fderr[1], 2); - } else if (cmd->err > 2) { - s2 = dup(2); - dup2(cmd->err, 2); - } - - if (cmd->no_stdout) { - s1 = dup(1); - dup_devnull(1); - } else if (cmd->stdout_to_stderr) { - s1 = dup(1); - dup2(2, 1); - } else if (need_out) { - s1 = dup(1); - dup2(fdout[1], 1); - } else if (cmd->out > 1) { - s1 = dup(1); - dup2(cmd->out, 1); - } + if (cmd->no_stdin) + fhin = open("/dev/null", O_RDWR); + else if (need_in) + fhin = dup(fdin[0]); + else if (cmd->in) + fhin = dup(cmd->in); + + if (cmd->no_stderr) + fherr = open("/dev/null", O_RDWR); + else if (need_err) + fherr = dup(fderr[1]); ++ else if (cmd->err > 2) ++ fherr = dup(cmd->err); + + if (cmd->no_stdout) + fhout = open("/dev/null", O_RDWR); + else if (cmd->stdout_to_stderr) + fhout = dup(fherr); + else if (need_out) + fhout = dup(fdout[1]); + else if (cmd->out > 1) + fhout = dup(cmd->out); if (cmd->dir) die("chdir in start_command() not implemented"); @@@ -342,12 -185,9 +347,12 @@@ if (cmd->git_cmd) { cmd->argv = prepare_git_cmd(cmd->argv); + } else if (cmd->use_shell) { + cmd->argv = prepare_shell_cmd(cmd->argv); } - cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env); + cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env, + fhin, fhout, fherr); failed_errno = errno; if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) error("cannot spawn %s: %s", cmd->argv[0], strerror(errno)); @@@ -358,12 -198,12 +363,12 @@@ free(cmd->argv); cmd->argv = sargv; - if (s0 >= 0) - dup2(s0, 0), close(s0); - if (s1 >= 0) - dup2(s1, 1), close(s1); - if (s2 >= 0) - dup2(s2, 2), close(s2); + if (fhin != 0) + close(fhin); + if (fhout != 1) + close(fhout); + if (fherr != 2) + close(fherr); } #endif @@@ -394,10 -234,54 +399,12 @@@ if (need_err) close(fderr[1]); + else if (cmd->err) + close(cmd->err); return 0; } -static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) -{ - int status, code = -1; - pid_t waiting; - int failed_errno = 0; - - while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) - ; /* nothing */ - - if (waiting < 0) { - failed_errno = errno; - error("waitpid for %s failed: %s", argv0, strerror(errno)); - } else if (waiting != pid) { - error("waitpid is confused (%s)", argv0); - } else if (WIFSIGNALED(status)) { - code = WTERMSIG(status); - error("%s died of signal %d", argv0, code); - /* - * This return value is chosen so that code & 0xff - * mimics the exit code that a POSIX shell would report for - * a program that died from this 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; - if (!silent_exec_failure) - error("cannot run %s: %s", argv0, - strerror(ENOENT)); - } - } else { - error("waitpid is confused (%s)", argv0); - } - errno = failed_errno; - return code; -} - int finish_command(struct child_process *cmd) { return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure); @@@ -421,7 -305,6 +428,7 @@@ static void prepare_run_command_v_opt(s cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0; cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0; cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0; + cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0; } int run_command_v_opt(const char **argv, int opt) @@@ -444,17 -327,51 +451,51 @@@ int run_command_v_opt_cd_env(const cha static unsigned __stdcall run_thread(void *data) { struct async *async = data; - return async->proc(async->fd_for_proc, async->data); + return async->proc(async->proc_in, async->proc_out, async->data); } #endif int start_async(struct async *async) { - int pipe_out[2]; + int need_in, need_out; + int fdin[2], fdout[2]; + int proc_in, proc_out; - if (pipe(pipe_out) < 0) - return error("cannot create pipe: %s", strerror(errno)); - async->out = pipe_out[0]; + need_in = async->in < 0; + if (need_in) { + if (pipe(fdin) < 0) { + if (async->out > 0) + close(async->out); + return error("cannot create pipe: %s", strerror(errno)); + } + async->in = fdin[1]; + } + + need_out = async->out < 0; + if (need_out) { + if (pipe(fdout) < 0) { + if (need_in) + close_pair(fdin); + else if (async->in) + close(async->in); + return error("cannot create pipe: %s", strerror(errno)); + } + async->out = fdout[0]; + } + + if (need_in) + proc_in = fdin[0]; + else if (async->in) + proc_in = async->in; + else + proc_in = -1; + + if (need_out) + proc_out = fdout[1]; + else if (async->out) + proc_out = async->out; + else + proc_out = -1; #ifndef WIN32 /* Flush stdio before fork() to avoid cloning buffers */ @@@ -463,24 -380,47 +504,47 @@@ async->pid = fork(); if (async->pid < 0) { error("fork (async) failed: %s", strerror(errno)); - close_pair(pipe_out); - return -1; + goto error; } if (!async->pid) { - close(pipe_out[0]); - exit(!!async->proc(pipe_out[1], async->data)); + if (need_in) + close(fdin[1]); + if (need_out) + close(fdout[0]); + exit(!!async->proc(proc_in, proc_out, async->data)); } - close(pipe_out[1]); + + if (need_in) + close(fdin[0]); + else if (async->in) + close(async->in); + + if (need_out) + close(fdout[1]); + else if (async->out) + close(async->out); #else - async->fd_for_proc = pipe_out[1]; + async->proc_in = proc_in; + async->proc_out = proc_out; async->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL); if (!async->tid) { error("cannot create thread: %s", strerror(errno)); - close_pair(pipe_out); - return -1; + goto error; } #endif return 0; + + error: + if (need_in) + close_pair(fdin); + else if (async->in) + close(async->in); + + if (need_out) + close_pair(fdout); + else if (async->out) + close(async->out); + return -1; } int finish_async(struct async *async) diff --combined run-command.h index 967ba8cc09,65ccb1c60f..94619f52d9 --- a/run-command.h +++ b/run-command.h @@@ -18,7 -18,7 +18,7 @@@ struct child_process * - Specify > 0 to set a channel to a particular FD as follows: * .in: a readable FD, becomes child's stdin * .out: a writable FD, becomes child's stdout/stderr - * .err > 0 not supported + * .err: a writable FD, becomes child's stderr * The specified FD is closed by start_command(), even in case * of errors! */ @@@ -33,7 -33,6 +33,7 @@@ unsigned git_cmd:1; /* if this is to be git sub-command */ unsigned silent_exec_failure:1; unsigned stdout_to_stderr:1; + unsigned use_shell:1; void (*preexec_cb)(void); }; @@@ -47,7 -46,6 +47,7 @@@ extern int run_hook(const char *index_f #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ #define RUN_COMMAND_STDOUT_TO_STDERR 4 #define RUN_SILENT_EXEC_FAILURE 8 +#define RUN_USING_SHELL 16 int run_command_v_opt(const char **argv, int opt); /* @@@ -66,17 -64,20 +66,20 @@@ int run_command_v_opt_cd_env(const cha */ struct async { /* - * proc writes to fd and closes it; + * proc reads from in; closes it before return + * proc writes to out; closes it before return * returns 0 on success, non-zero on failure */ - int (*proc)(int fd, void *data); + int (*proc)(int in, int out, void *data); void *data; + int in; /* caller writes here and closes it */ int out; /* caller reads from here and closes it */ #ifndef WIN32 pid_t pid; #else HANDLE tid; - int fd_for_proc; + int proc_in; + int proc_out; #endif }; diff --combined t/t5401-update-hooks.sh index 325714e529,c3cf397b03..0686390d2f --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@@ -18,7 -18,6 +18,7 @@@ test_expect_success setup git update-ref refs/heads/master $commit0 && git update-ref refs/heads/tofail $commit1 && git clone ./. victim && + GIT_DIR=victim/.git git config receive.denyCurrentBranch warn && GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 && git update-ref refs/heads/master $commit1 && git update-ref refs/heads/tofail $commit0 @@@ -119,19 -118,19 +119,19 @@@ test_expect_success 'send-pack produce ' cat <expect - STDOUT pre-receive - STDERR pre-receive - STDOUT update refs/heads/master - STDERR update refs/heads/master - STDOUT update refs/heads/tofail - STDERR update refs/heads/tofail - STDOUT post-receive - STDERR post-receive - STDOUT post-update - STDERR post-update + remote: STDOUT pre-receive + remote: STDERR pre-receive + remote: STDOUT update refs/heads/master + remote: STDERR update refs/heads/master + remote: STDOUT update refs/heads/tofail + remote: STDERR update refs/heads/tofail + remote: STDOUT post-receive + remote: STDERR post-receive + remote: STDOUT post-update + remote: STDERR post-update EOF test_expect_success 'send-pack stderr contains hook messages' ' - grep ^STD send.err >actual && + grep ^remote: send.err | sed "s/ *\$//" >actual && test_cmp - actual