|  | /* | 
|  | * Generic implementation of background process infrastructure. | 
|  | */ | 
|  | #include "git-compat-util.h" | 
|  | #include "sub-process.h" | 
|  | #include "sigchain.h" | 
|  | #include "pkt-line.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=<foo>" 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; | 
|  |  | 
|  | 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"; | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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 : "<flush packet>", 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 : "<flush packet>"); | 
|  | 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; | 
|  | } |