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