Git fork
at reftables-rust 635 lines 16 kB view raw
1#define USE_THE_REPOSITORY_VARIABLE 2#define DISABLE_SIGN_COMPARE_WARNINGS 3 4#include "git-compat-util.h" 5#include "repository.h" 6#include "hex.h" 7#include "walker.h" 8#include "http.h" 9#include "list.h" 10#include "transport.h" 11#include "packfile.h" 12#include "object-file.h" 13#include "odb.h" 14 15struct alt_base { 16 char *base; 17 int got_indices; 18 struct packed_git *packs; 19 struct alt_base *next; 20}; 21 22enum object_request_state { 23 WAITING, 24 ABORTED, 25 ACTIVE, 26 COMPLETE 27}; 28 29struct object_request { 30 struct walker *walker; 31 struct object_id oid; 32 struct alt_base *repo; 33 enum object_request_state state; 34 struct http_object_request *req; 35 struct list_head node; 36}; 37 38struct alternates_request { 39 struct walker *walker; 40 const char *base; 41 struct strbuf *url; 42 struct strbuf *buffer; 43 struct active_request_slot *slot; 44 int http_specific; 45}; 46 47struct walker_data { 48 const char *url; 49 int got_alternates; 50 struct alt_base *alt; 51}; 52 53static LIST_HEAD(object_queue_head); 54 55static void fetch_alternates(struct walker *walker, const char *base); 56 57static void process_object_response(void *callback_data); 58 59static void start_object_request(struct object_request *obj_req) 60{ 61 struct active_request_slot *slot; 62 struct http_object_request *req; 63 64 req = new_http_object_request(obj_req->repo->base, &obj_req->oid); 65 if (!req) { 66 obj_req->state = ABORTED; 67 return; 68 } 69 obj_req->req = req; 70 71 slot = req->slot; 72 slot->callback_func = process_object_response; 73 slot->callback_data = obj_req; 74 75 /* Try to get the request started, abort the request on error */ 76 obj_req->state = ACTIVE; 77 if (!start_active_slot(slot)) { 78 obj_req->state = ABORTED; 79 release_http_object_request(&req); 80 return; 81 } 82} 83 84static void finish_object_request(struct object_request *obj_req) 85{ 86 if (finish_http_object_request(obj_req->req)) 87 return; 88 89 if (obj_req->req->rename == 0) 90 walker_say(obj_req->walker, "got %s\n", oid_to_hex(&obj_req->oid)); 91} 92 93static void process_object_response(void *callback_data) 94{ 95 struct object_request *obj_req = 96 (struct object_request *)callback_data; 97 struct walker *walker = obj_req->walker; 98 struct walker_data *data = walker->data; 99 struct alt_base *alt = data->alt; 100 101 process_http_object_request(obj_req->req); 102 obj_req->state = COMPLETE; 103 104 normalize_curl_result(&obj_req->req->curl_result, 105 obj_req->req->http_code, 106 obj_req->req->errorstr, 107 sizeof(obj_req->req->errorstr)); 108 109 /* Use alternates if necessary */ 110 if (missing_target(obj_req->req)) { 111 fetch_alternates(walker, alt->base); 112 if (obj_req->repo->next) { 113 obj_req->repo = 114 obj_req->repo->next; 115 release_http_object_request(&obj_req->req); 116 start_object_request(obj_req); 117 return; 118 } 119 } 120 121 finish_object_request(obj_req); 122} 123 124static void release_object_request(struct object_request *obj_req) 125{ 126 if (obj_req->req !=NULL && obj_req->req->localfile != -1) 127 error("fd leakage in release: %d", obj_req->req->localfile); 128 129 list_del(&obj_req->node); 130 free(obj_req); 131} 132 133static int fill_active_slot(void *data UNUSED) 134{ 135 struct object_request *obj_req; 136 struct list_head *pos, *tmp, *head = &object_queue_head; 137 138 list_for_each_safe(pos, tmp, head) { 139 obj_req = list_entry(pos, struct object_request, node); 140 if (obj_req->state == WAITING) { 141 if (odb_has_object(the_repository->objects, &obj_req->oid, 142 HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) 143 obj_req->state = COMPLETE; 144 else { 145 start_object_request(obj_req); 146 return 1; 147 } 148 } 149 } 150 return 0; 151} 152 153static void prefetch(struct walker *walker, const struct object_id *oid) 154{ 155 struct object_request *newreq; 156 struct walker_data *data = walker->data; 157 158 newreq = xmalloc(sizeof(*newreq)); 159 newreq->walker = walker; 160 oidcpy(&newreq->oid, oid); 161 newreq->repo = data->alt; 162 newreq->state = WAITING; 163 newreq->req = NULL; 164 165 http_is_verbose = walker->get_verbosely; 166 list_add_tail(&newreq->node, &object_queue_head); 167 168 fill_active_slots(); 169 step_active_slots(); 170} 171 172static int is_alternate_allowed(const char *url) 173{ 174 const char *protocols[] = { 175 "http", "https", "ftp", "ftps" 176 }; 177 int i; 178 179 if (http_follow_config != HTTP_FOLLOW_ALWAYS) { 180 warning("alternate disabled by http.followRedirects: %s", url); 181 return 0; 182 } 183 184 for (i = 0; i < ARRAY_SIZE(protocols); i++) { 185 const char *end; 186 if (skip_prefix(url, protocols[i], &end) && 187 starts_with(end, "://")) 188 break; 189 } 190 191 if (i >= ARRAY_SIZE(protocols)) { 192 warning("ignoring alternate with unknown protocol: %s", url); 193 return 0; 194 } 195 if (!is_transport_allowed(protocols[i], 0)) { 196 warning("ignoring alternate with restricted protocol: %s", url); 197 return 0; 198 } 199 200 return 1; 201} 202 203static void process_alternates_response(void *callback_data) 204{ 205 struct alternates_request *alt_req = 206 (struct alternates_request *)callback_data; 207 struct walker *walker = alt_req->walker; 208 struct walker_data *cdata = walker->data; 209 struct active_request_slot *slot = alt_req->slot; 210 struct alt_base *tail = cdata->alt; 211 const char *base = alt_req->base; 212 const char null_byte = '\0'; 213 char *data; 214 int i = 0; 215 216 normalize_curl_result(&slot->curl_result, slot->http_code, 217 curl_errorstr, sizeof(curl_errorstr)); 218 219 if (alt_req->http_specific) { 220 if (slot->curl_result != CURLE_OK || 221 !alt_req->buffer->len) { 222 223 /* Try reusing the slot to get non-http alternates */ 224 alt_req->http_specific = 0; 225 strbuf_reset(alt_req->url); 226 strbuf_addf(alt_req->url, "%s/objects/info/alternates", 227 base); 228 curl_easy_setopt(slot->curl, CURLOPT_URL, 229 alt_req->url->buf); 230 active_requests++; 231 slot->in_use = 1; 232 if (slot->finished) 233 (*slot->finished) = 0; 234 if (!start_active_slot(slot)) { 235 cdata->got_alternates = -1; 236 slot->in_use = 0; 237 if (slot->finished) 238 (*slot->finished) = 1; 239 } 240 return; 241 } 242 } else if (slot->curl_result != CURLE_OK) { 243 if (!missing_target(slot)) { 244 cdata->got_alternates = -1; 245 return; 246 } 247 } 248 249 fwrite_buffer((char *)&null_byte, 1, 1, alt_req->buffer); 250 alt_req->buffer->len--; 251 data = alt_req->buffer->buf; 252 253 while (i < alt_req->buffer->len) { 254 int posn = i; 255 while (posn < alt_req->buffer->len && data[posn] != '\n') 256 posn++; 257 if (data[posn] == '\n') { 258 int okay = 0; 259 int serverlen = 0; 260 struct alt_base *newalt; 261 if (data[i] == '/') { 262 /* 263 * This counts 264 * http://git.host/pub/scm/linux.git/ 265 * -----------here^ 266 * so memcpy(dst, base, serverlen) will 267 * copy up to "...git.host". 268 */ 269 const char *colon_ss = strstr(base,"://"); 270 if (colon_ss) { 271 serverlen = (strchr(colon_ss + 3, '/') 272 - base); 273 okay = 1; 274 } 275 } else if (!memcmp(data + i, "../", 3)) { 276 /* 277 * Relative URL; chop the corresponding 278 * number of subpath from base (and ../ 279 * from data), and concatenate the result. 280 * 281 * The code first drops ../ from data, and 282 * then drops one ../ from data and one path 283 * from base. IOW, one extra ../ is dropped 284 * from data than path is dropped from base. 285 * 286 * This is not wrong. The alternate in 287 * http://git.host/pub/scm/linux.git/ 288 * to borrow from 289 * http://git.host/pub/scm/linus.git/ 290 * is ../../linus.git/objects/. You need 291 * two ../../ to borrow from your direct 292 * neighbour. 293 */ 294 i += 3; 295 serverlen = strlen(base); 296 while (i + 2 < posn && 297 !memcmp(data + i, "../", 3)) { 298 do { 299 serverlen--; 300 } while (serverlen && 301 base[serverlen - 1] != '/'); 302 i += 3; 303 } 304 /* If the server got removed, give up. */ 305 okay = strchr(base, ':') - base + 3 < 306 serverlen; 307 } else if (alt_req->http_specific) { 308 char *colon = strchr(data + i, ':'); 309 char *slash = strchr(data + i, '/'); 310 if (colon && slash && colon < data + posn && 311 slash < data + posn && colon < slash) { 312 okay = 1; 313 } 314 } 315 if (okay) { 316 struct strbuf target = STRBUF_INIT; 317 strbuf_add(&target, base, serverlen); 318 strbuf_add(&target, data + i, posn - i); 319 if (!strbuf_strip_suffix(&target, "objects")) { 320 warning("ignoring alternate that does" 321 " not end in 'objects': %s", 322 target.buf); 323 strbuf_release(&target); 324 } else if (is_alternate_allowed(target.buf)) { 325 warning("adding alternate object store: %s", 326 target.buf); 327 newalt = xmalloc(sizeof(*newalt)); 328 newalt->next = NULL; 329 newalt->base = strbuf_detach(&target, NULL); 330 newalt->got_indices = 0; 331 newalt->packs = NULL; 332 333 while (tail->next != NULL) 334 tail = tail->next; 335 tail->next = newalt; 336 } else { 337 strbuf_release(&target); 338 } 339 } 340 } 341 i = posn + 1; 342 } 343 344 cdata->got_alternates = 1; 345} 346 347static void fetch_alternates(struct walker *walker, const char *base) 348{ 349 struct strbuf buffer = STRBUF_INIT; 350 struct strbuf url = STRBUF_INIT; 351 struct active_request_slot *slot; 352 struct alternates_request alt_req; 353 struct walker_data *cdata = walker->data; 354 355 /* 356 * If another request has already started fetching alternates, 357 * wait for them to arrive and return to processing this request's 358 * curl message 359 */ 360 while (cdata->got_alternates == 0) { 361 step_active_slots(); 362 } 363 364 /* Nothing to do if they've already been fetched */ 365 if (cdata->got_alternates == 1) 366 return; 367 368 /* Start the fetch */ 369 cdata->got_alternates = 0; 370 371 if (walker->get_verbosely) 372 fprintf(stderr, "Getting alternates list for %s\n", base); 373 374 strbuf_addf(&url, "%s/objects/info/http-alternates", base); 375 376 /* 377 * Use a callback to process the result, since another request 378 * may fail and need to have alternates loaded before continuing 379 */ 380 slot = get_active_slot(); 381 slot->callback_func = process_alternates_response; 382 alt_req.walker = walker; 383 slot->callback_data = &alt_req; 384 385 curl_easy_setopt(slot->curl, CURLOPT_WRITEDATA, &buffer); 386 curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); 387 curl_easy_setopt(slot->curl, CURLOPT_URL, url.buf); 388 389 alt_req.base = base; 390 alt_req.url = &url; 391 alt_req.buffer = &buffer; 392 alt_req.http_specific = 1; 393 alt_req.slot = slot; 394 395 if (start_active_slot(slot)) 396 run_active_slot(slot); 397 else 398 cdata->got_alternates = -1; 399 400 strbuf_release(&buffer); 401 strbuf_release(&url); 402} 403 404static int fetch_indices(struct walker *walker, struct alt_base *repo) 405{ 406 int ret; 407 408 if (repo->got_indices) 409 return 0; 410 411 if (walker->get_verbosely) 412 fprintf(stderr, "Getting pack list for %s\n", repo->base); 413 414 switch (http_get_info_packs(repo->base, &repo->packs)) { 415 case HTTP_OK: 416 case HTTP_MISSING_TARGET: 417 repo->got_indices = 1; 418 ret = 0; 419 break; 420 default: 421 repo->got_indices = 0; 422 ret = -1; 423 } 424 425 return ret; 426} 427 428static int http_fetch_pack(struct walker *walker, struct alt_base *repo, 429 const struct object_id *oid) 430{ 431 struct packed_git *target; 432 int ret; 433 struct slot_results results; 434 struct http_pack_request *preq; 435 436 if (fetch_indices(walker, repo)) 437 return -1; 438 target = find_oid_pack(oid, repo->packs); 439 if (!target) 440 return -1; 441 close_pack_index(target); 442 443 if (walker->get_verbosely) { 444 fprintf(stderr, "Getting pack %s\n", 445 hash_to_hex(target->hash)); 446 fprintf(stderr, " which contains %s\n", 447 oid_to_hex(oid)); 448 } 449 450 preq = new_http_pack_request(target->hash, repo->base); 451 if (!preq) 452 goto abort; 453 preq->slot->results = &results; 454 455 if (start_active_slot(preq->slot)) { 456 run_active_slot(preq->slot); 457 if (results.curl_result != CURLE_OK) { 458 error("Unable to get pack file %s\n%s", preq->url, 459 curl_errorstr); 460 goto abort; 461 } 462 } else { 463 error("Unable to start request"); 464 goto abort; 465 } 466 467 ret = finish_http_pack_request(preq); 468 release_http_pack_request(preq); 469 if (ret) 470 return ret; 471 http_install_packfile(target, &repo->packs); 472 473 return 0; 474 475abort: 476 return -1; 477} 478 479static void abort_object_request(struct object_request *obj_req) 480{ 481 release_object_request(obj_req); 482} 483 484static int fetch_object(struct walker *walker, const struct object_id *oid) 485{ 486 char *hex = oid_to_hex(oid); 487 int ret = 0; 488 struct object_request *obj_req = NULL; 489 struct http_object_request *req; 490 struct list_head *pos, *head = &object_queue_head; 491 492 list_for_each(pos, head) { 493 obj_req = list_entry(pos, struct object_request, node); 494 if (oideq(&obj_req->oid, oid)) 495 break; 496 } 497 if (!obj_req) 498 return error("Couldn't find request for %s in the queue", hex); 499 500 if (odb_has_object(the_repository->objects, &obj_req->oid, 501 HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) { 502 if (obj_req->req) 503 abort_http_object_request(&obj_req->req); 504 abort_object_request(obj_req); 505 return 0; 506 } 507 508 while (obj_req->state == WAITING) 509 step_active_slots(); 510 511 /* 512 * obj_req->req might change when fetching alternates in the callback 513 * process_object_response; therefore, the "shortcut" variable, req, 514 * is used only after we're done with slots. 515 */ 516 while (obj_req->state == ACTIVE) 517 run_active_slot(obj_req->req->slot); 518 519 req = obj_req->req; 520 521 if (req->localfile != -1) { 522 close(req->localfile); 523 req->localfile = -1; 524 } 525 526 normalize_curl_result(&req->curl_result, req->http_code, 527 req->errorstr, sizeof(req->errorstr)); 528 529 if (obj_req->state == ABORTED) { 530 ret = error("Request for %s aborted", hex); 531 } else if (req->curl_result != CURLE_OK && 532 req->http_code != 416) { 533 if (missing_target(req)) 534 ret = -1; /* Be silent, it is probably in a pack. */ 535 else 536 ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)", 537 req->errorstr, req->curl_result, 538 req->http_code, hex); 539 } else if (req->zret != Z_STREAM_END) { 540 walker->corrupt_object_found++; 541 ret = error("File %s (%s) corrupt", hex, req->url); 542 } else if (!oideq(&obj_req->oid, &req->real_oid)) { 543 ret = error("File %s has bad hash", hex); 544 } else if (req->rename < 0) { 545 struct strbuf buf = STRBUF_INIT; 546 odb_loose_path(the_repository->objects->sources, &buf, &req->oid); 547 ret = error("unable to write sha1 filename %s", buf.buf); 548 strbuf_release(&buf); 549 } 550 551 release_http_object_request(&obj_req->req); 552 release_object_request(obj_req); 553 return ret; 554} 555 556static int fetch(struct walker *walker, const struct object_id *oid) 557{ 558 struct walker_data *data = walker->data; 559 struct alt_base *altbase = data->alt; 560 561 if (!fetch_object(walker, oid)) 562 return 0; 563 while (altbase) { 564 if (!http_fetch_pack(walker, altbase, oid)) 565 return 0; 566 fetch_alternates(walker, data->alt->base); 567 altbase = altbase->next; 568 } 569 return error("Unable to find %s under %s", oid_to_hex(oid), 570 data->alt->base); 571} 572 573static int fetch_ref(struct walker *walker, struct ref *ref) 574{ 575 struct walker_data *data = walker->data; 576 return http_fetch_ref(data->alt->base, ref); 577} 578 579static void cleanup(struct walker *walker) 580{ 581 struct walker_data *data = walker->data; 582 struct alt_base *alt, *alt_next; 583 584 if (data) { 585 alt = data->alt; 586 while (alt) { 587 struct packed_git *pack; 588 589 alt_next = alt->next; 590 591 pack = alt->packs; 592 while (pack) { 593 struct packed_git *pack_next = pack->next; 594 close_pack(pack); 595 free(pack); 596 pack = pack_next; 597 } 598 599 free(alt->base); 600 free(alt); 601 602 alt = alt_next; 603 } 604 free(data); 605 walker->data = NULL; 606 } 607} 608 609struct walker *get_http_walker(const char *url) 610{ 611 char *s; 612 struct walker_data *data = xmalloc(sizeof(struct walker_data)); 613 struct walker *walker = xmalloc(sizeof(struct walker)); 614 615 data->alt = xmalloc(sizeof(*data->alt)); 616 data->alt->base = xstrdup(url); 617 for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s) 618 *s = 0; 619 620 data->alt->got_indices = 0; 621 data->alt->packs = NULL; 622 data->alt->next = NULL; 623 data->got_alternates = -1; 624 625 walker->corrupt_object_found = 0; 626 walker->fetch = fetch; 627 walker->fetch_ref = fetch_ref; 628 walker->prefetch = prefetch; 629 walker->cleanup = cleanup; 630 walker->data = data; 631 632 add_fill_function(NULL, fill_active_slot); 633 634 return walker; 635}