sub-process.con commit p5551: add a script to test fetch pack-dir rescans (7893bf1)
   1/*
   2 * Generic implementation of background process infrastructure.
   3 */
   4#include "sub-process.h"
   5#include "sigchain.h"
   6#include "pkt-line.h"
   7
   8int cmd2process_cmp(const void *unused_cmp_data,
   9                    const struct subprocess_entry *e1,
  10                    const struct subprocess_entry *e2,
  11                    const void *unused_keydata)
  12{
  13        return strcmp(e1->cmd, e2->cmd);
  14}
  15
  16struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd)
  17{
  18        struct subprocess_entry key;
  19
  20        hashmap_entry_init(&key, strhash(cmd));
  21        key.cmd = cmd;
  22        return hashmap_get(hashmap, &key, NULL);
  23}
  24
  25int subprocess_read_status(int fd, struct strbuf *status)
  26{
  27        struct strbuf **pair;
  28        char *line;
  29        int len;
  30
  31        for (;;) {
  32                len = packet_read_line_gently(fd, NULL, &line);
  33                if ((len < 0) || !line)
  34                        break;
  35                pair = strbuf_split_str(line, '=', 2);
  36                if (pair[0] && pair[0]->len && pair[1]) {
  37                        /* the last "status=<foo>" line wins */
  38                        if (!strcmp(pair[0]->buf, "status=")) {
  39                                strbuf_reset(status);
  40                                strbuf_addbuf(status, pair[1]);
  41                        }
  42                }
  43                strbuf_list_free(pair);
  44        }
  45
  46        return (len < 0) ? len : 0;
  47}
  48
  49void subprocess_stop(struct hashmap *hashmap, struct subprocess_entry *entry)
  50{
  51        if (!entry)
  52                return;
  53
  54        entry->process.clean_on_exit = 0;
  55        kill(entry->process.pid, SIGTERM);
  56        finish_command(&entry->process);
  57
  58        hashmap_remove(hashmap, entry, NULL);
  59}
  60
  61static void subprocess_exit_handler(struct child_process *process)
  62{
  63        sigchain_push(SIGPIPE, SIG_IGN);
  64        /* Closing the pipe signals the subprocess to initiate a shutdown. */
  65        close(process->in);
  66        close(process->out);
  67        sigchain_pop(SIGPIPE);
  68        /* Finish command will wait until the shutdown is complete. */
  69        finish_command(process);
  70}
  71
  72int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd,
  73        subprocess_start_fn startfn)
  74{
  75        int err;
  76        struct child_process *process;
  77
  78        entry->cmd = cmd;
  79        process = &entry->process;
  80
  81        child_process_init(process);
  82        argv_array_push(&process->args, cmd);
  83        process->use_shell = 1;
  84        process->in = -1;
  85        process->out = -1;
  86        process->clean_on_exit = 1;
  87        process->clean_on_exit_handler = subprocess_exit_handler;
  88
  89        err = start_command(process);
  90        if (err) {
  91                error("cannot fork to run subprocess '%s'", cmd);
  92                return err;
  93        }
  94
  95        hashmap_entry_init(entry, strhash(cmd));
  96
  97        err = startfn(entry);
  98        if (err) {
  99                error("initialization for subprocess '%s' failed", cmd);
 100                subprocess_stop(hashmap, entry);
 101                return err;
 102        }
 103
 104        hashmap_add(hashmap, entry);
 105        return 0;
 106}
 107
 108static int handshake_version(struct child_process *process,
 109                             const char *welcome_prefix, int *versions,
 110                             int *chosen_version)
 111{
 112        int version_scratch;
 113        int i;
 114        char *line;
 115        const char *p;
 116
 117        if (!chosen_version)
 118                chosen_version = &version_scratch;
 119
 120        if (packet_write_fmt_gently(process->in, "%s-client\n",
 121                                    welcome_prefix))
 122                return error("Could not write client identification");
 123        for (i = 0; versions[i]; i++) {
 124                if (packet_write_fmt_gently(process->in, "version=%d\n",
 125                                            versions[i]))
 126                        return error("Could not write requested version");
 127        }
 128        if (packet_flush_gently(process->in))
 129                return error("Could not write flush packet");
 130
 131        if (!(line = packet_read_line(process->out, NULL)) ||
 132            !skip_prefix(line, welcome_prefix, &p) ||
 133            strcmp(p, "-server"))
 134                return error("Unexpected line '%s', expected %s-server",
 135                             line ? line : "<flush packet>", welcome_prefix);
 136        if (!(line = packet_read_line(process->out, NULL)) ||
 137            !skip_prefix(line, "version=", &p) ||
 138            strtol_i(p, 10, chosen_version))
 139                return error("Unexpected line '%s', expected version",
 140                             line ? line : "<flush packet>");
 141        if ((line = packet_read_line(process->out, NULL)))
 142                return error("Unexpected line '%s', expected flush", line);
 143
 144        /* Check to make sure that the version received is supported */
 145        for (i = 0; versions[i]; i++) {
 146                if (versions[i] == *chosen_version)
 147                        break;
 148        }
 149        if (!versions[i])
 150                return error("Version %d not supported", *chosen_version);
 151
 152        return 0;
 153}
 154
 155static int handshake_capabilities(struct child_process *process,
 156                                  struct subprocess_capability *capabilities,
 157                                  unsigned int *supported_capabilities)
 158{
 159        int i;
 160        char *line;
 161
 162        for (i = 0; capabilities[i].name; i++) {
 163                if (packet_write_fmt_gently(process->in, "capability=%s\n",
 164                                            capabilities[i].name))
 165                        return error("Could not write requested capability");
 166        }
 167        if (packet_flush_gently(process->in))
 168                return error("Could not write flush packet");
 169
 170        while ((line = packet_read_line(process->out, NULL))) {
 171                const char *p;
 172                if (!skip_prefix(line, "capability=", &p))
 173                        continue;
 174
 175                for (i = 0;
 176                     capabilities[i].name && strcmp(p, capabilities[i].name);
 177                     i++)
 178                        ;
 179                if (capabilities[i].name) {
 180                        if (supported_capabilities)
 181                                *supported_capabilities |= capabilities[i].flag;
 182                } else {
 183                        die("subprocess '%s' requested unsupported capability '%s'",
 184                            process->argv[0], p);
 185                }
 186        }
 187
 188        return 0;
 189}
 190
 191int subprocess_handshake(struct subprocess_entry *entry,
 192                         const char *welcome_prefix,
 193                         int *versions,
 194                         int *chosen_version,
 195                         struct subprocess_capability *capabilities,
 196                         unsigned int *supported_capabilities)
 197{
 198        int retval;
 199        struct child_process *process = &entry->process;
 200
 201        sigchain_push(SIGPIPE, SIG_IGN);
 202
 203        retval = handshake_version(process, welcome_prefix, versions,
 204                                   chosen_version) ||
 205                 handshake_capabilities(process, capabilities,
 206                                        supported_capabilities);
 207
 208        sigchain_pop(SIGPIPE);
 209        return retval;
 210}