http-fetch.con commit [PATCH] Return CURL error message when object transfer fails (1ddea77)
   1#include "cache.h"
   2#include "commit.h"
   3
   4#include "fetch.h"
   5
   6#include <curl/curl.h>
   7#include <curl/easy.h>
   8
   9#if LIBCURL_VERSION_NUM < 0x070704
  10#define curl_global_cleanup() do { /* nothing */ } while(0)
  11#endif
  12#if LIBCURL_VERSION_NUM < 0x070800
  13#define curl_global_init(a) do { /* nothing */ } while(0)
  14#endif
  15
  16static CURL *curl;
  17static struct curl_slist *no_pragma_header;
  18static char curl_errorstr[CURL_ERROR_SIZE];
  19
  20static char *initial_base;
  21
  22struct alt_base
  23{
  24        char *base;
  25        int got_indices;
  26        struct packed_git *packs;
  27        struct alt_base *next;
  28};
  29
  30struct alt_base *alt = NULL;
  31
  32static SHA_CTX c;
  33static z_stream stream;
  34
  35static int local;
  36static int zret;
  37
  38static int curl_ssl_verify;
  39
  40struct buffer
  41{
  42        size_t posn;
  43        size_t size;
  44        void *buffer;
  45};
  46
  47static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
  48                            struct buffer *buffer)
  49{
  50        size_t size = eltsize * nmemb;
  51        if (size > buffer->size - buffer->posn)
  52                size = buffer->size - buffer->posn;
  53        memcpy(buffer->buffer + buffer->posn, ptr, size);
  54        buffer->posn += size;
  55        return size;
  56}
  57
  58static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
  59                               void *data)
  60{
  61        unsigned char expn[4096];
  62        size_t size = eltsize * nmemb;
  63        int posn = 0;
  64        do {
  65                ssize_t retval = write(local, ptr + posn, size - posn);
  66                if (retval < 0)
  67                        return posn;
  68                posn += retval;
  69        } while (posn < size);
  70
  71        stream.avail_in = size;
  72        stream.next_in = ptr;
  73        do {
  74                stream.next_out = expn;
  75                stream.avail_out = sizeof(expn);
  76                zret = inflate(&stream, Z_SYNC_FLUSH);
  77                SHA1_Update(&c, expn, sizeof(expn) - stream.avail_out);
  78        } while (stream.avail_in && zret == Z_OK);
  79        return size;
  80}
  81
  82void prefetch(unsigned char *sha1)
  83{
  84}
  85
  86static int got_alternates = 0;
  87
  88static int fetch_index(struct alt_base *repo, unsigned char *sha1)
  89{
  90        char *filename;
  91        char *url;
  92
  93        FILE *indexfile;
  94
  95        if (has_pack_index(sha1))
  96                return 0;
  97
  98        if (get_verbosely)
  99                fprintf(stderr, "Getting index for pack %s\n",
 100                        sha1_to_hex(sha1));
 101        
 102        url = xmalloc(strlen(repo->base) + 64);
 103        sprintf(url, "%s/objects/pack/pack-%s.idx",
 104                repo->base, sha1_to_hex(sha1));
 105        
 106        filename = sha1_pack_index_name(sha1);
 107        indexfile = fopen(filename, "w");
 108        if (!indexfile)
 109                return error("Unable to open local file %s for pack index",
 110                             filename);
 111
 112        curl_easy_setopt(curl, CURLOPT_FILE, indexfile);
 113        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
 114        curl_easy_setopt(curl, CURLOPT_URL, url);
 115        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
 116        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 117        
 118        if (curl_easy_perform(curl)) {
 119                fclose(indexfile);
 120                return error("Unable to get pack index %s\n%s", url,
 121                             curl_errorstr);
 122        }
 123
 124        fclose(indexfile);
 125        return 0;
 126}
 127
 128static int setup_index(struct alt_base *repo, unsigned char *sha1)
 129{
 130        struct packed_git *new_pack;
 131        if (has_pack_file(sha1))
 132                return 0; // don't list this as something we can get
 133
 134        if (fetch_index(repo, sha1))
 135                return -1;
 136
 137        new_pack = parse_pack_index(sha1);
 138        new_pack->next = repo->packs;
 139        repo->packs = new_pack;
 140        return 0;
 141}
 142
 143static int fetch_alternates(char *base)
 144{
 145        int ret = 0;
 146        struct buffer buffer;
 147        char *url;
 148        char *data;
 149        int i = 0;
 150        int http_specific = 1;
 151        if (got_alternates)
 152                return 0;
 153        data = xmalloc(4096);
 154        buffer.size = 4095;
 155        buffer.posn = 0;
 156        buffer.buffer = data;
 157
 158        if (get_verbosely)
 159                fprintf(stderr, "Getting alternates list\n");
 160        
 161        url = xmalloc(strlen(base) + 31);
 162        sprintf(url, "%s/objects/info/http-alternates", base);
 163
 164        curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
 165        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 166        curl_easy_setopt(curl, CURLOPT_URL, url);
 167
 168        if (curl_easy_perform(curl) || !buffer.posn) {
 169                http_specific = 0;
 170
 171                sprintf(url, "%s/objects/info/alternates", base);
 172                
 173                curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
 174                curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 175                curl_easy_setopt(curl, CURLOPT_URL, url);
 176                
 177                if (curl_easy_perform(curl)) {
 178                        return 0;
 179                }
 180        }
 181
 182        data[buffer.posn] = '\0';
 183
 184        while (i < buffer.posn) {
 185                int posn = i;
 186                while (posn < buffer.posn && data[posn] != '\n')
 187                        posn++;
 188                if (data[posn] == '\n') {
 189                        int okay = 0;
 190                        int serverlen = 0;
 191                        struct alt_base *newalt;
 192                        char *target = NULL;
 193                        if (data[i] == '/') {
 194                                serverlen = strchr(base + 8, '/') - base;
 195                                okay = 1;
 196                        } else if (!memcmp(data + i, "../", 3)) {
 197                                i += 3;
 198                                serverlen = strlen(base);
 199                                while (i + 2 < posn && 
 200                                       !memcmp(data + i, "../", 3)) {
 201                                        do {
 202                                                serverlen--;
 203                                        } while (serverlen &&
 204                                                 base[serverlen - 1] != '/');
 205                                        i += 3;
 206                                }
 207                                // If the server got removed, give up.
 208                                okay = strchr(base, ':') - base + 3 < 
 209                                        serverlen;
 210                        } else if (http_specific) {
 211                                char *colon = strchr(data + i, ':');
 212                                char *slash = strchr(data + i, '/');
 213                                if (colon && slash && colon < data + posn &&
 214                                    slash < data + posn && colon < slash) {
 215                                        okay = 1;
 216                                }
 217                        }
 218                        // skip 'objects' at end
 219                        if (okay) {
 220                                target = xmalloc(serverlen + posn - i - 6);
 221                                strncpy(target, base, serverlen);
 222                                strncpy(target + serverlen, data + i,
 223                                        posn - i - 7);
 224                                target[serverlen + posn - i - 7] = '\0';
 225                                if (get_verbosely)
 226                                        fprintf(stderr, 
 227                                                "Also look at %s\n", target);
 228                                newalt = xmalloc(sizeof(*newalt));
 229                                newalt->next = alt;
 230                                newalt->base = target;
 231                                newalt->got_indices = 0;
 232                                newalt->packs = NULL;
 233                                alt = newalt;
 234                                ret++;
 235                        }
 236                }
 237                i = posn + 1;
 238        }
 239        got_alternates = 1;
 240        
 241        return ret;
 242}
 243
 244static int fetch_indices(struct alt_base *repo)
 245{
 246        unsigned char sha1[20];
 247        char *url;
 248        struct buffer buffer;
 249        char *data;
 250        int i = 0;
 251
 252        if (repo->got_indices)
 253                return 0;
 254
 255        data = xmalloc(4096);
 256        buffer.size = 4096;
 257        buffer.posn = 0;
 258        buffer.buffer = data;
 259
 260        if (get_verbosely)
 261                fprintf(stderr, "Getting pack list\n");
 262        
 263        url = xmalloc(strlen(repo->base) + 21);
 264        sprintf(url, "%s/objects/info/packs", repo->base);
 265
 266        curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
 267        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 268        curl_easy_setopt(curl, CURLOPT_URL, url);
 269        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
 270        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 271        
 272        if (curl_easy_perform(curl))
 273                return error("%s", curl_errorstr);
 274
 275        while (i < buffer.posn) {
 276                switch (data[i]) {
 277                case 'P':
 278                        i++;
 279                        if (i + 52 < buffer.posn &&
 280                            !strncmp(data + i, " pack-", 6) &&
 281                            !strncmp(data + i + 46, ".pack\n", 6)) {
 282                                get_sha1_hex(data + i + 6, sha1);
 283                                setup_index(repo, sha1);
 284                                i += 51;
 285                                break;
 286                        }
 287                default:
 288                        while (data[i] != '\n')
 289                                i++;
 290                }
 291                i++;
 292        }
 293
 294        repo->got_indices = 1;
 295        return 0;
 296}
 297
 298static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
 299{
 300        char *url;
 301        struct packed_git *target;
 302        struct packed_git **lst;
 303        FILE *packfile;
 304        char *filename;
 305
 306        if (fetch_indices(repo))
 307                return -1;
 308        target = find_sha1_pack(sha1, repo->packs);
 309        if (!target)
 310                return -1;
 311
 312        if (get_verbosely) {
 313                fprintf(stderr, "Getting pack %s\n",
 314                        sha1_to_hex(target->sha1));
 315                fprintf(stderr, " which contains %s\n",
 316                        sha1_to_hex(sha1));
 317        }
 318
 319        url = xmalloc(strlen(repo->base) + 65);
 320        sprintf(url, "%s/objects/pack/pack-%s.pack",
 321                repo->base, sha1_to_hex(target->sha1));
 322
 323        filename = sha1_pack_name(target->sha1);
 324        packfile = fopen(filename, "w");
 325        if (!packfile)
 326                return error("Unable to open local file %s for pack",
 327                             filename);
 328
 329        curl_easy_setopt(curl, CURLOPT_FILE, packfile);
 330        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
 331        curl_easy_setopt(curl, CURLOPT_URL, url);
 332        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
 333        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 334
 335        if (curl_easy_perform(curl)) {
 336                fclose(packfile);
 337                return error("Unable to get pack file %s\n%s", url,
 338                             curl_errorstr);
 339        }
 340
 341        fclose(packfile);
 342
 343        lst = &repo->packs;
 344        while (*lst != target)
 345                lst = &((*lst)->next);
 346        *lst = (*lst)->next;
 347
 348        install_packed_git(target);
 349
 350        return 0;
 351}
 352
 353int fetch_object(struct alt_base *repo, unsigned char *sha1)
 354{
 355        char *hex = sha1_to_hex(sha1);
 356        char *filename = sha1_file_name(sha1);
 357        unsigned char real_sha1[20];
 358        char tmpfile[PATH_MAX];
 359        int ret;
 360        char *url;
 361        char *posn;
 362
 363        snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX",
 364                 get_object_directory());
 365
 366        local = mkstemp(tmpfile);
 367        if (local < 0)
 368                return error("Couldn't create temporary file %s for %s: %s\n",
 369                             tmpfile, filename, strerror(errno));
 370
 371        memset(&stream, 0, sizeof(stream));
 372
 373        inflateInit(&stream);
 374
 375        SHA1_Init(&c);
 376
 377        curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
 378        curl_easy_setopt(curl, CURLOPT_FILE, NULL);
 379        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
 380        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
 381        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 382
 383        url = xmalloc(strlen(repo->base) + 50);
 384        strcpy(url, repo->base);
 385        posn = url + strlen(repo->base);
 386        strcpy(posn, "objects/");
 387        posn += 8;
 388        memcpy(posn, hex, 2);
 389        posn += 2;
 390        *(posn++) = '/';
 391        strcpy(posn, hex + 2);
 392
 393        curl_easy_setopt(curl, CURLOPT_URL, url);
 394
 395        if (curl_easy_perform(curl)) {
 396                unlink(filename);
 397                return error("%s", curl_errorstr);
 398        }
 399
 400        fchmod(local, 0444);
 401        close(local);
 402        inflateEnd(&stream);
 403        SHA1_Final(real_sha1, &c);
 404        if (zret != Z_STREAM_END) {
 405                unlink(tmpfile);
 406                return error("File %s (%s) corrupt\n", hex, url);
 407        }
 408        if (memcmp(sha1, real_sha1, 20)) {
 409                unlink(tmpfile);
 410                return error("File %s has bad hash\n", hex);
 411        }
 412        ret = link(tmpfile, filename);
 413        if (ret < 0) {
 414                /* Same Coda hack as in write_sha1_file(sha1_file.c) */
 415                ret = errno;
 416                if (ret == EXDEV && !rename(tmpfile, filename))
 417                        goto out;
 418        }
 419        unlink(tmpfile);
 420        if (ret) {
 421                if (ret != EEXIST)
 422                        return error("unable to write sha1 filename %s: %s",
 423                                     filename, strerror(ret));
 424        }
 425 out:
 426        pull_say("got %s\n", hex);
 427        return 0;
 428}
 429
 430int fetch(unsigned char *sha1)
 431{
 432        struct alt_base *altbase = alt;
 433        while (altbase) {
 434                if (!fetch_object(altbase, sha1))
 435                        return 0;
 436                if (!fetch_pack(altbase, sha1))
 437                        return 0;
 438                if (fetch_alternates(altbase->base) > 0) {
 439                        altbase = alt;
 440                        continue;
 441                }
 442                altbase = altbase->next;
 443        }
 444        return error("Unable to find %s under %s\n", sha1_to_hex(sha1), 
 445                     initial_base);
 446}
 447
 448int fetch_ref(char *ref, unsigned char *sha1)
 449{
 450        char *url, *posn;
 451        char hex[42];
 452        struct buffer buffer;
 453        char *base = initial_base;
 454        buffer.size = 41;
 455        buffer.posn = 0;
 456        buffer.buffer = hex;
 457        hex[41] = '\0';
 458        
 459        curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
 460        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
 461        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
 462        curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
 463
 464        url = xmalloc(strlen(base) + 6 + strlen(ref));
 465        strcpy(url, base);
 466        posn = url + strlen(base);
 467        strcpy(posn, "refs/");
 468        posn += 5;
 469        strcpy(posn, ref);
 470
 471        curl_easy_setopt(curl, CURLOPT_URL, url);
 472
 473        if (curl_easy_perform(curl))
 474                return error("Couldn't get %s for %s\n%s",
 475                             url, ref, curl_errorstr);
 476
 477        hex[40] = '\0';
 478        get_sha1_hex(hex, sha1);
 479        return 0;
 480}
 481
 482int main(int argc, char **argv)
 483{
 484        char *commit_id;
 485        char *url;
 486        int arg = 1;
 487
 488        while (arg < argc && argv[arg][0] == '-') {
 489                if (argv[arg][1] == 't') {
 490                        get_tree = 1;
 491                } else if (argv[arg][1] == 'c') {
 492                        get_history = 1;
 493                } else if (argv[arg][1] == 'a') {
 494                        get_all = 1;
 495                        get_tree = 1;
 496                        get_history = 1;
 497                } else if (argv[arg][1] == 'v') {
 498                        get_verbosely = 1;
 499                } else if (argv[arg][1] == 'w') {
 500                        write_ref = argv[arg + 1];
 501                        arg++;
 502                } else if (!strcmp(argv[arg], "--recover")) {
 503                        get_recover = 1;
 504                }
 505                arg++;
 506        }
 507        if (argc < arg + 2) {
 508                usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
 509                return 1;
 510        }
 511        commit_id = argv[arg];
 512        url = argv[arg + 1];
 513
 514        curl_global_init(CURL_GLOBAL_ALL);
 515
 516        curl = curl_easy_init();
 517        no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
 518
 519        curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
 520        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
 521#if LIBCURL_VERSION_NUM >= 0x070907
 522        curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
 523#endif
 524
 525        alt = xmalloc(sizeof(*alt));
 526        alt->base = url;
 527        alt->got_indices = 0;
 528        alt->packs = NULL;
 529        alt->next = NULL;
 530        initial_base = url;
 531
 532        if (pull(commit_id))
 533                return 1;
 534
 535        curl_slist_free_all(no_pragma_header);
 536        curl_global_cleanup();
 537        return 0;
 538}