/* * Generic implementation of background process infrastructure. */ #include "git-compat-util.h" #include "sub-process.h" #include "sigchain.h" #include "pkt-line.h" #include "quote.h" int cmd2process_cmp(const void *cmp_data UNUSED, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, const void *keydata UNUSED) { const struct subprocess_entry *e1, *e2; e1 = container_of(eptr, const struct subprocess_entry, ent); e2 = container_of(entry_or_key, const struct subprocess_entry, ent); return strcmp(e1->cmd, e2->cmd); } struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd) { struct subprocess_entry key; hashmap_entry_init(&key.ent, strhash(cmd)); key.cmd = cmd; return hashmap_get_entry(hashmap, &key, ent, NULL); } int subprocess_read_status(int fd, struct strbuf *status) { struct strbuf **pair; char *line; int len; for (;;) { len = packet_read_line_gently(fd, NULL, &line); if ((len < 0) || !line) break; pair = strbuf_split_str(line, '=', 2); if (pair[0] && pair[0]->len && pair[1]) { /* the last "status=" line wins */ if (!strcmp(pair[0]->buf, "status=")) { strbuf_reset(status); strbuf_addbuf(status, pair[1]); } } strbuf_list_free(pair); } return (len < 0) ? len : 0; } void subprocess_stop(struct hashmap *hashmap, struct subprocess_entry *entry) { if (!entry) return; entry->process.clean_on_exit = 0; kill(entry->process.pid, SIGTERM); finish_command(&entry->process); hashmap_remove(hashmap, &entry->ent, NULL); } 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); } int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd, subprocess_start_fn startfn) { int err; struct child_process *process; // BUGBUG most callers to subprocess_start() pass in "cmd" the value // BUGBUG of find_hook() which returns a static buffer (that's only // BUGBUG good until the next call to find_hook()). // BUGFIX Defer assignment until we copy the string in our argv. // entry->cmd = cmd; process = &entry->process; child_process_init(process); strvec_push(&process->args, cmd); process->use_shell = 1; process->in = -1; process->out = -1; process->clean_on_exit = 1; process->clean_on_exit_handler = subprocess_exit_handler; process->trace2_child_class = "subprocess"; entry->cmd = process->args.v[0]; err = start_command(process); if (err) { error("cannot fork to run subprocess '%s'", cmd); return err; } hashmap_entry_init(&entry->ent, strhash(cmd)); err = startfn(entry); if (err) { error("initialization for subprocess '%s' failed", cmd); subprocess_stop(hashmap, entry); return err; } hashmap_add(hashmap, &entry->ent); return 0; } int subprocess_start_strvec(struct hashmap *hashmap, struct subprocess_entry *entry, int is_git_cmd, const struct strvec *argv, subprocess_start_fn startfn) { int err; int k; struct child_process *process; struct strbuf quoted = STRBUF_INIT; process = &entry->process; child_process_init(process); for (k = 0; k < argv->nr; k++) strvec_push(&process->args, argv->v[k]); process->use_shell = 1; process->in = -1; process->out = -1; process->git_cmd = is_git_cmd; process->clean_on_exit = 1; process->clean_on_exit_handler = subprocess_exit_handler; process->trace2_child_class = "subprocess"; sq_quote_argv_pretty("ed, argv->v); entry->cmd = strbuf_detach("ed, NULL); err = start_command(process); if (err) { error("cannot fork to run subprocess '%s'", entry->cmd); return err; } hashmap_entry_init(&entry->ent, strhash(entry->cmd)); err = startfn(entry); if (err) { error("initialization for subprocess '%s' failed", entry->cmd); subprocess_stop(hashmap, entry); return err; } hashmap_add(hashmap, &entry->ent); return 0; } static int handshake_version(struct child_process *process, const char *welcome_prefix, int *versions, int *chosen_version) { int version_scratch; int i; char *line; const char *p; if (!chosen_version) chosen_version = &version_scratch; if (packet_write_fmt_gently(process->in, "%s-client\n", welcome_prefix)) return error("Could not write client identification"); for (i = 0; versions[i]; i++) { if (packet_write_fmt_gently(process->in, "version=%d\n", versions[i])) return error("Could not write requested version"); } if (packet_flush_gently(process->in)) return error("Could not write flush packet"); if (!(line = packet_read_line(process->out, NULL)) || !skip_prefix(line, welcome_prefix, &p) || strcmp(p, "-server")) return error("Unexpected line '%s', expected %s-server", line ? line : "", welcome_prefix); if (!(line = packet_read_line(process->out, NULL)) || !skip_prefix(line, "version=", &p) || strtol_i(p, 10, chosen_version)) return error("Unexpected line '%s', expected version", line ? line : ""); if ((line = packet_read_line(process->out, NULL))) return error("Unexpected line '%s', expected flush", line); /* Check to make sure that the version received is supported */ for (i = 0; versions[i]; i++) { if (versions[i] == *chosen_version) break; } if (!versions[i]) return error("Version %d not supported", *chosen_version); return 0; } static int handshake_capabilities(struct child_process *process, struct subprocess_capability *capabilities, unsigned int *supported_capabilities) { int i; char *line; for (i = 0; capabilities[i].name; i++) { if (packet_write_fmt_gently(process->in, "capability=%s\n", capabilities[i].name)) return error("Could not write requested capability"); } if (packet_flush_gently(process->in)) return error("Could not write flush packet"); while ((line = packet_read_line(process->out, NULL))) { const char *p; if (!skip_prefix(line, "capability=", &p)) continue; for (i = 0; capabilities[i].name && strcmp(p, capabilities[i].name); i++) ; if (capabilities[i].name) { if (supported_capabilities) *supported_capabilities |= capabilities[i].flag; } else { die("subprocess '%s' requested unsupported capability '%s'", process->args.v[0], p); } } return 0; } 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; }