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