Add support for parallel HTTP transfers
[git.git] / http-fetch.c
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 #define DEFAULT_MAX_REQUESTS 5
10
11 #if LIBCURL_VERSION_NUM < 0x070704
12 #define curl_global_cleanup() do { /* nothing */ } while(0)
13 #endif
14 #if LIBCURL_VERSION_NUM < 0x070800
15 #define curl_global_init(a) do { /* nothing */ } while(0)
16 #endif
17
18 #define PREV_BUF_SIZE 4096
19 #define RANGE_HEADER_SIZE 30
20
21 static int max_requests = DEFAULT_MAX_REQUESTS;
22 static int active_requests = 0;
23 static int data_received;
24
25 static CURLM *curlm;
26 static CURL *curl_default;
27 static struct curl_slist *no_pragma_header;
28 static struct curl_slist *no_range_header;
29 static char curl_errorstr[CURL_ERROR_SIZE];
30
31 struct alt_base
32 {
33         char *base;
34         int got_indices;
35         struct packed_git *packs;
36         struct alt_base *next;
37 };
38
39 static struct alt_base *alt = NULL;
40
41 enum transfer_state {
42         WAITING,
43         ABORTED,
44         ACTIVE,
45         COMPLETE,
46 };
47
48 struct transfer_request
49 {
50         unsigned char sha1[20];
51         struct alt_base *repo;
52         char *url;
53         char filename[PATH_MAX];
54         char tmpfile[PATH_MAX];
55         int local;
56         enum transfer_state state;
57         CURLcode curl_result;
58         char errorstr[CURL_ERROR_SIZE];
59         long http_code;
60         unsigned char real_sha1[20];
61         SHA_CTX c;
62         z_stream stream;
63         int zret;
64         int rename;
65         struct active_request_slot *slot;
66         struct transfer_request *next;
67 };
68
69 struct active_request_slot
70 {
71         CURL *curl;
72         FILE *local;
73         int in_use;
74         int done;
75         CURLcode curl_result;
76         struct active_request_slot *next;
77 };
78
79 static struct transfer_request *request_queue_head = NULL;
80 static struct active_request_slot *active_queue_head = NULL;
81
82 static int curl_ssl_verify;
83 static char *ssl_cert;
84 static char *ssl_key;
85 static char *ssl_capath;
86 static char *ssl_cainfo;
87
88 struct buffer
89 {
90         size_t posn;
91         size_t size;
92         void *buffer;
93 };
94
95 static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
96                             struct buffer *buffer)
97 {
98         size_t size = eltsize * nmemb;
99         if (size > buffer->size - buffer->posn)
100                 size = buffer->size - buffer->posn;
101         memcpy(buffer->buffer + buffer->posn, ptr, size);
102         buffer->posn += size;
103         data_received++;
104         return size;
105 }
106
107 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
108                                void *data)
109 {
110         unsigned char expn[4096];
111         size_t size = eltsize * nmemb;
112         int posn = 0;
113         struct transfer_request *request = (struct transfer_request *)data;
114         do {
115                 ssize_t retval = write(request->local,
116                                        ptr + posn, size - posn);
117                 if (retval < 0)
118                         return posn;
119                 posn += retval;
120         } while (posn < size);
121
122         request->stream.avail_in = size;
123         request->stream.next_in = ptr;
124         do {
125                 request->stream.next_out = expn;
126                 request->stream.avail_out = sizeof(expn);
127                 request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
128                 SHA1_Update(&request->c, expn,
129                             sizeof(expn) - request->stream.avail_out);
130         } while (request->stream.avail_in && request->zret == Z_OK);
131         data_received++;
132         return size;
133 }
134
135 int relink_or_rename(char *old, char *new) {
136         int ret;
137
138         ret = link(old, new);
139         if (ret < 0) {
140                 /* Same Coda hack as in write_sha1_file(sha1_file.c) */
141                 ret = errno;
142                 if (ret == EXDEV && !rename(old, new))
143                         return 0;
144         }
145         unlink(old);
146         if (ret) {
147                 if (ret != EEXIST)
148                         return ret;
149         }
150
151         return 0;
152 }
153
154 void process_curl_messages();
155 void process_request_queue();
156
157 struct active_request_slot *get_active_slot()
158 {
159         struct active_request_slot *slot = active_queue_head;
160         struct active_request_slot *newslot;
161         int num_transfers;
162
163         /* Wait for a slot to open up if the queue is full */
164         while (active_requests >= max_requests) {
165                 curl_multi_perform(curlm, &num_transfers);
166                 if (num_transfers < active_requests) {
167                         process_curl_messages();
168                 }
169         }
170
171         while (slot != NULL && slot->in_use) {
172                 slot = slot->next;
173         }
174         if (slot == NULL) {
175                 newslot = xmalloc(sizeof(*newslot));
176                 newslot->curl = curl_easy_duphandle(curl_default);
177                 newslot->in_use = 0;
178                 newslot->next = NULL;
179
180                 slot = active_queue_head;
181                 if (slot == NULL) {
182                         active_queue_head = newslot;
183                 } else {
184                         while (slot->next != NULL) {
185                                 slot = slot->next;
186                         }
187                         slot->next = newslot;
188                 }
189                 slot = newslot;
190         }
191
192         active_requests++;
193         slot->in_use = 1;
194         slot->done = 0;
195         slot->local = NULL;
196         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
197         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header);
198         curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
199
200         return slot;
201 }
202
203 int start_active_slot(struct active_request_slot *slot)
204 {
205         CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
206
207         if (curlm_result != CURLM_OK &&
208             curlm_result != CURLM_CALL_MULTI_PERFORM) {
209                 active_requests--;
210                 slot->in_use = 0;
211                 return 0;
212         }
213
214         return 1;
215 }
216
217 void run_active_slot(struct active_request_slot *slot)
218 {
219         int num_transfers;
220         long last_pos = 0;
221         long current_pos;
222         fd_set readfds;
223         fd_set writefds;
224         fd_set excfds;
225         int max_fd;
226         struct timeval select_timeout;
227         CURLMcode curlm_result;
228
229         while (!slot->done) {
230                 data_received = 0;
231                 do {
232                         curlm_result = curl_multi_perform(curlm,
233                                                           &num_transfers);
234                 } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
235                 if (num_transfers < active_requests) {
236                         process_curl_messages();
237                         process_request_queue();
238                 }
239
240                 if (!data_received && slot->local != NULL) {
241                         current_pos = ftell(slot->local);
242                         if (current_pos > last_pos)
243                                 data_received++;
244                         last_pos = current_pos;
245                 }
246
247                 if (!slot->done && !data_received) {
248                         max_fd = 0;
249                         FD_ZERO(&readfds);
250                         FD_ZERO(&writefds);
251                         FD_ZERO(&excfds);
252                         select_timeout.tv_sec = 0;
253                         select_timeout.tv_usec = 50000;
254                         select(max_fd, &readfds, &writefds,
255                                &excfds, &select_timeout);
256                 }
257         }
258 }
259
260 void start_request(struct transfer_request *request)
261 {
262         char *hex = sha1_to_hex(request->sha1);
263         char prevfile[PATH_MAX];
264         char *url;
265         char *posn;
266         int prevlocal;
267         unsigned char prev_buf[PREV_BUF_SIZE];
268         ssize_t prev_read = 0;
269         long prev_posn = 0;
270         char range[RANGE_HEADER_SIZE];
271         struct curl_slist *range_header = NULL;
272         struct active_request_slot *slot;
273
274         snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
275         unlink(prevfile);
276         rename(request->tmpfile, prevfile);
277         unlink(request->tmpfile);
278
279         request->local = open(request->tmpfile,
280                               O_WRONLY | O_CREAT | O_EXCL, 0666);
281         if (request->local < 0) {
282                 request->state = ABORTED;
283                 error("Couldn't create temporary file %s for %s: %s\n",
284                       request->tmpfile, request->filename, strerror(errno));
285                 return;
286         }
287
288         memset(&request->stream, 0, sizeof(request->stream));
289
290         inflateInit(&request->stream);
291
292         SHA1_Init(&request->c);
293
294         url = xmalloc(strlen(request->repo->base) + 50);
295         request->url = xmalloc(strlen(request->repo->base) + 50);
296         strcpy(url, request->repo->base);
297         posn = url + strlen(request->repo->base);
298         strcpy(posn, "objects/");
299         posn += 8;
300         memcpy(posn, hex, 2);
301         posn += 2;
302         *(posn++) = '/';
303         strcpy(posn, hex + 2);
304         strcpy(request->url, url);
305
306         /* If a previous temp file is present, process what was already
307            fetched. */
308         prevlocal = open(prevfile, O_RDONLY);
309         if (prevlocal != -1) {
310                 do {
311                         prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
312                         if (prev_read>0) {
313                                 if (fwrite_sha1_file(prev_buf,
314                                                      1,
315                                                      prev_read,
316                                                      request) == prev_read) {
317                                         prev_posn += prev_read;
318                                 } else {
319                                         prev_read = -1;
320                                 }
321                         }
322                 } while (prev_read > 0);
323                 close(prevlocal);
324         }
325         unlink(prevfile);
326
327         /* Reset inflate/SHA1 if there was an error reading the previous temp
328            file; also rewind to the beginning of the local file. */
329         if (prev_read == -1) {
330                 memset(&request->stream, 0, sizeof(request->stream));
331                 inflateInit(&request->stream);
332                 SHA1_Init(&request->c);
333                 if (prev_posn>0) {
334                         prev_posn = 0;
335                         lseek(request->local, SEEK_SET, 0);
336                         ftruncate(request->local, 0);
337                 }
338         }
339
340         slot = get_active_slot();
341         curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
342         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
343         curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
344         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
345
346         /* If we have successfully processed data from a previous fetch
347            attempt, only fetch the data we don't already have. */
348         if (prev_posn>0) {
349                 if (get_verbosely)
350                         fprintf(stderr,
351                                 "Resuming fetch of object %s at byte %ld\n",
352                                 hex, prev_posn);
353                 sprintf(range, "Range: bytes=%ld-", prev_posn);
354                 range_header = curl_slist_append(range_header, range);
355                 curl_easy_setopt(slot->curl,
356                                  CURLOPT_HTTPHEADER, range_header);
357         }
358
359         /* Try to add to multi handle, abort the request on error */
360         if (!start_active_slot(slot)) {
361                 request->state = ABORTED;
362                 close(request->local);
363                 free(request->url);
364                 return;
365         }
366         
367         request->slot = slot;
368         request->state = ACTIVE;
369 }
370
371 void finish_request(struct transfer_request *request)
372 {
373         fchmod(request->local, 0444);
374         close(request->local);
375
376         if (request->http_code == 416) {
377                 fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
378         } else if (request->curl_result != CURLE_OK) {
379                 return;
380         }
381
382         inflateEnd(&request->stream);
383         SHA1_Final(request->real_sha1, &request->c);
384         if (request->zret != Z_STREAM_END) {
385                 unlink(request->tmpfile);
386                 return;
387         }
388         if (memcmp(request->sha1, request->real_sha1, 20)) {
389                 unlink(request->tmpfile);
390                 return;
391         }
392         request->rename =
393                 relink_or_rename(request->tmpfile, request->filename);
394
395         if (request->rename == 0)
396                 pull_say("got %s\n", sha1_to_hex(request->sha1));
397 }
398
399 void release_request(struct transfer_request *request)
400 {
401         struct transfer_request *entry = request_queue_head;
402
403         if (request == request_queue_head) {
404                 request_queue_head = request->next;
405         } else {
406                 while (entry->next != NULL && entry->next != request)
407                         entry = entry->next;
408                 if (entry->next == request)
409                         entry->next = entry->next->next;
410         }
411
412         free(request->url);
413         free(request);
414 }
415
416 void process_curl_messages()
417 {
418         int num_messages;
419         struct active_request_slot *slot;
420         struct transfer_request *request = NULL;
421         CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
422
423         while (curl_message != NULL) {
424                 if (curl_message->msg == CURLMSG_DONE) {
425                         slot = active_queue_head;
426                         while (slot != NULL &&
427                                slot->curl != curl_message->easy_handle)
428                                 slot = slot->next;
429                         if (slot != NULL) {
430                                 curl_multi_remove_handle(curlm, slot->curl);
431                                 active_requests--;
432                                 slot->done = 1;
433                                 slot->in_use = 0;
434                                 slot->curl_result = curl_message->data.result;
435                                 request = request_queue_head;
436                                 while (request != NULL &&
437                                        request->slot != slot)
438                                         request = request->next;
439                         } else {
440                                 fprintf(stderr, "Received DONE message for unknown request!\n");
441                         }
442                         if (request != NULL) {
443                                 request->curl_result =
444                                         curl_message->data.result;
445                                 curl_easy_getinfo(slot->curl,
446                                                   CURLINFO_HTTP_CODE,
447                                                   &request->http_code);
448                                 request->slot = NULL;
449
450                                 /* Use alternates if necessary */
451                                 if (request->http_code == 404 &&
452                                     request->repo->next != NULL) {
453                                         request->repo = request->repo->next;
454                                         start_request(request);
455                                 } else {
456                                         finish_request(request);
457                                         request->state = COMPLETE;
458                                 }
459                         }
460                 } else {
461                         fprintf(stderr, "Unknown CURL message received: %d\n",
462                                 (int)curl_message->msg);
463                 }
464                 curl_message = curl_multi_info_read(curlm, &num_messages);
465         }
466 }
467
468 void process_request_queue()
469 {
470         struct transfer_request *request = request_queue_head;
471         int num_transfers;
472
473         while (active_requests < max_requests && request != NULL) {
474                 if (request->state == WAITING) {
475                         start_request(request);
476                         curl_multi_perform(curlm, &num_transfers);
477                 }
478                 request = request->next;
479         }
480 }
481
482 void prefetch(unsigned char *sha1)
483 {
484         struct transfer_request *newreq;
485         struct transfer_request *tail;
486         char *filename = sha1_file_name(sha1);
487
488         newreq = xmalloc(sizeof(*newreq));
489         memcpy(newreq->sha1, sha1, 20);
490         newreq->repo = alt;
491         newreq->url = NULL;
492         newreq->local = -1;
493         newreq->state = WAITING;
494         snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
495         snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
496                  "%s.temp", filename);
497         newreq->next = NULL;
498
499         if (request_queue_head == NULL) {
500                 request_queue_head = newreq;
501         } else {
502                 tail = request_queue_head;
503                 while (tail->next != NULL) {
504                         tail = tail->next;
505                 }
506                 tail->next = newreq;
507         }
508         process_request_queue();
509         process_curl_messages();
510 }
511
512 static int got_alternates = 0;
513
514 static int fetch_index(struct alt_base *repo, unsigned char *sha1)
515 {
516         char *hex = sha1_to_hex(sha1);
517         char *filename;
518         char *url;
519         char tmpfile[PATH_MAX];
520         int ret;
521         long prev_posn = 0;
522         char range[RANGE_HEADER_SIZE];
523         struct curl_slist *range_header = NULL;
524
525         FILE *indexfile;
526         struct active_request_slot *slot;
527
528         if (has_pack_index(sha1))
529                 return 0;
530
531         if (get_verbosely)
532                 fprintf(stderr, "Getting index for pack %s\n", hex);
533         
534         url = xmalloc(strlen(repo->base) + 64);
535         sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
536         
537         filename = sha1_pack_index_name(sha1);
538         snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
539         indexfile = fopen(tmpfile, "a");
540         if (!indexfile)
541                 return error("Unable to open local file %s for pack index",
542                              filename);
543
544         slot = get_active_slot();
545         curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
546         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
547         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
548         slot->local = indexfile;
549
550         /* If there is data present from a previous transfer attempt,
551            resume where it left off */
552         prev_posn = ftell(indexfile);
553         if (prev_posn>0) {
554                 if (get_verbosely)
555                         fprintf(stderr,
556                                 "Resuming fetch of index for pack %s at byte %ld\n",
557                                 hex, prev_posn);
558                 sprintf(range, "Range: bytes=%ld-", prev_posn);
559                 range_header = curl_slist_append(range_header, range);
560                 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
561         }
562
563         if (start_active_slot(slot)) {
564                 run_active_slot(slot);
565                 if (slot->curl_result != CURLE_OK) {
566                         fclose(indexfile);
567                         return error("Unable to get pack index %s\n%s", url,
568                                      curl_errorstr);
569                 }
570         } else {
571                 return error("Unable to start request");
572         }
573
574         fclose(indexfile);
575
576         ret = relink_or_rename(tmpfile, filename);
577         if (ret)
578                 return error("unable to write index filename %s: %s",
579                              filename, strerror(ret));
580
581         return 0;
582 }
583
584 static int setup_index(struct alt_base *repo, unsigned char *sha1)
585 {
586         struct packed_git *new_pack;
587         if (has_pack_file(sha1))
588                 return 0; // don't list this as something we can get
589
590         if (fetch_index(repo, sha1))
591                 return -1;
592
593         new_pack = parse_pack_index(sha1);
594         new_pack->next = repo->packs;
595         repo->packs = new_pack;
596         return 0;
597 }
598
599 static int fetch_alternates(char *base)
600 {
601         int ret = 0;
602         struct buffer buffer;
603         char *url;
604         char *data;
605         int i = 0;
606         int http_specific = 1;
607         struct alt_base *tail = alt;
608
609         struct active_request_slot *slot;
610         if (got_alternates)
611                 return 0;
612         data = xmalloc(4096);
613         buffer.size = 4095;
614         buffer.posn = 0;
615         buffer.buffer = data;
616
617         if (get_verbosely)
618                 fprintf(stderr, "Getting alternates list\n");
619         
620         url = xmalloc(strlen(base) + 31);
621         sprintf(url, "%s/objects/info/http-alternates", base);
622
623         slot = get_active_slot();
624         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
625         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
626         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
627         if (start_active_slot(slot)) {
628                 run_active_slot(slot);
629                 if (slot->curl_result != CURLE_OK || !buffer.posn) {
630                         http_specific = 0;
631
632                         sprintf(url, "%s/objects/info/alternates", base);
633
634                         slot = get_active_slot();
635                         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
636                         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
637                                          fwrite_buffer);
638                         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
639                         if (start_active_slot(slot)) {
640                                 run_active_slot(slot);
641                                 if (slot->curl_result != CURLE_OK) {
642                                         return 0;
643                                 }
644                         }
645                 }
646         } else {
647                 return 0;
648         }
649
650         data[buffer.posn] = '\0';
651
652         while (i < buffer.posn) {
653                 int posn = i;
654                 while (posn < buffer.posn && data[posn] != '\n')
655                         posn++;
656                 if (data[posn] == '\n') {
657                         int okay = 0;
658                         int serverlen = 0;
659                         struct alt_base *newalt;
660                         char *target = NULL;
661                         if (data[i] == '/') {
662                                 serverlen = strchr(base + 8, '/') - base;
663                                 okay = 1;
664                         } else if (!memcmp(data + i, "../", 3)) {
665                                 i += 3;
666                                 serverlen = strlen(base);
667                                 while (i + 2 < posn && 
668                                        !memcmp(data + i, "../", 3)) {
669                                         do {
670                                                 serverlen--;
671                                         } while (serverlen &&
672                                                  base[serverlen - 1] != '/');
673                                         i += 3;
674                                 }
675                                 // If the server got removed, give up.
676                                 okay = strchr(base, ':') - base + 3 < 
677                                         serverlen;
678                         } else if (http_specific) {
679                                 char *colon = strchr(data + i, ':');
680                                 char *slash = strchr(data + i, '/');
681                                 if (colon && slash && colon < data + posn &&
682                                     slash < data + posn && colon < slash) {
683                                         okay = 1;
684                                 }
685                         }
686                         // skip 'objects' at end
687                         if (okay) {
688                                 target = xmalloc(serverlen + posn - i - 6);
689                                 strncpy(target, base, serverlen);
690                                 strncpy(target + serverlen, data + i,
691                                         posn - i - 7);
692                                 target[serverlen + posn - i - 7] = '\0';
693                                 if (get_verbosely)
694                                         fprintf(stderr, 
695                                                 "Also look at %s\n", target);
696                                 newalt = xmalloc(sizeof(*newalt));
697                                 newalt->next = NULL;
698                                 newalt->base = target;
699                                 newalt->got_indices = 0;
700                                 newalt->packs = NULL;
701                                 while (tail->next != NULL)
702                                         tail = tail->next;
703                                 tail->next = newalt;
704                                 ret++;
705                         }
706                 }
707                 i = posn + 1;
708         }
709         got_alternates = 1;
710         
711         return ret;
712 }
713
714 static int fetch_indices(struct alt_base *repo)
715 {
716         unsigned char sha1[20];
717         char *url;
718         struct buffer buffer;
719         char *data;
720         int i = 0;
721
722         struct active_request_slot *slot;
723
724         if (repo->got_indices)
725                 return 0;
726
727         data = xmalloc(4096);
728         buffer.size = 4096;
729         buffer.posn = 0;
730         buffer.buffer = data;
731
732         if (get_verbosely)
733                 fprintf(stderr, "Getting pack list\n");
734         
735         url = xmalloc(strlen(repo->base) + 21);
736         sprintf(url, "%s/objects/info/packs", repo->base);
737
738         slot = get_active_slot();
739         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
740         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
741         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
742         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
743         if (start_active_slot(slot)) {
744                 run_active_slot(slot);
745                 if (slot->curl_result != CURLE_OK)
746                         return error("%s", curl_errorstr);
747         } else {
748                 return error("Unable to start request");
749         }
750
751         while (i < buffer.posn) {
752                 switch (data[i]) {
753                 case 'P':
754                         i++;
755                         if (i + 52 < buffer.posn &&
756                             !strncmp(data + i, " pack-", 6) &&
757                             !strncmp(data + i + 46, ".pack\n", 6)) {
758                                 get_sha1_hex(data + i + 6, sha1);
759                                 setup_index(repo, sha1);
760                                 i += 51;
761                                 break;
762                         }
763                 default:
764                         while (data[i] != '\n')
765                                 i++;
766                 }
767                 i++;
768         }
769
770         repo->got_indices = 1;
771         return 0;
772 }
773
774 static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
775 {
776         char *url;
777         struct packed_git *target;
778         struct packed_git **lst;
779         FILE *packfile;
780         char *filename;
781         char tmpfile[PATH_MAX];
782         int ret;
783         long prev_posn = 0;
784         char range[RANGE_HEADER_SIZE];
785         struct curl_slist *range_header = NULL;
786
787         struct active_request_slot *slot;
788
789         if (fetch_indices(repo))
790                 return -1;
791         target = find_sha1_pack(sha1, repo->packs);
792         if (!target)
793                 return -1;
794
795         if (get_verbosely) {
796                 fprintf(stderr, "Getting pack %s\n",
797                         sha1_to_hex(target->sha1));
798                 fprintf(stderr, " which contains %s\n",
799                         sha1_to_hex(sha1));
800         }
801
802         url = xmalloc(strlen(repo->base) + 65);
803         sprintf(url, "%s/objects/pack/pack-%s.pack",
804                 repo->base, sha1_to_hex(target->sha1));
805
806         filename = sha1_pack_name(target->sha1);
807         snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
808         packfile = fopen(tmpfile, "a");
809         if (!packfile)
810                 return error("Unable to open local file %s for pack",
811                              filename);
812
813         slot = get_active_slot();
814         curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
815         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
816         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
817         slot->local = packfile;
818
819         /* If there is data present from a previous transfer attempt,
820            resume where it left off */
821         prev_posn = ftell(packfile);
822         if (prev_posn>0) {
823                 if (get_verbosely)
824                         fprintf(stderr,
825                                 "Resuming fetch of pack %s at byte %ld\n",
826                                 sha1_to_hex(target->sha1), prev_posn);
827                 sprintf(range, "Range: bytes=%ld-", prev_posn);
828                 range_header = curl_slist_append(range_header, range);
829                 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
830         }
831
832         if (start_active_slot(slot)) {
833                 run_active_slot(slot);
834                 if (slot->curl_result != CURLE_OK) {
835                         fclose(packfile);
836                         return error("Unable to get pack file %s\n%s", url,
837                                      curl_errorstr);
838                 }
839         } else {
840                 return error("Unable to start request");
841         }
842
843         fclose(packfile);
844
845         ret = relink_or_rename(tmpfile, filename);
846         if (ret)
847                 return error("unable to write pack filename %s: %s",
848                              filename, strerror(ret));
849
850         lst = &repo->packs;
851         while (*lst != target)
852                 lst = &((*lst)->next);
853         *lst = (*lst)->next;
854
855         if (verify_pack(target, 0))
856                 return -1;
857         install_packed_git(target);
858
859         return 0;
860 }
861
862 static int fetch_object(struct alt_base *repo, unsigned char *sha1)
863 {
864         char *hex = sha1_to_hex(sha1);
865         int ret;
866         struct transfer_request *request = request_queue_head;
867         int num_transfers;
868
869         while (request != NULL && memcmp(request->sha1, sha1, 20))
870                 request = request->next;
871         if (request == NULL)
872                 return error("Couldn't find request for %s in the queue", hex);
873
874         while (request->state == WAITING) {
875                 curl_multi_perform(curlm, &num_transfers);
876                 if (num_transfers < active_requests) {
877                         process_curl_messages();
878                         process_request_queue();
879                 }
880         }
881
882         if (request->state == ACTIVE)
883                 run_active_slot(request->slot);
884
885         if (request->state == ABORTED) {
886                 release_request(request);
887                 return error("Request for %s aborted", hex);
888         }
889
890         if (request->curl_result != CURLE_OK && request->http_code != 416) {
891                 ret = error("%s", request->errorstr);
892                 release_request(request);
893                 return ret;
894         }
895
896         if (request->zret != Z_STREAM_END) {
897                 ret = error("File %s (%s) corrupt\n", hex, request->url);
898                 release_request(request);
899                 return ret;
900         }
901
902         if (memcmp(request->sha1, request->real_sha1, 20)) {
903                 release_request(request);
904                 return error("File %s has bad hash\n", hex);
905         }
906
907         if (request->rename < 0) {
908                 ret = error("unable to write sha1 filename %s: %s",
909                             request->filename,
910                             strerror(request->rename));
911                 release_request(request);
912                 return ret;
913         }
914
915         release_request(request);
916         return 0;
917 }
918
919 int fetch(unsigned char *sha1)
920 {
921         struct alt_base *altbase = alt;
922
923         if (!fetch_object(altbase, sha1))
924                 return 0;
925         while (altbase) {
926                 if (!fetch_pack(altbase, sha1))
927                         return 0;
928                 altbase = altbase->next;
929         }
930         return error("Unable to find %s under %s\n", sha1_to_hex(sha1), 
931                      alt->base);
932 }
933
934 int fetch_ref(char *ref, unsigned char *sha1)
935 {
936         char *url, *posn;
937         char hex[42];
938         struct buffer buffer;
939         char *base = alt->base;
940         struct active_request_slot *slot;
941         buffer.size = 41;
942         buffer.posn = 0;
943         buffer.buffer = hex;
944         hex[41] = '\0';
945         
946         url = xmalloc(strlen(base) + 6 + strlen(ref));
947         strcpy(url, base);
948         posn = url + strlen(base);
949         strcpy(posn, "refs/");
950         posn += 5;
951         strcpy(posn, ref);
952
953         slot = get_active_slot();
954         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
955         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
956         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
957         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
958         if (start_active_slot(slot)) {
959                 run_active_slot(slot);
960                 if (slot->curl_result != CURLE_OK)
961                         return error("Couldn't get %s for %s\n%s",
962                                      url, ref, curl_errorstr);
963         } else {
964                 return error("Unable to start request");
965         }
966
967         hex[40] = '\0';
968         get_sha1_hex(hex, sha1);
969         return 0;
970 }
971
972 int main(int argc, char **argv)
973 {
974         char *commit_id;
975         char *url;
976         int arg = 1;
977         struct active_request_slot *slot;
978
979         while (arg < argc && argv[arg][0] == '-') {
980                 if (argv[arg][1] == 't') {
981                         get_tree = 1;
982                 } else if (argv[arg][1] == 'c') {
983                         get_history = 1;
984                 } else if (argv[arg][1] == 'a') {
985                         get_all = 1;
986                         get_tree = 1;
987                         get_history = 1;
988                 } else if (argv[arg][1] == 'v') {
989                         get_verbosely = 1;
990                 } else if (argv[arg][1] == 'w') {
991                         write_ref = argv[arg + 1];
992                         arg++;
993                 } else if (!strcmp(argv[arg], "--recover")) {
994                         get_recover = 1;
995                 } else if (argv[arg][1] == 'r') {
996                         max_requests = atoi(argv[arg + 1]);
997                         if (max_requests < 1)
998                                 max_requests = DEFAULT_MAX_REQUESTS;
999                         arg++;
1000                 }
1001                 arg++;
1002         }
1003         if (argc < arg + 2) {
1004                 usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
1005                 return 1;
1006         }
1007         commit_id = argv[arg];
1008         url = argv[arg + 1];
1009
1010         curl_global_init(CURL_GLOBAL_ALL);
1011
1012         curlm = curl_multi_init();
1013         if (curlm == NULL) {
1014                 fprintf(stderr, "Error creating curl multi handle.\n");
1015                 return 1;
1016         }
1017         no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
1018         no_range_header = curl_slist_append(no_range_header, "Range:");
1019
1020         curl_default = curl_easy_init();
1021
1022         curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
1023         curl_easy_setopt(curl_default, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
1024 #if LIBCURL_VERSION_NUM >= 0x070907
1025         curl_easy_setopt(curl_default, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
1026 #endif
1027
1028         if ((ssl_cert = getenv("GIT_SSL_CERT")) != NULL) {
1029                 curl_easy_setopt(curl_default, CURLOPT_SSLCERT, ssl_cert);
1030         }
1031 #if LIBCURL_VERSION_NUM >= 0x070902
1032         if ((ssl_key = getenv("GIT_SSL_KEY")) != NULL) {
1033                 curl_easy_setopt(curl_default, CURLOPT_SSLKEY, ssl_key);
1034         }
1035 #endif
1036 #if LIBCURL_VERSION_NUM >= 0x070908
1037         if ((ssl_capath = getenv("GIT_SSL_CAPATH")) != NULL) {
1038                 curl_easy_setopt(curl_default, CURLOPT_CAPATH, ssl_capath);
1039         }
1040 #endif
1041         if ((ssl_cainfo = getenv("GIT_SSL_CAINFO")) != NULL) {
1042                 curl_easy_setopt(curl_default, CURLOPT_CAINFO, ssl_cainfo);
1043         }
1044         curl_easy_setopt(curl_default, CURLOPT_FAILONERROR, 1);
1045
1046         alt = xmalloc(sizeof(*alt));
1047         alt->base = url;
1048         alt->got_indices = 0;
1049         alt->packs = NULL;
1050         alt->next = NULL;
1051         fetch_alternates(alt->base);
1052
1053         if (pull(commit_id))
1054                 return 1;
1055
1056         curl_slist_free_all(no_pragma_header);
1057         curl_slist_free_all(no_range_header);
1058         curl_easy_cleanup(curl_default);
1059         slot = active_queue_head;
1060         while (slot != NULL) {
1061                 curl_easy_cleanup(slot->curl);
1062                 slot = slot->next;
1063         }
1064         curl_multi_cleanup(curlm);
1065         curl_global_cleanup();
1066         return 0;
1067 }