http-fetch.con commit Merge branch 'hold/rpm' (68afd5f)
   1#include "cache.h"
   2#include "commit.h"
   3#include "pack.h"
   4#include "fetch.h"
   5
   6#include <curl/curl.h>
   7#include <curl/easy.h>
   8
   9#if LIBCURL_VERSION_NUM >= 0x070908
  10#define USE_CURL_MULTI
  11#define DEFAULT_MAX_REQUESTS 5
  12#endif
  13
  14#if LIBCURL_VERSION_NUM < 0x070704
  15#define curl_global_cleanup() do { /* nothing */ } while(0)
  16#endif
  17#if LIBCURL_VERSION_NUM < 0x070800
  18#define curl_global_init(a) do { /* nothing */ } while(0)
  19#endif
  20
  21#if LIBCURL_VERSION_NUM < 0x070c04
  22#define NO_CURL_EASY_DUPHANDLE
  23#endif
  24
  25#define PREV_BUF_SIZE 4096
  26#define RANGE_HEADER_SIZE 30
  27
  28static int got_alternates = 0;
  29static int active_requests = 0;
  30static int data_received;
  31
  32#ifdef USE_CURL_MULTI
  33static int max_requests = -1;
  34static CURLM *curlm;
  35#endif
  36#ifndef NO_CURL_EASY_DUPHANDLE
  37static CURL *curl_default;
  38#endif
  39static struct curl_slist *pragma_header;
  40static struct curl_slist *no_pragma_header;
  41static struct curl_slist *no_range_header;
  42static char curl_errorstr[CURL_ERROR_SIZE];
  43
  44struct alt_base
  45{
  46        char *base;
  47        int got_indices;
  48        struct packed_git *packs;
  49        struct alt_base *next;
  50};
  51
  52static struct alt_base *alt = NULL;
  53
  54enum transfer_state {
  55        WAITING,
  56        ABORTED,
  57        ACTIVE,
  58        COMPLETE,
  59};
  60
  61struct transfer_request
  62{
  63        unsigned char sha1[20];
  64        struct alt_base *repo;
  65        char *url;
  66        char filename[PATH_MAX];
  67        char tmpfile[PATH_MAX];
  68        int local;
  69        enum transfer_state state;
  70        CURLcode curl_result;
  71        char errorstr[CURL_ERROR_SIZE];
  72        long http_code;
  73        unsigned char real_sha1[20];
  74        SHA_CTX c;
  75        z_stream stream;
  76        int zret;
  77        int rename;
  78        struct active_request_slot *slot;
  79        struct transfer_request *next;
  80};
  81
  82struct active_request_slot
  83{
  84        CURL *curl;
  85        FILE *local;
  86        int in_use;
  87        int done;
  88        CURLcode curl_result;
  89        long http_code;
  90        struct active_request_slot *next;
  91};
  92
  93static struct transfer_request *request_queue_head = NULL;
  94static struct active_request_slot *active_queue_head = NULL;
  95
  96static int curl_ssl_verify = -1;
  97static char *ssl_cert = NULL;
  98#if LIBCURL_VERSION_NUM >= 0x070902
  99static char *ssl_key = NULL;
 100#endif
 101#if LIBCURL_VERSION_NUM >= 0x070908
 102static char *ssl_capath = NULL;
 103#endif
 104static char *ssl_cainfo = NULL;
 105static long curl_low_speed_limit = -1;
 106static long curl_low_speed_time = -1;
 107
 108struct buffer
 109{
 110        size_t posn;
 111        size_t size;
 112        void *buffer;
 113};
 114
 115static int http_options(const char *var, const char *value)
 116{
 117        if (!strcmp("http.sslverify", var)) {
 118                if (curl_ssl_verify == -1) {
 119                        curl_ssl_verify = git_config_bool(var, value);
 120                }
 121                return 0;
 122        }
 123
 124        if (!strcmp("http.sslcert", var)) {
 125                if (ssl_cert == NULL) {
 126                        ssl_cert = xmalloc(strlen(value)+1);
 127                        strcpy(ssl_cert, value);
 128                }
 129                return 0;
 130        }
 131#if LIBCURL_VERSION_NUM >= 0x070902
 132        if (!strcmp("http.sslkey", var)) {
 133                if (ssl_key == NULL) {
 134                        ssl_key = xmalloc(strlen(value)+1);
 135                        strcpy(ssl_key, value);
 136                }
 137                return 0;
 138        }
 139#endif
 140#if LIBCURL_VERSION_NUM >= 0x070908
 141        if (!strcmp("http.sslcapath", var)) {
 142                if (ssl_capath == NULL) {
 143                        ssl_capath = xmalloc(strlen(value)+1);
 144                        strcpy(ssl_capath, value);
 145                }
 146                return 0;
 147        }
 148#endif
 149        if (!strcmp("http.sslcainfo", var)) {
 150                if (ssl_cainfo == NULL) {
 151                        ssl_cainfo = xmalloc(strlen(value)+1);
 152                        strcpy(ssl_cainfo, value);
 153                }
 154                return 0;
 155        }
 156
 157#ifdef USE_CURL_MULTI   
 158        if (!strcmp("http.maxrequests", var)) {
 159                if (max_requests == -1)
 160                        max_requests = git_config_int(var, value);
 161                return 0;
 162        }
 163#endif
 164
 165        if (!strcmp("http.lowspeedlimit", var)) {
 166                if (curl_low_speed_limit == -1)
 167                        curl_low_speed_limit = (long)git_config_int(var, value);
 168                return 0;
 169        }
 170        if (!strcmp("http.lowspeedtime", var)) {
 171                if (curl_low_speed_time == -1)
 172                        curl_low_speed_time = (long)git_config_int(var, value);
 173                return 0;
 174        }
 175
 176        /* Fall back on the default ones */
 177        return git_default_config(var, value);
 178}
 179
 180static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
 181                            struct buffer *buffer)
 182{
 183        size_t size = eltsize * nmemb;
 184        if (size > buffer->size - buffer->posn)
 185                size = buffer->size - buffer->posn;
 186        memcpy(buffer->buffer + buffer->posn, ptr, size);
 187        buffer->posn += size;
 188        data_received++;
 189        return size;
 190}
 191
 192static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
 193                                    size_t nmemb, struct buffer *buffer)
 194{
 195        size_t size = eltsize * nmemb;
 196        if (size > buffer->size - buffer->posn) {
 197                buffer->size = buffer->size * 3 / 2;
 198                if (buffer->size < buffer->posn + size)
 199                        buffer->size = buffer->posn + size;
 200                buffer->buffer = xrealloc(buffer->buffer, buffer->size);
 201        }
 202        memcpy(buffer->buffer + buffer->posn, ptr, size);
 203        buffer->posn += size;
 204        data_received++;
 205        return size;
 206}
 207
 208static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
 209                               void *data)
 210{
 211        unsigned char expn[4096];
 212        size_t size = eltsize * nmemb;
 213        int posn = 0;
 214        struct transfer_request *request = (struct transfer_request *)data;
 215        do {
 216                ssize_t retval = write(request->local,
 217                                       ptr + posn, size - posn);
 218                if (retval < 0)
 219                        return posn;
 220                posn += retval;
 221        } while (posn < size);
 222
 223        request->stream.avail_in = size;
 224        request->stream.next_in = ptr;
 225        do {
 226                request->stream.next_out = expn;
 227                request->stream.avail_out = sizeof(expn);
 228                request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
 229                SHA1_Update(&request->c, expn,
 230                            sizeof(expn) - request->stream.avail_out);
 231        } while (request->stream.avail_in && request->zret == Z_OK);
 232        data_received++;
 233        return size;
 234}
 235
 236#ifdef USE_CURL_MULTI
 237static void process_curl_messages(void);
 238static void process_request_queue(void);
 239#endif
 240static int fetch_alternates(char *base);
 241
 242static CURL* get_curl_handle(void)
 243{
 244        CURL* result = curl_easy_init();
 245
 246        curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
 247#if LIBCURL_VERSION_NUM >= 0x070907
 248        curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
 249#endif
 250
 251        if (ssl_cert != NULL)
 252                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
 253#if LIBCURL_VERSION_NUM >= 0x070902
 254        if (ssl_key != NULL)
 255                curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
 256#endif
 257#if LIBCURL_VERSION_NUM >= 0x070908
 258        if (ssl_capath != NULL)
 259                curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
 260#endif
 261        if (ssl_cainfo != NULL)
 262                curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
 263        curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
 264
 265        if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
 266                curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
 267                                 curl_low_speed_limit);
 268                curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
 269                                 curl_low_speed_time);
 270        }
 271
 272        return result;
 273}
 274
 275static struct active_request_slot *get_active_slot(void)
 276{
 277        struct active_request_slot *slot = active_queue_head;
 278        struct active_request_slot *newslot;
 279
 280#ifdef USE_CURL_MULTI
 281        int num_transfers;
 282
 283        /* Wait for a slot to open up if the queue is full */
 284        while (active_requests >= max_requests) {
 285                curl_multi_perform(curlm, &num_transfers);
 286                if (num_transfers < active_requests) {
 287                        process_curl_messages();
 288                }
 289        }
 290#endif
 291
 292        while (slot != NULL && slot->in_use) {
 293                slot = slot->next;
 294        }
 295        if (slot == NULL) {
 296                newslot = xmalloc(sizeof(*newslot));
 297                newslot->curl = NULL;
 298                newslot->in_use = 0;
 299                newslot->next = NULL;
 300
 301                slot = active_queue_head;
 302                if (slot == NULL) {
 303                        active_queue_head = newslot;
 304                } else {
 305                        while (slot->next != NULL) {
 306                                slot = slot->next;
 307                        }
 308                        slot->next = newslot;
 309                }
 310                slot = newslot;
 311        }
 312
 313        if (slot->curl == NULL) {
 314#ifdef NO_CURL_EASY_DUPHANDLE
 315                slot->curl = get_curl_handle();
 316#else
 317                slot->curl = curl_easy_duphandle(curl_default);
 318#endif
 319        }
 320
 321        active_requests++;
 322        slot->in_use = 1;
 323        slot->done = 0;
 324        slot->local = NULL;
 325        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
 326        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header);
 327        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 328
 329        return slot;
 330}
 331
 332static int start_active_slot(struct active_request_slot *slot)
 333{
 334#ifdef USE_CURL_MULTI
 335        CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
 336
 337        if (curlm_result != CURLM_OK &&
 338            curlm_result != CURLM_CALL_MULTI_PERFORM) {
 339                active_requests--;
 340                slot->in_use = 0;
 341                return 0;
 342        }
 343#endif
 344        return 1;
 345}
 346
 347static void run_active_slot(struct active_request_slot *slot)
 348{
 349#ifdef USE_CURL_MULTI
 350        int num_transfers;
 351        long last_pos = 0;
 352        long current_pos;
 353        fd_set readfds;
 354        fd_set writefds;
 355        fd_set excfds;
 356        int max_fd;
 357        struct timeval select_timeout;
 358        CURLMcode curlm_result;
 359
 360        while (!slot->done) {
 361                data_received = 0;
 362                do {
 363                        curlm_result = curl_multi_perform(curlm,
 364                                                          &num_transfers);
 365                } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
 366                if (num_transfers < active_requests) {
 367                        process_curl_messages();
 368                        process_request_queue();
 369                }
 370
 371                if (!data_received && slot->local != NULL) {
 372                        current_pos = ftell(slot->local);
 373                        if (current_pos > last_pos)
 374                                data_received++;
 375                        last_pos = current_pos;
 376                }
 377
 378                if (!slot->done && !data_received) {
 379                        max_fd = 0;
 380                        FD_ZERO(&readfds);
 381                        FD_ZERO(&writefds);
 382                        FD_ZERO(&excfds);
 383                        select_timeout.tv_sec = 0;
 384                        select_timeout.tv_usec = 50000;
 385                        select(max_fd, &readfds, &writefds,
 386                               &excfds, &select_timeout);
 387                }
 388        }
 389#else
 390        slot->curl_result = curl_easy_perform(slot->curl);
 391        active_requests--;
 392#endif
 393}
 394
 395static void start_request(struct transfer_request *request)
 396{
 397        char *hex = sha1_to_hex(request->sha1);
 398        char prevfile[PATH_MAX];
 399        char *url;
 400        char *posn;
 401        int prevlocal;
 402        unsigned char prev_buf[PREV_BUF_SIZE];
 403        ssize_t prev_read = 0;
 404        long prev_posn = 0;
 405        char range[RANGE_HEADER_SIZE];
 406        struct curl_slist *range_header = NULL;
 407        struct active_request_slot *slot;
 408
 409        snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
 410        unlink(prevfile);
 411        rename(request->tmpfile, prevfile);
 412        unlink(request->tmpfile);
 413
 414        request->local = open(request->tmpfile,
 415                              O_WRONLY | O_CREAT | O_EXCL, 0666);
 416        /* This could have failed due to the "lazy directory creation";
 417         * try to mkdir the last path component.
 418         */
 419        if (request->local < 0 && errno == ENOENT) {
 420                char *dir = strrchr(request->tmpfile, '/');
 421                if (dir) {
 422                        *dir = 0;
 423                        mkdir(request->tmpfile, 0777);
 424                        *dir = '/';
 425                }
 426                request->local = open(request->tmpfile,
 427                                      O_WRONLY | O_CREAT | O_EXCL, 0666);
 428        }
 429
 430        if (request->local < 0) {
 431                request->state = ABORTED;
 432                error("Couldn't create temporary file %s for %s: %s\n",
 433                      request->tmpfile, request->filename, strerror(errno));
 434                return;
 435        }
 436
 437        memset(&request->stream, 0, sizeof(request->stream));
 438
 439        inflateInit(&request->stream);
 440
 441        SHA1_Init(&request->c);
 442
 443        url = xmalloc(strlen(request->repo->base) + 50);
 444        request->url = xmalloc(strlen(request->repo->base) + 50);
 445        strcpy(url, request->repo->base);
 446        posn = url + strlen(request->repo->base);
 447        strcpy(posn, "objects/");
 448        posn += 8;
 449        memcpy(posn, hex, 2);
 450        posn += 2;
 451        *(posn++) = '/';
 452        strcpy(posn, hex + 2);
 453        strcpy(request->url, url);
 454
 455        /* If a previous temp file is present, process what was already
 456           fetched. */
 457        prevlocal = open(prevfile, O_RDONLY);
 458        if (prevlocal != -1) {
 459                do {
 460                        prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
 461                        if (prev_read>0) {
 462                                if (fwrite_sha1_file(prev_buf,
 463                                                     1,
 464                                                     prev_read,
 465                                                     request) == prev_read) {
 466                                        prev_posn += prev_read;
 467                                } else {
 468                                        prev_read = -1;
 469                                }
 470                        }
 471                } while (prev_read > 0);
 472                close(prevlocal);
 473        }
 474        unlink(prevfile);
 475
 476        /* Reset inflate/SHA1 if there was an error reading the previous temp
 477           file; also rewind to the beginning of the local file. */
 478        if (prev_read == -1) {
 479                memset(&request->stream, 0, sizeof(request->stream));
 480                inflateInit(&request->stream);
 481                SHA1_Init(&request->c);
 482                if (prev_posn>0) {
 483                        prev_posn = 0;
 484                        lseek(request->local, SEEK_SET, 0);
 485                        ftruncate(request->local, 0);
 486                }
 487        }
 488
 489        slot = get_active_slot();
 490        curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
 491        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
 492        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
 493        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
 494        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
 495
 496        /* If we have successfully processed data from a previous fetch
 497           attempt, only fetch the data we don't already have. */
 498        if (prev_posn>0) {
 499                if (get_verbosely)
 500                        fprintf(stderr,
 501                                "Resuming fetch of object %s at byte %ld\n",
 502                                hex, prev_posn);
 503                sprintf(range, "Range: bytes=%ld-", prev_posn);
 504                range_header = curl_slist_append(range_header, range);
 505                curl_easy_setopt(slot->curl,
 506                                 CURLOPT_HTTPHEADER, range_header);
 507        }
 508
 509        /* Try to get the request started, abort the request on error */
 510        if (!start_active_slot(slot)) {
 511                request->state = ABORTED;
 512                close(request->local);
 513                free(request->url);
 514                return;
 515        }
 516        
 517        request->slot = slot;
 518        request->state = ACTIVE;
 519}
 520
 521static void finish_request(struct transfer_request *request)
 522{
 523        struct stat st;
 524
 525        fchmod(request->local, 0444);
 526        close(request->local);
 527
 528        if (request->http_code == 416) {
 529                fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
 530        } else if (request->curl_result != CURLE_OK) {
 531                if (stat(request->tmpfile, &st) == 0)
 532                        if (st.st_size == 0)
 533                                unlink(request->tmpfile);
 534                return;
 535        }
 536
 537        inflateEnd(&request->stream);
 538        SHA1_Final(request->real_sha1, &request->c);
 539        if (request->zret != Z_STREAM_END) {
 540                unlink(request->tmpfile);
 541                return;
 542        }
 543        if (memcmp(request->sha1, request->real_sha1, 20)) {
 544                unlink(request->tmpfile);
 545                return;
 546        }
 547        request->rename =
 548                move_temp_to_file(request->tmpfile, request->filename);
 549
 550        if (request->rename == 0)
 551                pull_say("got %s\n", sha1_to_hex(request->sha1));
 552}
 553
 554static void release_request(struct transfer_request *request)
 555{
 556        struct transfer_request *entry = request_queue_head;
 557
 558        if (request == request_queue_head) {
 559                request_queue_head = request->next;
 560        } else {
 561                while (entry->next != NULL && entry->next != request)
 562                        entry = entry->next;
 563                if (entry->next == request)
 564                        entry->next = entry->next->next;
 565        }
 566
 567        free(request->url);
 568        free(request);
 569}
 570
 571#ifdef USE_CURL_MULTI
 572static void process_curl_messages(void)
 573{
 574        int num_messages;
 575        struct active_request_slot *slot;
 576        struct transfer_request *request = NULL;
 577        CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
 578
 579        while (curl_message != NULL) {
 580                if (curl_message->msg == CURLMSG_DONE) {
 581                        int curl_result = curl_message->data.result;
 582                        slot = active_queue_head;
 583                        while (slot != NULL &&
 584                               slot->curl != curl_message->easy_handle)
 585                                slot = slot->next;
 586                        if (slot != NULL) {
 587                                curl_multi_remove_handle(curlm, slot->curl);
 588                                active_requests--;
 589                                slot->done = 1;
 590                                slot->in_use = 0;
 591                                slot->curl_result = curl_result;
 592                                curl_easy_getinfo(slot->curl,
 593                                                  CURLINFO_HTTP_CODE,
 594                                                  &slot->http_code);
 595                                request = request_queue_head;
 596                                while (request != NULL &&
 597                                       request->slot != slot)
 598                                        request = request->next;
 599                        } else {
 600                                fprintf(stderr, "Received DONE message for unknown request!\n");
 601                        }
 602                        if (request != NULL) {
 603                                request->curl_result = curl_result;
 604                                request->http_code = slot->http_code;
 605                                request->slot = NULL;
 606                                request->state = COMPLETE;
 607
 608                                /* Use alternates if necessary */
 609                                if (request->http_code == 404) {
 610                                        fetch_alternates(alt->base);
 611                                        if (request->repo->next != NULL) {
 612                                                request->repo =
 613                                                        request->repo->next;
 614                                                start_request(request);
 615                                        }
 616                                } else {
 617                                        finish_request(request);
 618                                }
 619                        }
 620                } else {
 621                        fprintf(stderr, "Unknown CURL message received: %d\n",
 622                                (int)curl_message->msg);
 623                }
 624                curl_message = curl_multi_info_read(curlm, &num_messages);
 625        }
 626}
 627
 628static void process_request_queue(void)
 629{
 630        struct transfer_request *request = request_queue_head;
 631        struct active_request_slot *slot = active_queue_head;
 632        int num_transfers;
 633
 634        while (active_requests < max_requests && request != NULL) {
 635                if (request->state == WAITING) {
 636                        if (has_sha1_file(request->sha1))
 637                                release_request(request);
 638                        else
 639                                start_request(request);
 640                        curl_multi_perform(curlm, &num_transfers);
 641                }
 642                request = request->next;
 643        }
 644
 645        while (slot != NULL) {
 646                if (!slot->in_use && slot->curl != NULL) {
 647                        curl_easy_cleanup(slot->curl);
 648                        slot->curl = NULL;
 649                }
 650                slot = slot->next;
 651        }                               
 652}
 653#endif
 654
 655void prefetch(unsigned char *sha1)
 656{
 657        struct transfer_request *newreq;
 658        struct transfer_request *tail;
 659        char *filename = sha1_file_name(sha1);
 660
 661        newreq = xmalloc(sizeof(*newreq));
 662        memcpy(newreq->sha1, sha1, 20);
 663        newreq->repo = alt;
 664        newreq->url = NULL;
 665        newreq->local = -1;
 666        newreq->state = WAITING;
 667        snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
 668        snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
 669                 "%s.temp", filename);
 670        newreq->next = NULL;
 671
 672        if (request_queue_head == NULL) {
 673                request_queue_head = newreq;
 674        } else {
 675                tail = request_queue_head;
 676                while (tail->next != NULL) {
 677                        tail = tail->next;
 678                }
 679                tail->next = newreq;
 680        }
 681#ifdef USE_CURL_MULTI
 682        process_request_queue();
 683        process_curl_messages();
 684#endif
 685}
 686
 687static int fetch_index(struct alt_base *repo, unsigned char *sha1)
 688{
 689        char *hex = sha1_to_hex(sha1);
 690        char *filename;
 691        char *url;
 692        char tmpfile[PATH_MAX];
 693        long prev_posn = 0;
 694        char range[RANGE_HEADER_SIZE];
 695        struct curl_slist *range_header = NULL;
 696
 697        FILE *indexfile;
 698        struct active_request_slot *slot;
 699
 700        if (has_pack_index(sha1))
 701                return 0;
 702
 703        if (get_verbosely)
 704                fprintf(stderr, "Getting index for pack %s\n", hex);
 705        
 706        url = xmalloc(strlen(repo->base) + 64);
 707        sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
 708        
 709        filename = sha1_pack_index_name(sha1);
 710        snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
 711        indexfile = fopen(tmpfile, "a");
 712        if (!indexfile)
 713                return error("Unable to open local file %s for pack index",
 714                             filename);
 715
 716        slot = get_active_slot();
 717        curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
 718        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
 719        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
 720        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
 721        slot->local = indexfile;
 722
 723        /* If there is data present from a previous transfer attempt,
 724           resume where it left off */
 725        prev_posn = ftell(indexfile);
 726        if (prev_posn>0) {
 727                if (get_verbosely)
 728                        fprintf(stderr,
 729                                "Resuming fetch of index for pack %s at byte %ld\n",
 730                                hex, prev_posn);
 731                sprintf(range, "Range: bytes=%ld-", prev_posn);
 732                range_header = curl_slist_append(range_header, range);
 733                curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
 734        }
 735
 736        if (start_active_slot(slot)) {
 737                run_active_slot(slot);
 738                if (slot->curl_result != CURLE_OK) {
 739                        fclose(indexfile);
 740                        return error("Unable to get pack index %s\n%s", url,
 741                                     curl_errorstr);
 742                }
 743        } else {
 744                return error("Unable to start request");
 745        }
 746
 747        fclose(indexfile);
 748
 749        return move_temp_to_file(tmpfile, filename);
 750}
 751
 752static int setup_index(struct alt_base *repo, unsigned char *sha1)
 753{
 754        struct packed_git *new_pack;
 755        if (has_pack_file(sha1))
 756                return 0; // don't list this as something we can get
 757
 758        if (fetch_index(repo, sha1))
 759                return -1;
 760
 761        new_pack = parse_pack_index(sha1);
 762        new_pack->next = repo->packs;
 763        repo->packs = new_pack;
 764        return 0;
 765}
 766
 767static int fetch_alternates(char *base)
 768{
 769        int ret = 0;
 770        struct buffer buffer;
 771        char *url;
 772        char *data;
 773        int i = 0;
 774        int http_specific = 1;
 775        struct alt_base *tail = alt;
 776        static const char null_byte = '\0';
 777
 778        struct active_request_slot *slot;
 779
 780        if (got_alternates)
 781                return 0;
 782
 783        data = xmalloc(4096);
 784        buffer.size = 4096;
 785        buffer.posn = 0;
 786        buffer.buffer = data;
 787
 788        if (get_verbosely)
 789                fprintf(stderr, "Getting alternates list\n");
 790        
 791        url = xmalloc(strlen(base) + 31);
 792        sprintf(url, "%s/objects/info/http-alternates", base);
 793
 794        slot = get_active_slot();
 795        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
 796        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
 797                         fwrite_buffer_dynamic);
 798        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
 799        if (start_active_slot(slot)) {
 800                run_active_slot(slot);
 801                if (slot->curl_result != CURLE_OK || !buffer.posn) {
 802                        http_specific = 0;
 803
 804                        sprintf(url, "%s/objects/info/alternates", base);
 805
 806                        slot = get_active_slot();
 807                        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
 808                        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
 809                                         fwrite_buffer_dynamic);
 810                        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
 811                        if (start_active_slot(slot)) {
 812                                run_active_slot(slot);
 813                                if (slot->curl_result != CURLE_OK) {
 814                                        free(buffer.buffer);
 815                                        if (slot->http_code == 404)
 816                                                got_alternates = 1;
 817                                        return 0;
 818                                }
 819                        }
 820                }
 821        } else {
 822                free(buffer.buffer);
 823                return 0;
 824        }
 825
 826        fwrite_buffer_dynamic(&null_byte, 1, 1, &buffer);
 827        buffer.posn--;
 828        data = buffer.buffer;
 829
 830        while (i < buffer.posn) {
 831                int posn = i;
 832                while (posn < buffer.posn && data[posn] != '\n')
 833                        posn++;
 834                if (data[posn] == '\n') {
 835                        int okay = 0;
 836                        int serverlen = 0;
 837                        struct alt_base *newalt;
 838                        char *target = NULL;
 839                        if (data[i] == '/') {
 840                                serverlen = strchr(base + 8, '/') - base;
 841                                okay = 1;
 842                        } else if (!memcmp(data + i, "../", 3)) {
 843                                i += 3;
 844                                serverlen = strlen(base);
 845                                while (i + 2 < posn && 
 846                                       !memcmp(data + i, "../", 3)) {
 847                                        do {
 848                                                serverlen--;
 849                                        } while (serverlen &&
 850                                                 base[serverlen - 1] != '/');
 851                                        i += 3;
 852                                }
 853                                // If the server got removed, give up.
 854                                okay = strchr(base, ':') - base + 3 < 
 855                                        serverlen;
 856                        } else if (http_specific) {
 857                                char *colon = strchr(data + i, ':');
 858                                char *slash = strchr(data + i, '/');
 859                                if (colon && slash && colon < data + posn &&
 860                                    slash < data + posn && colon < slash) {
 861                                        okay = 1;
 862                                }
 863                        }
 864                        // skip 'objects' at end
 865                        if (okay) {
 866                                target = xmalloc(serverlen + posn - i - 6);
 867                                strncpy(target, base, serverlen);
 868                                strncpy(target + serverlen, data + i,
 869                                        posn - i - 7);
 870                                target[serverlen + posn - i - 7] = '\0';
 871                                if (get_verbosely)
 872                                        fprintf(stderr, 
 873                                                "Also look at %s\n", target);
 874                                newalt = xmalloc(sizeof(*newalt));
 875                                newalt->next = NULL;
 876                                newalt->base = target;
 877                                newalt->got_indices = 0;
 878                                newalt->packs = NULL;
 879                                while (tail->next != NULL)
 880                                        tail = tail->next;
 881                                tail->next = newalt;
 882                                ret++;
 883                        }
 884                }
 885                i = posn + 1;
 886        }
 887
 888        got_alternates = 1;
 889        free(buffer.buffer);
 890        return ret;
 891}
 892
 893static int fetch_indices(struct alt_base *repo)
 894{
 895        unsigned char sha1[20];
 896        char *url;
 897        struct buffer buffer;
 898        char *data;
 899        int i = 0;
 900
 901        struct active_request_slot *slot;
 902
 903        if (repo->got_indices)
 904                return 0;
 905
 906        data = xmalloc(4096);
 907        buffer.size = 4096;
 908        buffer.posn = 0;
 909        buffer.buffer = data;
 910
 911        if (get_verbosely)
 912                fprintf(stderr, "Getting pack list\n");
 913        
 914        url = xmalloc(strlen(repo->base) + 21);
 915        sprintf(url, "%s/objects/info/packs", repo->base);
 916
 917        slot = get_active_slot();
 918        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
 919        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
 920                         fwrite_buffer_dynamic);
 921        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
 922        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
 923        if (start_active_slot(slot)) {
 924                run_active_slot(slot);
 925                if (slot->curl_result != CURLE_OK) {
 926                        free(buffer.buffer);
 927                        return error("%s", curl_errorstr);
 928                }
 929        } else {
 930                free(buffer.buffer);
 931                return error("Unable to start request");
 932        }
 933
 934        data = buffer.buffer;
 935        while (i < buffer.posn) {
 936                switch (data[i]) {
 937                case 'P':
 938                        i++;
 939                        if (i + 52 < buffer.posn &&
 940                            !strncmp(data + i, " pack-", 6) &&
 941                            !strncmp(data + i + 46, ".pack\n", 6)) {
 942                                get_sha1_hex(data + i + 6, sha1);
 943                                setup_index(repo, sha1);
 944                                i += 51;
 945                                break;
 946                        }
 947                default:
 948                        while (data[i] != '\n')
 949                                i++;
 950                }
 951                i++;
 952        }
 953
 954        free(buffer.buffer);
 955        repo->got_indices = 1;
 956        return 0;
 957}
 958
 959static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
 960{
 961        char *url;
 962        struct packed_git *target;
 963        struct packed_git **lst;
 964        FILE *packfile;
 965        char *filename;
 966        char tmpfile[PATH_MAX];
 967        int ret;
 968        long prev_posn = 0;
 969        char range[RANGE_HEADER_SIZE];
 970        struct curl_slist *range_header = NULL;
 971
 972        struct active_request_slot *slot;
 973
 974        if (fetch_indices(repo))
 975                return -1;
 976        target = find_sha1_pack(sha1, repo->packs);
 977        if (!target)
 978                return -1;
 979
 980        if (get_verbosely) {
 981                fprintf(stderr, "Getting pack %s\n",
 982                        sha1_to_hex(target->sha1));
 983                fprintf(stderr, " which contains %s\n",
 984                        sha1_to_hex(sha1));
 985        }
 986
 987        url = xmalloc(strlen(repo->base) + 65);
 988        sprintf(url, "%s/objects/pack/pack-%s.pack",
 989                repo->base, sha1_to_hex(target->sha1));
 990
 991        filename = sha1_pack_name(target->sha1);
 992        snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
 993        packfile = fopen(tmpfile, "a");
 994        if (!packfile)
 995                return error("Unable to open local file %s for pack",
 996                             filename);
 997
 998        slot = get_active_slot();
 999        curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
1000        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
1001        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
1002        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
1003        slot->local = packfile;
1004
1005        /* If there is data present from a previous transfer attempt,
1006           resume where it left off */
1007        prev_posn = ftell(packfile);
1008        if (prev_posn>0) {
1009                if (get_verbosely)
1010                        fprintf(stderr,
1011                                "Resuming fetch of pack %s at byte %ld\n",
1012                                sha1_to_hex(target->sha1), prev_posn);
1013                sprintf(range, "Range: bytes=%ld-", prev_posn);
1014                range_header = curl_slist_append(range_header, range);
1015                curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
1016        }
1017
1018        if (start_active_slot(slot)) {
1019                run_active_slot(slot);
1020                if (slot->curl_result != CURLE_OK) {
1021                        fclose(packfile);
1022                        return error("Unable to get pack file %s\n%s", url,
1023                                     curl_errorstr);
1024                }
1025        } else {
1026                return error("Unable to start request");
1027        }
1028
1029        fclose(packfile);
1030
1031        ret = move_temp_to_file(tmpfile, filename);
1032        if (ret)
1033                return ret;
1034
1035        lst = &repo->packs;
1036        while (*lst != target)
1037                lst = &((*lst)->next);
1038        *lst = (*lst)->next;
1039
1040        if (verify_pack(target, 0))
1041                return -1;
1042        install_packed_git(target);
1043
1044        return 0;
1045}
1046
1047static int fetch_object(struct alt_base *repo, unsigned char *sha1)
1048{
1049        char *hex = sha1_to_hex(sha1);
1050        int ret;
1051        struct transfer_request *request = request_queue_head;
1052
1053        while (request != NULL && memcmp(request->sha1, sha1, 20))
1054                request = request->next;
1055        if (request == NULL)
1056                return error("Couldn't find request for %s in the queue", hex);
1057
1058        if (has_sha1_file(request->sha1)) {
1059                release_request(request);
1060                return 0;
1061        }
1062
1063#ifdef USE_CURL_MULTI
1064        while (request->state == WAITING) {
1065                int num_transfers;
1066                curl_multi_perform(curlm, &num_transfers);
1067                if (num_transfers < active_requests) {
1068                        process_curl_messages();
1069                        process_request_queue();
1070                }
1071        }
1072#else
1073        start_request(request);
1074#endif
1075
1076        while (request->state == ACTIVE) {
1077                run_active_slot(request->slot);
1078#ifndef USE_CURL_MULTI
1079                request->curl_result = request->slot->curl_result;
1080                request->http_code = request->slot->http_code;
1081                request->slot = NULL;
1082
1083                /* Use alternates if necessary */
1084                if (request->http_code == 404) {
1085                        fetch_alternates(alt->base);
1086                        if (request->repo->next != NULL) {
1087                                request->repo = request->repo->next;
1088                                start_request(request);
1089                        }
1090                } else {
1091                        finish_request(request);
1092                        request->state = COMPLETE;
1093                }
1094#endif
1095        }
1096
1097        if (request->state == ABORTED) {
1098                release_request(request);
1099                return error("Request for %s aborted", hex);
1100        }
1101
1102        if (request->curl_result != CURLE_OK && request->http_code != 416) {
1103                if (request->http_code == 404)
1104                        ret = -1; /* Be silent, it is probably in a pack. */
1105                else
1106                        ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
1107                                    request->errorstr, request->curl_result,
1108                                    request->http_code, hex);
1109                release_request(request);
1110                return ret;
1111        }
1112
1113        if (request->zret != Z_STREAM_END) {
1114                ret = error("File %s (%s) corrupt\n", hex, request->url);
1115                release_request(request);
1116                return ret;
1117        }
1118
1119        if (memcmp(request->sha1, request->real_sha1, 20)) {
1120                release_request(request);
1121                return error("File %s has bad hash\n", hex);
1122        }
1123
1124        if (request->rename < 0) {
1125                ret = error("unable to write sha1 filename %s: %s",
1126                            request->filename,
1127                            strerror(request->rename));
1128                release_request(request);
1129                return ret;
1130        }
1131
1132        release_request(request);
1133        return 0;
1134}
1135
1136int fetch(unsigned char *sha1)
1137{
1138        struct alt_base *altbase = alt;
1139
1140        if (!fetch_object(altbase, sha1))
1141                return 0;
1142        while (altbase) {
1143                if (!fetch_pack(altbase, sha1))
1144                        return 0;
1145                fetch_alternates(alt->base);
1146                altbase = altbase->next;
1147        }
1148        return error("Unable to find %s under %s\n", sha1_to_hex(sha1), 
1149                     alt->base);
1150}
1151
1152static inline int needs_quote(int ch)
1153{
1154        switch (ch) {
1155        case '/': case '-': case '.':
1156        case 'A'...'Z': case 'a'...'z': case '0'...'9':
1157                return 0;
1158        default:
1159                return 1;
1160        }
1161}
1162
1163static inline int hex(int v)
1164{
1165        if (v < 10) return '0' + v;
1166        else return 'A' + v - 10;
1167}
1168
1169static char *quote_ref_url(const char *base, const char *ref)
1170{
1171        const char *cp;
1172        char *dp, *qref;
1173        int len, baselen, ch;
1174
1175        baselen = strlen(base);
1176        len = baselen + 6; /* "refs/" + NUL */
1177        for (cp = ref; (ch = *cp) != 0; cp++, len++)
1178                if (needs_quote(ch))
1179                        len += 2; /* extra two hex plus replacement % */
1180        qref = xmalloc(len);
1181        memcpy(qref, base, baselen);
1182        memcpy(qref + baselen, "refs/", 5);
1183        for (cp = ref, dp = qref + baselen + 5; (ch = *cp) != 0; cp++) {
1184                if (needs_quote(ch)) {
1185                        *dp++ = '%';
1186                        *dp++ = hex((ch >> 4) & 0xF);
1187                        *dp++ = hex(ch & 0xF);
1188                }
1189                else
1190                        *dp++ = ch;
1191        }
1192        *dp = 0;
1193
1194        return qref;
1195}
1196
1197int fetch_ref(char *ref, unsigned char *sha1)
1198{
1199        char *url;
1200        char hex[42];
1201        struct buffer buffer;
1202        char *base = alt->base;
1203        struct active_request_slot *slot;
1204        buffer.size = 41;
1205        buffer.posn = 0;
1206        buffer.buffer = hex;
1207        hex[41] = '\0';
1208        
1209        url = quote_ref_url(base, ref);
1210        slot = get_active_slot();
1211        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
1212        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
1213        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
1214        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
1215        if (start_active_slot(slot)) {
1216                run_active_slot(slot);
1217                if (slot->curl_result != CURLE_OK)
1218                        return error("Couldn't get %s for %s\n%s",
1219                                     url, ref, curl_errorstr);
1220        } else {
1221                return error("Unable to start request");
1222        }
1223
1224        hex[40] = '\0';
1225        get_sha1_hex(hex, sha1);
1226        return 0;
1227}
1228
1229int main(int argc, char **argv)
1230{
1231        char *commit_id;
1232        char *url;
1233        int arg = 1;
1234        struct active_request_slot *slot;
1235        char *low_speed_limit;
1236        char *low_speed_time;
1237        char *wait_url;
1238        int rc = 0;
1239
1240        while (arg < argc && argv[arg][0] == '-') {
1241                if (argv[arg][1] == 't') {
1242                        get_tree = 1;
1243                } else if (argv[arg][1] == 'c') {
1244                        get_history = 1;
1245                } else if (argv[arg][1] == 'a') {
1246                        get_all = 1;
1247                        get_tree = 1;
1248                        get_history = 1;
1249                } else if (argv[arg][1] == 'v') {
1250                        get_verbosely = 1;
1251                } else if (argv[arg][1] == 'w') {
1252                        write_ref = argv[arg + 1];
1253                        arg++;
1254                } else if (!strcmp(argv[arg], "--recover")) {
1255                        get_recover = 1;
1256                }
1257                arg++;
1258        }
1259        if (argc < arg + 2) {
1260                usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
1261                return 1;
1262        }
1263        commit_id = argv[arg];
1264        url = argv[arg + 1];
1265
1266        curl_global_init(CURL_GLOBAL_ALL);
1267
1268#ifdef USE_CURL_MULTI
1269        {
1270                char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
1271                if (http_max_requests != NULL)
1272                        max_requests = atoi(http_max_requests);
1273        }
1274
1275        curlm = curl_multi_init();
1276        if (curlm == NULL) {
1277                fprintf(stderr, "Error creating curl multi handle.\n");
1278                return 1;
1279        }
1280#endif
1281
1282        if (getenv("GIT_SSL_NO_VERIFY"))
1283                curl_ssl_verify = 0;
1284
1285        ssl_cert = getenv("GIT_SSL_CERT");
1286#if LIBCURL_VERSION_NUM >= 0x070902
1287        ssl_key = getenv("GIT_SSL_KEY");
1288#endif
1289#if LIBCURL_VERSION_NUM >= 0x070908
1290        ssl_capath = getenv("GIT_SSL_CAPATH");
1291#endif
1292        ssl_cainfo = getenv("GIT_SSL_CAINFO");
1293
1294        low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
1295        if (low_speed_limit != NULL)
1296                curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
1297        low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
1298        if (low_speed_time != NULL)
1299                curl_low_speed_time = strtol(low_speed_time, NULL, 10);
1300
1301        git_config(http_options);
1302
1303        if (curl_ssl_verify == -1)
1304                curl_ssl_verify = 1;
1305
1306#ifdef USE_CURL_MULTI
1307        if (max_requests < 1)
1308                max_requests = DEFAULT_MAX_REQUESTS;
1309#endif
1310
1311        pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
1312        no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
1313        no_range_header = curl_slist_append(no_range_header, "Range:");
1314
1315#ifndef NO_CURL_EASY_DUPHANDLE
1316        curl_default = get_curl_handle();
1317#endif
1318
1319        alt = xmalloc(sizeof(*alt));
1320        alt->base = url;
1321        alt->got_indices = 0;
1322        alt->packs = NULL;
1323        alt->next = NULL;
1324
1325        if (pull(commit_id))
1326                rc = 1;
1327
1328        curl_slist_free_all(pragma_header);
1329        curl_slist_free_all(no_pragma_header);
1330        curl_slist_free_all(no_range_header);
1331#ifndef NO_CURL_EASY_DUPHANDLE
1332        curl_easy_cleanup(curl_default);
1333#endif
1334        slot = active_queue_head;
1335        while (slot != NULL) {
1336                if (slot->in_use) {
1337                        if (get_verbosely) {
1338                                curl_easy_getinfo(slot->curl,
1339                                                  CURLINFO_EFFECTIVE_URL,
1340                                                  &wait_url);
1341                                fprintf(stderr, "Waiting for %s\n", wait_url);
1342                        }
1343                        run_active_slot(slot);
1344                }
1345                if (slot->curl != NULL)
1346                        curl_easy_cleanup(slot->curl);
1347                slot = slot->next;
1348        }
1349#ifdef USE_CURL_MULTI
1350        curl_multi_cleanup(curlm);
1351#endif
1352        curl_global_cleanup();
1353        return rc;
1354}