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