upload-pack.con commit git-daemon: timeout, eliminate double DWIM (b7080d8)
   1#include "cache.h"
   2#include "refs.h"
   3#include "pkt-line.h"
   4
   5static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
   6
   7#define MAX_HAS (16)
   8#define MAX_NEEDS (256)
   9static int nr_has = 0, nr_needs = 0;
  10static unsigned char has_sha1[MAX_HAS][20];
  11static unsigned char needs_sha1[MAX_NEEDS][20];
  12static unsigned int timeout = 0;
  13
  14static void reset_timeout(void)
  15{
  16        alarm(timeout);
  17}
  18
  19static int strip(char *line, int len)
  20{
  21        if (len && line[len-1] == '\n')
  22                line[--len] = 0;
  23        return len;
  24}
  25
  26static void create_pack_file(void)
  27{
  28        int fd[2];
  29        pid_t pid;
  30
  31        if (pipe(fd) < 0)
  32                die("git-upload-pack: unable to create pipe");
  33        pid = fork();
  34        if (pid < 0)
  35                die("git-upload-pack: unable to fork git-rev-list");
  36
  37        if (!pid) {
  38                int i;
  39                int args;
  40                char **argv;
  41                char *buf;
  42                char **p;
  43
  44                if (MAX_NEEDS <= nr_needs)
  45                        args = nr_has + 10;
  46                else
  47                        args = nr_has + nr_needs + 5;
  48                argv = xmalloc(args * sizeof(char *));
  49                buf = xmalloc(args * 45);
  50                p = argv;
  51
  52                dup2(fd[1], 1);
  53                close(0);
  54                close(fd[0]);
  55                close(fd[1]);
  56                *p++ = "git-rev-list";
  57                *p++ = "--objects";
  58                if (MAX_NEEDS <= nr_needs)
  59                        *p++ = "--all";
  60                else {
  61                        for (i = 0; i < nr_needs; i++) {
  62                                *p++ = buf;
  63                                memcpy(buf, sha1_to_hex(needs_sha1[i]), 41);
  64                                buf += 41;
  65                        }
  66                }
  67                for (i = 0; i < nr_has; i++) {
  68                        *p++ = buf;
  69                        *buf++ = '^';
  70                        memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
  71                        buf += 41;
  72                }
  73                *p++ = NULL;
  74                execvp("git-rev-list", argv);
  75                die("git-upload-pack: unable to exec git-rev-list");
  76        }
  77        dup2(fd[0], 0);
  78        close(fd[0]);
  79        close(fd[1]);
  80        execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);
  81        die("git-upload-pack: unable to exec git-pack-objects");
  82}
  83
  84static int got_sha1(char *hex, unsigned char *sha1)
  85{
  86        int nr;
  87        if (get_sha1_hex(hex, sha1))
  88                die("git-upload-pack: expected SHA1 object, got '%s'", hex);
  89        if (!has_sha1_file(sha1))
  90                return 0;
  91        nr = nr_has;
  92        if (nr < MAX_HAS) {
  93                memcpy(has_sha1[nr], sha1, 20);
  94                nr_has = nr+1;
  95        }
  96        return 1;
  97}
  98
  99static int get_common_commits(void)
 100{
 101        static char line[1000];
 102        unsigned char sha1[20];
 103        int len;
 104
 105        for(;;) {
 106                len = packet_read_line(0, line, sizeof(line));
 107                reset_timeout();
 108
 109                if (!len) {
 110                        packet_write(1, "NAK\n");
 111                        continue;
 112                }
 113                len = strip(line, len);
 114                if (!strncmp(line, "have ", 5)) {
 115                        if (got_sha1(line+5, sha1)) {
 116                                packet_write(1, "ACK %s\n", sha1_to_hex(sha1));
 117                                break;
 118                        }
 119                        continue;
 120                }
 121                if (!strcmp(line, "done")) {
 122                        packet_write(1, "NAK\n");
 123                        return -1;
 124                }
 125                die("git-upload-pack: expected SHA1 list, got '%s'", line);
 126        }
 127
 128        for (;;) {
 129                len = packet_read_line(0, line, sizeof(line));
 130                reset_timeout();
 131                if (!len)
 132                        continue;
 133                len = strip(line, len);
 134                if (!strncmp(line, "have ", 5)) {
 135                        got_sha1(line+5, sha1);
 136                        continue;
 137                }
 138                if (!strcmp(line, "done"))
 139                        break;
 140                die("git-upload-pack: expected SHA1 list, got '%s'", line);
 141        }
 142        return 0;
 143}
 144
 145static int receive_needs(void)
 146{
 147        static char line[1000];
 148        int len, needs;
 149
 150        needs = 0;
 151        for (;;) {
 152                unsigned char dummy[20], *sha1_buf;
 153                len = packet_read_line(0, line, sizeof(line));
 154                reset_timeout();
 155                if (!len)
 156                        return needs;
 157
 158                sha1_buf = dummy;
 159                if (needs == MAX_NEEDS) {
 160                        fprintf(stderr,
 161                                "warning: supporting only a max of %d requests. "
 162                                "sending everything instead.\n",
 163                                MAX_NEEDS);
 164                }
 165                else if (needs < MAX_NEEDS)
 166                        sha1_buf = needs_sha1[needs];
 167
 168                if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf))
 169                        die("git-upload-pack: protocol error, "
 170                            "expected to get sha, not '%s'", line);
 171                needs++;
 172        }
 173}
 174
 175static int send_ref(const char *refname, const unsigned char *sha1)
 176{
 177        packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
 178        return 0;
 179}
 180
 181static int upload_pack(void)
 182{
 183        reset_timeout();
 184        head_ref(send_ref);
 185        for_each_ref(send_ref);
 186        packet_flush(1);
 187        nr_needs = receive_needs();
 188        if (!nr_needs)
 189                return 0;
 190        get_common_commits();
 191        create_pack_file();
 192        return 0;
 193}
 194
 195int main(int argc, char **argv)
 196{
 197        const char *dir;
 198        int i;
 199        int strict = 0;
 200
 201        for (i = 1; i < argc; i++) {
 202                char *arg = argv[i];
 203
 204                if (arg[0] != '-')
 205                        break;
 206                if (!strcmp(arg, "--strict")) {
 207                        strict = 1;
 208                        continue;
 209                }
 210                if (!strncmp(arg, "--timeout=", 10)) {
 211                        timeout = atoi(arg+10);
 212                        continue;
 213                }
 214                if (!strcmp(arg, "--")) {
 215                        i++;
 216                        break;
 217                }
 218        }
 219        
 220        if (i != argc-1)
 221                usage(upload_pack_usage);
 222        dir = argv[i];
 223
 224        /* chdir to the directory. If that fails, try appending ".git" */
 225        if (chdir(dir) < 0) {
 226                if (strict || chdir(mkpath("%s.git", dir)) < 0)
 227                        die("git-upload-pack unable to chdir to %s", dir);
 228        }
 229        if (!strict)
 230                chdir(".git");
 231
 232        if (access("objects", X_OK) || access("refs", X_OK))
 233                die("git-upload-pack: %s doesn't seem to be a git archive", dir);
 234
 235        putenv("GIT_DIR=.");
 236        upload_pack();
 237        return 0;
 238}