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