1 /* 2 * QEMU Block driver for CURL images 3 * 4 * Copyright (c) 2009 Alexander Graf <agraf@suse.de> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 */ 24 #include "qemu-common.h" 25 #include "block/block_int.h" 26 #include <curl/curl.h> 27 28 // #define DEBUG 29 // #define DEBUG_VERBOSE 30 31 #ifdef DEBUG_CURL 32 #define DPRINTF(fmt, ...) do { printf(fmt, ## __VA_ARGS__); } while (0) 33 #else 34 #define DPRINTF(fmt, ...) do { } while (0) 35 #endif 36 37 #if LIBCURL_VERSION_NUM >= 0x071000 38 /* The multi interface timer callback was introduced in 7.16.0 */ 39 #define NEED_CURL_TIMER_CALLBACK 40 #endif 41 42 #define PROTOCOLS (CURLPROTO_HTTP | CURLPROTO_HTTPS | \ 43 CURLPROTO_FTP | CURLPROTO_FTPS | \ 44 CURLPROTO_TFTP) 45 46 #define CURL_NUM_STATES 8 47 #define CURL_NUM_ACB 8 48 #define SECTOR_SIZE 512 49 #define READ_AHEAD_SIZE (256 * 1024) 50 51 #define FIND_RET_NONE 0 52 #define FIND_RET_OK 1 53 #define FIND_RET_WAIT 2 54 55 struct BDRVCURLState; 56 57 typedef struct CURLAIOCB { 58 BlockDriverAIOCB common; 59 QEMUBH *bh; 60 QEMUIOVector *qiov; 61 62 int64_t sector_num; 63 int nb_sectors; 64 65 size_t start; 66 size_t end; 67 } CURLAIOCB; 68 69 typedef struct CURLState 70 { 71 struct BDRVCURLState *s; 72 CURLAIOCB *acb[CURL_NUM_ACB]; 73 CURL *curl; 74 char *orig_buf; 75 size_t buf_start; 76 size_t buf_off; 77 size_t buf_len; 78 char range[128]; 79 char errmsg[CURL_ERROR_SIZE]; 80 char in_use; 81 } CURLState; 82 83 typedef struct BDRVCURLState { 84 CURLM *multi; 85 QEMUTimer timer; 86 size_t len; 87 CURLState states[CURL_NUM_STATES]; 88 char *url; 89 size_t readahead_size; 90 bool accept_range; 91 } BDRVCURLState; 92 93 static void curl_clean_state(CURLState *s); 94 static void curl_multi_do(void *arg); 95 96 #ifdef NEED_CURL_TIMER_CALLBACK 97 static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque) 98 { 99 BDRVCURLState *s = opaque; 100 101 DPRINTF("CURL: timer callback timeout_ms %ld\n", timeout_ms); 102 if (timeout_ms == -1) { 103 timer_del(&s->timer); 104 } else { 105 int64_t timeout_ns = (int64_t)timeout_ms * 1000 * 1000; 106 timer_mod(&s->timer, 107 qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + timeout_ns); 108 } 109 return 0; 110 } 111 #endif 112 113 static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action, 114 void *s, void *sp) 115 { 116 DPRINTF("CURL (AIO): Sock action %d on fd %d\n", action, fd); 117 switch (action) { 118 case CURL_POLL_IN: 119 qemu_aio_set_fd_handler(fd, curl_multi_do, NULL, s); 120 break; 121 case CURL_POLL_OUT: 122 qemu_aio_set_fd_handler(fd, NULL, curl_multi_do, s); 123 break; 124 case CURL_POLL_INOUT: 125 qemu_aio_set_fd_handler(fd, curl_multi_do, curl_multi_do, s); 126 break; 127 case CURL_POLL_REMOVE: 128 qemu_aio_set_fd_handler(fd, NULL, NULL, NULL); 129 break; 130 } 131 132 return 0; 133 } 134 135 static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque) 136 { 137 BDRVCURLState *s = opaque; 138 size_t realsize = size * nmemb; 139 const char *accept_line = "Accept-Ranges: bytes"; 140 141 if (realsize >= strlen(accept_line) 142 && strncmp((char *)ptr, accept_line, strlen(accept_line)) == 0) { 143 s->accept_range = true; 144 } 145 146 return realsize; 147 } 148 149 static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque) 150 { 151 CURLState *s = ((CURLState*)opaque); 152 size_t realsize = size * nmemb; 153 int i; 154 155 DPRINTF("CURL: Just reading %zd bytes\n", realsize); 156 157 if (!s || !s->orig_buf) 158 goto read_end; 159 160 if (s->buf_off >= s->buf_len) { 161 /* buffer full, read nothing */ 162 return 0; 163 } 164 realsize = MIN(realsize, s->buf_len - s->buf_off); 165 memcpy(s->orig_buf + s->buf_off, ptr, realsize); 166 s->buf_off += realsize; 167 168 for(i=0; i<CURL_NUM_ACB; i++) { 169 CURLAIOCB *acb = s->acb[i]; 170 171 if (!acb) 172 continue; 173 174 if ((s->buf_off >= acb->end)) { 175 qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start, 176 acb->end - acb->start); 177 acb->common.cb(acb->common.opaque, 0); 178 qemu_aio_release(acb); 179 s->acb[i] = NULL; 180 } 181 } 182 183 read_end: 184 return realsize; 185 } 186 187 static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len, 188 CURLAIOCB *acb) 189 { 190 int i; 191 size_t end = start + len; 192 193 for (i=0; i<CURL_NUM_STATES; i++) { 194 CURLState *state = &s->states[i]; 195 size_t buf_end = (state->buf_start + state->buf_off); 196 size_t buf_fend = (state->buf_start + state->buf_len); 197 198 if (!state->orig_buf) 199 continue; 200 if (!state->buf_off) 201 continue; 202 203 // Does the existing buffer cover our section? 204 if ((start >= state->buf_start) && 205 (start <= buf_end) && 206 (end >= state->buf_start) && 207 (end <= buf_end)) 208 { 209 char *buf = state->orig_buf + (start - state->buf_start); 210 211 qemu_iovec_from_buf(acb->qiov, 0, buf, len); 212 acb->common.cb(acb->common.opaque, 0); 213 214 return FIND_RET_OK; 215 } 216 217 // Wait for unfinished chunks 218 if ((start >= state->buf_start) && 219 (start <= buf_fend) && 220 (end >= state->buf_start) && 221 (end <= buf_fend)) 222 { 223 int j; 224 225 acb->start = start - state->buf_start; 226 acb->end = acb->start + len; 227 228 for (j=0; j<CURL_NUM_ACB; j++) { 229 if (!state->acb[j]) { 230 state->acb[j] = acb; 231 return FIND_RET_WAIT; 232 } 233 } 234 } 235 } 236 237 return FIND_RET_NONE; 238 } 239 240 static void curl_multi_read(BDRVCURLState *s) 241 { 242 int msgs_in_queue; 243 244 /* Try to find done transfers, so we can free the easy 245 * handle again. */ 246 do { 247 CURLMsg *msg; 248 msg = curl_multi_info_read(s->multi, &msgs_in_queue); 249 250 if (!msg) 251 break; 252 if (msg->msg == CURLMSG_NONE) 253 break; 254 255 switch (msg->msg) { 256 case CURLMSG_DONE: 257 { 258 CURLState *state = NULL; 259 curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char**)&state); 260 261 /* ACBs for successful messages get completed in curl_read_cb */ 262 if (msg->data.result != CURLE_OK) { 263 int i; 264 for (i = 0; i < CURL_NUM_ACB; i++) { 265 CURLAIOCB *acb = state->acb[i]; 266 267 if (acb == NULL) { 268 continue; 269 } 270 271 acb->common.cb(acb->common.opaque, -EIO); 272 qemu_aio_release(acb); 273 state->acb[i] = NULL; 274 } 275 } 276 277 curl_clean_state(state); 278 break; 279 } 280 default: 281 msgs_in_queue = 0; 282 break; 283 } 284 } while(msgs_in_queue); 285 } 286 287 static void curl_multi_do(void *arg) 288 { 289 BDRVCURLState *s = (BDRVCURLState *)arg; 290 int running; 291 int r; 292 293 if (!s->multi) { 294 return; 295 } 296 297 do { 298 r = curl_multi_socket_all(s->multi, &running); 299 } while(r == CURLM_CALL_MULTI_PERFORM); 300 301 curl_multi_read(s); 302 } 303 304 static void curl_multi_timeout_do(void *arg) 305 { 306 #ifdef NEED_CURL_TIMER_CALLBACK 307 BDRVCURLState *s = (BDRVCURLState *)arg; 308 int running; 309 310 if (!s->multi) { 311 return; 312 } 313 314 curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running); 315 316 curl_multi_read(s); 317 #else 318 abort(); 319 #endif 320 } 321 322 static CURLState *curl_init_state(BDRVCURLState *s) 323 { 324 CURLState *state = NULL; 325 int i, j; 326 327 do { 328 for (i=0; i<CURL_NUM_STATES; i++) { 329 for (j=0; j<CURL_NUM_ACB; j++) 330 if (s->states[i].acb[j]) 331 continue; 332 if (s->states[i].in_use) 333 continue; 334 335 state = &s->states[i]; 336 state->in_use = 1; 337 break; 338 } 339 if (!state) { 340 g_usleep(100); 341 curl_multi_do(s); 342 } 343 } while(!state); 344 345 if (state->curl) 346 goto has_curl; 347 348 state->curl = curl_easy_init(); 349 if (!state->curl) 350 return NULL; 351 curl_easy_setopt(state->curl, CURLOPT_URL, s->url); 352 curl_easy_setopt(state->curl, CURLOPT_TIMEOUT, 5); 353 curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_read_cb); 354 curl_easy_setopt(state->curl, CURLOPT_WRITEDATA, (void *)state); 355 curl_easy_setopt(state->curl, CURLOPT_PRIVATE, (void *)state); 356 curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1); 357 curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1); 358 curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1); 359 curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg); 360 curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1); 361 362 /* Restrict supported protocols to avoid security issues in the more 363 * obscure protocols. For example, do not allow POP3/SMTP/IMAP see 364 * CVE-2013-0249. 365 * 366 * Restricting protocols is only supported from 7.19.4 upwards. 367 */ 368 #if LIBCURL_VERSION_NUM >= 0x071304 369 curl_easy_setopt(state->curl, CURLOPT_PROTOCOLS, PROTOCOLS); 370 curl_easy_setopt(state->curl, CURLOPT_REDIR_PROTOCOLS, PROTOCOLS); 371 #endif 372 373 #ifdef DEBUG_VERBOSE 374 curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1); 375 #endif 376 377 has_curl: 378 379 state->s = s; 380 381 return state; 382 } 383 384 static void curl_clean_state(CURLState *s) 385 { 386 if (s->s->multi) 387 curl_multi_remove_handle(s->s->multi, s->curl); 388 s->in_use = 0; 389 } 390 391 static void curl_parse_filename(const char *filename, QDict *options, 392 Error **errp) 393 { 394 395 #define RA_OPTSTR ":readahead=" 396 char *file; 397 char *ra; 398 const char *ra_val; 399 int parse_state = 0; 400 401 file = g_strdup(filename); 402 403 /* Parse a trailing ":readahead=#:" param, if present. */ 404 ra = file + strlen(file) - 1; 405 while (ra >= file) { 406 if (parse_state == 0) { 407 if (*ra == ':') { 408 parse_state++; 409 } else { 410 break; 411 } 412 } else if (parse_state == 1) { 413 if (*ra > '9' || *ra < '0') { 414 char *opt_start = ra - strlen(RA_OPTSTR) + 1; 415 if (opt_start > file && 416 strncmp(opt_start, RA_OPTSTR, strlen(RA_OPTSTR)) == 0) { 417 ra_val = ra + 1; 418 ra -= strlen(RA_OPTSTR) - 1; 419 *ra = '\0'; 420 qdict_put(options, "readahead", qstring_from_str(ra_val)); 421 } 422 break; 423 } 424 } 425 ra--; 426 } 427 428 qdict_put(options, "url", qstring_from_str(file)); 429 430 g_free(file); 431 } 432 433 static QemuOptsList runtime_opts = { 434 .name = "curl", 435 .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head), 436 .desc = { 437 { 438 .name = "url", 439 .type = QEMU_OPT_STRING, 440 .help = "URL to open", 441 }, 442 { 443 .name = "readahead", 444 .type = QEMU_OPT_SIZE, 445 .help = "Readahead size", 446 }, 447 { /* end of list */ } 448 }, 449 }; 450 451 static int curl_open(BlockDriverState *bs, QDict *options, int flags, 452 Error **errp) 453 { 454 BDRVCURLState *s = bs->opaque; 455 CURLState *state = NULL; 456 QemuOpts *opts; 457 Error *local_err = NULL; 458 const char *file; 459 double d; 460 461 static int inited = 0; 462 463 if (flags & BDRV_O_RDWR) { 464 error_setg(errp, "curl block device does not support writes"); 465 return -EROFS; 466 } 467 468 opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); 469 qemu_opts_absorb_qdict(opts, options, &local_err); 470 if (local_err) { 471 error_propagate(errp, local_err); 472 goto out_noclean; 473 } 474 475 s->readahead_size = qemu_opt_get_size(opts, "readahead", READ_AHEAD_SIZE); 476 if ((s->readahead_size & 0x1ff) != 0) { 477 error_setg(errp, "HTTP_READAHEAD_SIZE %zd is not a multiple of 512", 478 s->readahead_size); 479 goto out_noclean; 480 } 481 482 file = qemu_opt_get(opts, "url"); 483 if (file == NULL) { 484 error_setg(errp, "curl block driver requires an 'url' option"); 485 goto out_noclean; 486 } 487 488 if (!inited) { 489 curl_global_init(CURL_GLOBAL_ALL); 490 inited = 1; 491 } 492 493 DPRINTF("CURL: Opening %s\n", file); 494 s->url = g_strdup(file); 495 state = curl_init_state(s); 496 if (!state) 497 goto out_noclean; 498 499 // Get file size 500 501 s->accept_range = false; 502 curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1); 503 curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION, 504 curl_header_cb); 505 curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s); 506 if (curl_easy_perform(state->curl)) 507 goto out; 508 curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d); 509 if (d) 510 s->len = (size_t)d; 511 else if(!s->len) 512 goto out; 513 if ((!strncasecmp(s->url, "http://", strlen("http://")) 514 || !strncasecmp(s->url, "https://", strlen("https://"))) 515 && !s->accept_range) { 516 pstrcpy(state->errmsg, CURL_ERROR_SIZE, 517 "Server does not support 'range' (byte ranges)."); 518 goto out; 519 } 520 DPRINTF("CURL: Size = %zd\n", s->len); 521 522 curl_clean_state(state); 523 curl_easy_cleanup(state->curl); 524 state->curl = NULL; 525 526 aio_timer_init(bdrv_get_aio_context(bs), &s->timer, 527 QEMU_CLOCK_REALTIME, SCALE_NS, 528 curl_multi_timeout_do, s); 529 530 // Now we know the file exists and its size, so let's 531 // initialize the multi interface! 532 533 s->multi = curl_multi_init(); 534 curl_multi_setopt(s->multi, CURLMOPT_SOCKETDATA, s); 535 curl_multi_setopt(s->multi, CURLMOPT_SOCKETFUNCTION, curl_sock_cb); 536 #ifdef NEED_CURL_TIMER_CALLBACK 537 curl_multi_setopt(s->multi, CURLMOPT_TIMERDATA, s); 538 curl_multi_setopt(s->multi, CURLMOPT_TIMERFUNCTION, curl_timer_cb); 539 #endif 540 curl_multi_do(s); 541 542 qemu_opts_del(opts); 543 return 0; 544 545 out: 546 error_setg(errp, "CURL: Error opening file: %s", state->errmsg); 547 curl_easy_cleanup(state->curl); 548 state->curl = NULL; 549 out_noclean: 550 g_free(s->url); 551 qemu_opts_del(opts); 552 return -EINVAL; 553 } 554 555 static void curl_aio_cancel(BlockDriverAIOCB *blockacb) 556 { 557 // Do we have to implement canceling? Seems to work without... 558 } 559 560 static const AIOCBInfo curl_aiocb_info = { 561 .aiocb_size = sizeof(CURLAIOCB), 562 .cancel = curl_aio_cancel, 563 }; 564 565 566 static void curl_readv_bh_cb(void *p) 567 { 568 CURLState *state; 569 570 CURLAIOCB *acb = p; 571 BDRVCURLState *s = acb->common.bs->opaque; 572 573 qemu_bh_delete(acb->bh); 574 acb->bh = NULL; 575 576 size_t start = acb->sector_num * SECTOR_SIZE; 577 size_t end; 578 579 // In case we have the requested data already (e.g. read-ahead), 580 // we can just call the callback and be done. 581 switch (curl_find_buf(s, start, acb->nb_sectors * SECTOR_SIZE, acb)) { 582 case FIND_RET_OK: 583 qemu_aio_release(acb); 584 // fall through 585 case FIND_RET_WAIT: 586 return; 587 default: 588 break; 589 } 590 591 // No cache found, so let's start a new request 592 state = curl_init_state(s); 593 if (!state) { 594 acb->common.cb(acb->common.opaque, -EIO); 595 qemu_aio_release(acb); 596 return; 597 } 598 599 acb->start = 0; 600 acb->end = (acb->nb_sectors * SECTOR_SIZE); 601 602 state->buf_off = 0; 603 if (state->orig_buf) 604 g_free(state->orig_buf); 605 state->buf_start = start; 606 state->buf_len = acb->end + s->readahead_size; 607 end = MIN(start + state->buf_len, s->len) - 1; 608 state->orig_buf = g_malloc(state->buf_len); 609 state->acb[0] = acb; 610 611 snprintf(state->range, 127, "%zd-%zd", start, end); 612 DPRINTF("CURL (AIO): Reading %d at %zd (%s)\n", 613 (acb->nb_sectors * SECTOR_SIZE), start, state->range); 614 curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range); 615 616 curl_multi_add_handle(s->multi, state->curl); 617 curl_multi_do(s); 618 619 } 620 621 static BlockDriverAIOCB *curl_aio_readv(BlockDriverState *bs, 622 int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, 623 BlockDriverCompletionFunc *cb, void *opaque) 624 { 625 CURLAIOCB *acb; 626 627 acb = qemu_aio_get(&curl_aiocb_info, bs, cb, opaque); 628 629 acb->qiov = qiov; 630 acb->sector_num = sector_num; 631 acb->nb_sectors = nb_sectors; 632 633 acb->bh = qemu_bh_new(curl_readv_bh_cb, acb); 634 qemu_bh_schedule(acb->bh); 635 return &acb->common; 636 } 637 638 static void curl_close(BlockDriverState *bs) 639 { 640 BDRVCURLState *s = bs->opaque; 641 int i; 642 643 DPRINTF("CURL: Close\n"); 644 for (i=0; i<CURL_NUM_STATES; i++) { 645 if (s->states[i].in_use) 646 curl_clean_state(&s->states[i]); 647 if (s->states[i].curl) { 648 curl_easy_cleanup(s->states[i].curl); 649 s->states[i].curl = NULL; 650 } 651 if (s->states[i].orig_buf) { 652 g_free(s->states[i].orig_buf); 653 s->states[i].orig_buf = NULL; 654 } 655 } 656 if (s->multi) 657 curl_multi_cleanup(s->multi); 658 659 timer_del(&s->timer); 660 661 g_free(s->url); 662 } 663 664 static int64_t curl_getlength(BlockDriverState *bs) 665 { 666 BDRVCURLState *s = bs->opaque; 667 return s->len; 668 } 669 670 static BlockDriver bdrv_http = { 671 .format_name = "http", 672 .protocol_name = "http", 673 674 .instance_size = sizeof(BDRVCURLState), 675 .bdrv_parse_filename = curl_parse_filename, 676 .bdrv_file_open = curl_open, 677 .bdrv_close = curl_close, 678 .bdrv_getlength = curl_getlength, 679 680 .bdrv_aio_readv = curl_aio_readv, 681 }; 682 683 static BlockDriver bdrv_https = { 684 .format_name = "https", 685 .protocol_name = "https", 686 687 .instance_size = sizeof(BDRVCURLState), 688 .bdrv_parse_filename = curl_parse_filename, 689 .bdrv_file_open = curl_open, 690 .bdrv_close = curl_close, 691 .bdrv_getlength = curl_getlength, 692 693 .bdrv_aio_readv = curl_aio_readv, 694 }; 695 696 static BlockDriver bdrv_ftp = { 697 .format_name = "ftp", 698 .protocol_name = "ftp", 699 700 .instance_size = sizeof(BDRVCURLState), 701 .bdrv_parse_filename = curl_parse_filename, 702 .bdrv_file_open = curl_open, 703 .bdrv_close = curl_close, 704 .bdrv_getlength = curl_getlength, 705 706 .bdrv_aio_readv = curl_aio_readv, 707 }; 708 709 static BlockDriver bdrv_ftps = { 710 .format_name = "ftps", 711 .protocol_name = "ftps", 712 713 .instance_size = sizeof(BDRVCURLState), 714 .bdrv_parse_filename = curl_parse_filename, 715 .bdrv_file_open = curl_open, 716 .bdrv_close = curl_close, 717 .bdrv_getlength = curl_getlength, 718 719 .bdrv_aio_readv = curl_aio_readv, 720 }; 721 722 static BlockDriver bdrv_tftp = { 723 .format_name = "tftp", 724 .protocol_name = "tftp", 725 726 .instance_size = sizeof(BDRVCURLState), 727 .bdrv_parse_filename = curl_parse_filename, 728 .bdrv_file_open = curl_open, 729 .bdrv_close = curl_close, 730 .bdrv_getlength = curl_getlength, 731 732 .bdrv_aio_readv = curl_aio_readv, 733 }; 734 735 static void curl_block_init(void) 736 { 737 bdrv_register(&bdrv_http); 738 bdrv_register(&bdrv_https); 739 bdrv_register(&bdrv_ftp); 740 bdrv_register(&bdrv_ftps); 741 bdrv_register(&bdrv_tftp); 742 } 743 744 block_init(curl_block_init); 745