serve.con commit serve: introduce git-serve (ed10cb9)
   1#include "cache.h"
   2#include "repository.h"
   3#include "config.h"
   4#include "pkt-line.h"
   5#include "version.h"
   6#include "argv-array.h"
   7#include "serve.h"
   8
   9static int agent_advertise(struct repository *r,
  10                           struct strbuf *value)
  11{
  12        if (value)
  13                strbuf_addstr(value, git_user_agent_sanitized());
  14        return 1;
  15}
  16
  17struct protocol_capability {
  18        /*
  19         * The name of the capability.  The server uses this name when
  20         * advertising this capability, and the client uses this name to
  21         * specify this capability.
  22         */
  23        const char *name;
  24
  25        /*
  26         * Function queried to see if a capability should be advertised.
  27         * Optionally a value can be specified by adding it to 'value'.
  28         * If a value is added to 'value', the server will advertise this
  29         * capability as "<name>=<value>" instead of "<name>".
  30         */
  31        int (*advertise)(struct repository *r, struct strbuf *value);
  32
  33        /*
  34         * Function called when a client requests the capability as a command.
  35         * The function will be provided the capabilities requested via 'keys'
  36         * as well as a struct packet_reader 'request' which the command should
  37         * use to read the command specific part of the request.  Every command
  38         * MUST read until a flush packet is seen before sending a response.
  39         *
  40         * This field should be NULL for capabilities which are not commands.
  41         */
  42        int (*command)(struct repository *r,
  43                       struct argv_array *keys,
  44                       struct packet_reader *request);
  45};
  46
  47static struct protocol_capability capabilities[] = {
  48        { "agent", agent_advertise, NULL },
  49};
  50
  51static void advertise_capabilities(void)
  52{
  53        struct strbuf capability = STRBUF_INIT;
  54        struct strbuf value = STRBUF_INIT;
  55        int i;
  56
  57        for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
  58                struct protocol_capability *c = &capabilities[i];
  59
  60                if (c->advertise(the_repository, &value)) {
  61                        strbuf_addstr(&capability, c->name);
  62
  63                        if (value.len) {
  64                                strbuf_addch(&capability, '=');
  65                                strbuf_addbuf(&capability, &value);
  66                        }
  67
  68                        strbuf_addch(&capability, '\n');
  69                        packet_write(1, capability.buf, capability.len);
  70                }
  71
  72                strbuf_reset(&capability);
  73                strbuf_reset(&value);
  74        }
  75
  76        packet_flush(1);
  77        strbuf_release(&capability);
  78        strbuf_release(&value);
  79}
  80
  81static struct protocol_capability *get_capability(const char *key)
  82{
  83        int i;
  84
  85        if (!key)
  86                return NULL;
  87
  88        for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
  89                struct protocol_capability *c = &capabilities[i];
  90                const char *out;
  91                if (skip_prefix(key, c->name, &out) && (!*out || *out == '='))
  92                        return c;
  93        }
  94
  95        return NULL;
  96}
  97
  98static int is_valid_capability(const char *key)
  99{
 100        const struct protocol_capability *c = get_capability(key);
 101
 102        return c && c->advertise(the_repository, NULL);
 103}
 104
 105static int is_command(const char *key, struct protocol_capability **command)
 106{
 107        const char *out;
 108
 109        if (skip_prefix(key, "command=", &out)) {
 110                struct protocol_capability *cmd = get_capability(out);
 111
 112                if (*command)
 113                        die("command '%s' requested after already requesting command '%s'",
 114                            out, (*command)->name);
 115                if (!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command)
 116                        die("invalid command '%s'", out);
 117
 118                *command = cmd;
 119                return 1;
 120        }
 121
 122        return 0;
 123}
 124
 125int has_capability(const struct argv_array *keys, const char *capability,
 126                   const char **value)
 127{
 128        int i;
 129        for (i = 0; i < keys->argc; i++) {
 130                const char *out;
 131                if (skip_prefix(keys->argv[i], capability, &out) &&
 132                    (!*out || *out == '=')) {
 133                        if (value) {
 134                                if (*out == '=')
 135                                        out++;
 136                                *value = out;
 137                        }
 138                        return 1;
 139                }
 140        }
 141
 142        return 0;
 143}
 144
 145enum request_state {
 146        PROCESS_REQUEST_KEYS,
 147        PROCESS_REQUEST_DONE,
 148};
 149
 150static int process_request(void)
 151{
 152        enum request_state state = PROCESS_REQUEST_KEYS;
 153        struct packet_reader reader;
 154        struct argv_array keys = ARGV_ARRAY_INIT;
 155        struct protocol_capability *command = NULL;
 156
 157        packet_reader_init(&reader, 0, NULL, 0,
 158                           PACKET_READ_CHOMP_NEWLINE |
 159                           PACKET_READ_GENTLE_ON_EOF);
 160
 161        /*
 162         * Check to see if the client closed their end before sending another
 163         * request.  If so we can terminate the connection.
 164         */
 165        if (packet_reader_peek(&reader) == PACKET_READ_EOF)
 166                return 1;
 167        reader.options = PACKET_READ_CHOMP_NEWLINE;
 168
 169        while (state != PROCESS_REQUEST_DONE) {
 170                switch (packet_reader_peek(&reader)) {
 171                case PACKET_READ_EOF:
 172                        BUG("Should have already died when seeing EOF");
 173                case PACKET_READ_NORMAL:
 174                        /* collect request; a sequence of keys and values */
 175                        if (is_command(reader.line, &command) ||
 176                            is_valid_capability(reader.line))
 177                                argv_array_push(&keys, reader.line);
 178                        else
 179                                die("unknown capability '%s'", reader.line);
 180
 181                        /* Consume the peeked line */
 182                        packet_reader_read(&reader);
 183                        break;
 184                case PACKET_READ_FLUSH:
 185                        /*
 186                         * If no command and no keys were given then the client
 187                         * wanted to terminate the connection.
 188                         */
 189                        if (!keys.argc)
 190                                return 1;
 191
 192                        /*
 193                         * The flush packet isn't consume here like it is in
 194                         * the other parts of this switch statement.  This is
 195                         * so that the command can read the flush packet and
 196                         * see the end of the request in the same way it would
 197                         * if command specific arguments were provided after a
 198                         * delim packet.
 199                         */
 200                        state = PROCESS_REQUEST_DONE;
 201                        break;
 202                case PACKET_READ_DELIM:
 203                        /* Consume the peeked line */
 204                        packet_reader_read(&reader);
 205
 206                        state = PROCESS_REQUEST_DONE;
 207                        break;
 208                }
 209        }
 210
 211        if (!command)
 212                die("no command requested");
 213
 214        command->command(the_repository, &keys, &reader);
 215
 216        argv_array_clear(&keys);
 217        return 0;
 218}
 219
 220/* Main serve loop for protocol version 2 */
 221void serve(struct serve_options *options)
 222{
 223        if (options->advertise_capabilities || !options->stateless_rpc) {
 224                /* serve by default supports v2 */
 225                packet_write_fmt(1, "version 2\n");
 226
 227                advertise_capabilities();
 228                /*
 229                 * If only the list of capabilities was requested exit
 230                 * immediately after advertising capabilities
 231                 */
 232                if (options->advertise_capabilities)
 233                        return;
 234        }
 235
 236        /*
 237         * If stateless-rpc was requested then exit after
 238         * a single request/response exchange
 239         */
 240        if (options->stateless_rpc) {
 241                process_request();
 242        } else {
 243                for (;;)
 244                        if (process_request())
 245                                break;
 246        }
 247}