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