|
|
1.1 ! root 1: /* ! 2: * QEMU Block driver for CURL images ! 3: * ! 4: * Copyright (c) 2009 Alexander Graf <[email protected]> ! 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_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: #define CURL_NUM_STATES 8 ! 38: #define CURL_NUM_ACB 8 ! 39: #define SECTOR_SIZE 512 ! 40: #define READ_AHEAD_SIZE (256 * 1024) ! 41: ! 42: #define FIND_RET_NONE 0 ! 43: #define FIND_RET_OK 1 ! 44: #define FIND_RET_WAIT 2 ! 45: ! 46: struct BDRVCURLState; ! 47: ! 48: typedef struct CURLAIOCB { ! 49: BlockDriverAIOCB common; ! 50: QEMUIOVector *qiov; ! 51: size_t start; ! 52: size_t end; ! 53: } CURLAIOCB; ! 54: ! 55: typedef struct CURLState ! 56: { ! 57: struct BDRVCURLState *s; ! 58: CURLAIOCB *acb[CURL_NUM_ACB]; ! 59: CURL *curl; ! 60: char *orig_buf; ! 61: size_t buf_start; ! 62: size_t buf_off; ! 63: size_t buf_len; ! 64: char range[128]; ! 65: char errmsg[CURL_ERROR_SIZE]; ! 66: char in_use; ! 67: } CURLState; ! 68: ! 69: typedef struct BDRVCURLState { ! 70: CURLM *multi; ! 71: size_t len; ! 72: CURLState states[CURL_NUM_STATES]; ! 73: char *url; ! 74: size_t readahead_size; ! 75: } BDRVCURLState; ! 76: ! 77: static void curl_clean_state(CURLState *s); ! 78: static void curl_multi_do(void *arg); ! 79: ! 80: static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action, ! 81: void *s, void *sp) ! 82: { ! 83: dprintf("CURL (AIO): Sock action %d on fd %d\n", action, fd); ! 84: switch (action) { ! 85: case CURL_POLL_IN: ! 86: qemu_aio_set_fd_handler(fd, curl_multi_do, NULL, NULL, s); ! 87: break; ! 88: case CURL_POLL_OUT: ! 89: qemu_aio_set_fd_handler(fd, NULL, curl_multi_do, NULL, s); ! 90: break; ! 91: case CURL_POLL_INOUT: ! 92: qemu_aio_set_fd_handler(fd, curl_multi_do, ! 93: curl_multi_do, NULL, s); ! 94: break; ! 95: case CURL_POLL_REMOVE: ! 96: qemu_aio_set_fd_handler(fd, NULL, NULL, NULL, NULL); ! 97: break; ! 98: } ! 99: ! 100: return 0; ! 101: } ! 102: ! 103: static size_t curl_size_cb(void *ptr, size_t size, size_t nmemb, void *opaque) ! 104: { ! 105: CURLState *s = ((CURLState*)opaque); ! 106: size_t realsize = size * nmemb; ! 107: long long fsize; ! 108: ! 109: if(sscanf(ptr, "Content-Length: %lld", &fsize) == 1) ! 110: s->s->len = fsize; ! 111: ! 112: return realsize; ! 113: } ! 114: ! 115: static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque) ! 116: { ! 117: CURLState *s = ((CURLState*)opaque); ! 118: size_t realsize = size * nmemb; ! 119: int i; ! 120: ! 121: dprintf("CURL: Just reading %lld bytes\n", (unsigned long long)realsize); ! 122: ! 123: if (!s || !s->orig_buf) ! 124: goto read_end; ! 125: ! 126: memcpy(s->orig_buf + s->buf_off, ptr, realsize); ! 127: s->buf_off += realsize; ! 128: ! 129: for(i=0; i<CURL_NUM_ACB; i++) { ! 130: CURLAIOCB *acb = s->acb[i]; ! 131: ! 132: if (!acb) ! 133: continue; ! 134: ! 135: if ((s->buf_off >= acb->end)) { ! 136: qemu_iovec_from_buffer(acb->qiov, s->orig_buf + acb->start, ! 137: acb->end - acb->start); ! 138: acb->common.cb(acb->common.opaque, 0); ! 139: qemu_aio_release(acb); ! 140: s->acb[i] = NULL; ! 141: } ! 142: } ! 143: ! 144: read_end: ! 145: return realsize; ! 146: } ! 147: ! 148: static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len, ! 149: CURLAIOCB *acb) ! 150: { ! 151: int i; ! 152: size_t end = start + len; ! 153: ! 154: for (i=0; i<CURL_NUM_STATES; i++) { ! 155: CURLState *state = &s->states[i]; ! 156: size_t buf_end = (state->buf_start + state->buf_off); ! 157: size_t buf_fend = (state->buf_start + state->buf_len); ! 158: ! 159: if (!state->orig_buf) ! 160: continue; ! 161: if (!state->buf_off) ! 162: continue; ! 163: ! 164: // Does the existing buffer cover our section? ! 165: if ((start >= state->buf_start) && ! 166: (start <= buf_end) && ! 167: (end >= state->buf_start) && ! 168: (end <= buf_end)) ! 169: { ! 170: char *buf = state->orig_buf + (start - state->buf_start); ! 171: ! 172: qemu_iovec_from_buffer(acb->qiov, buf, len); ! 173: acb->common.cb(acb->common.opaque, 0); ! 174: ! 175: return FIND_RET_OK; ! 176: } ! 177: ! 178: // Wait for unfinished chunks ! 179: if ((start >= state->buf_start) && ! 180: (start <= buf_fend) && ! 181: (end >= state->buf_start) && ! 182: (end <= buf_fend)) ! 183: { ! 184: int j; ! 185: ! 186: acb->start = start - state->buf_start; ! 187: acb->end = acb->start + len; ! 188: ! 189: for (j=0; j<CURL_NUM_ACB; j++) { ! 190: if (!state->acb[j]) { ! 191: state->acb[j] = acb; ! 192: return FIND_RET_WAIT; ! 193: } ! 194: } ! 195: } ! 196: } ! 197: ! 198: return FIND_RET_NONE; ! 199: } ! 200: ! 201: static void curl_multi_do(void *arg) ! 202: { ! 203: BDRVCURLState *s = (BDRVCURLState *)arg; ! 204: int running; ! 205: int r; ! 206: int msgs_in_queue; ! 207: ! 208: if (!s->multi) ! 209: return; ! 210: ! 211: do { ! 212: r = curl_multi_socket_all(s->multi, &running); ! 213: } while(r == CURLM_CALL_MULTI_PERFORM); ! 214: ! 215: /* Try to find done transfers, so we can free the easy ! 216: * handle again. */ ! 217: do { ! 218: CURLMsg *msg; ! 219: msg = curl_multi_info_read(s->multi, &msgs_in_queue); ! 220: ! 221: if (!msg) ! 222: break; ! 223: if (msg->msg == CURLMSG_NONE) ! 224: break; ! 225: ! 226: switch (msg->msg) { ! 227: case CURLMSG_DONE: ! 228: { ! 229: CURLState *state = NULL; ! 230: curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char**)&state); ! 231: curl_clean_state(state); ! 232: break; ! 233: } ! 234: default: ! 235: msgs_in_queue = 0; ! 236: break; ! 237: } ! 238: } while(msgs_in_queue); ! 239: } ! 240: ! 241: static CURLState *curl_init_state(BDRVCURLState *s) ! 242: { ! 243: CURLState *state = NULL; ! 244: int i, j; ! 245: ! 246: do { ! 247: for (i=0; i<CURL_NUM_STATES; i++) { ! 248: for (j=0; j<CURL_NUM_ACB; j++) ! 249: if (s->states[i].acb[j]) ! 250: continue; ! 251: if (s->states[i].in_use) ! 252: continue; ! 253: ! 254: state = &s->states[i]; ! 255: state->in_use = 1; ! 256: break; ! 257: } ! 258: if (!state) { ! 259: usleep(100); ! 260: curl_multi_do(s); ! 261: } ! 262: } while(!state); ! 263: ! 264: if (state->curl) ! 265: goto has_curl; ! 266: ! 267: state->curl = curl_easy_init(); ! 268: if (!state->curl) ! 269: return NULL; ! 270: curl_easy_setopt(state->curl, CURLOPT_URL, s->url); ! 271: curl_easy_setopt(state->curl, CURLOPT_TIMEOUT, 5); ! 272: curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_read_cb); ! 273: curl_easy_setopt(state->curl, CURLOPT_WRITEDATA, (void *)state); ! 274: curl_easy_setopt(state->curl, CURLOPT_PRIVATE, (void *)state); ! 275: curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1); ! 276: curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1); ! 277: curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1); ! 278: curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg); ! 279: ! 280: #ifdef DEBUG_VERBOSE ! 281: curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1); ! 282: #endif ! 283: ! 284: has_curl: ! 285: ! 286: state->s = s; ! 287: ! 288: return state; ! 289: } ! 290: ! 291: static void curl_clean_state(CURLState *s) ! 292: { ! 293: if (s->s->multi) ! 294: curl_multi_remove_handle(s->s->multi, s->curl); ! 295: s->in_use = 0; ! 296: } ! 297: ! 298: static int curl_open(BlockDriverState *bs, const char *filename, int flags) ! 299: { ! 300: BDRVCURLState *s = bs->opaque; ! 301: CURLState *state = NULL; ! 302: double d; ! 303: ! 304: #define RA_OPTSTR ":readahead=" ! 305: char *file; ! 306: char *ra; ! 307: const char *ra_val; ! 308: int parse_state = 0; ! 309: ! 310: static int inited = 0; ! 311: ! 312: file = strdup(filename); ! 313: s->readahead_size = READ_AHEAD_SIZE; ! 314: ! 315: /* Parse a trailing ":readahead=#:" param, if present. */ ! 316: ra = file + strlen(file) - 1; ! 317: while (ra >= file) { ! 318: if (parse_state == 0) { ! 319: if (*ra == ':') ! 320: parse_state++; ! 321: else ! 322: break; ! 323: } else if (parse_state == 1) { ! 324: if (*ra > '9' || *ra < '0') { ! 325: char *opt_start = ra - strlen(RA_OPTSTR) + 1; ! 326: if (opt_start > file && ! 327: strncmp(opt_start, RA_OPTSTR, strlen(RA_OPTSTR)) == 0) { ! 328: ra_val = ra + 1; ! 329: ra -= strlen(RA_OPTSTR) - 1; ! 330: *ra = '\0'; ! 331: s->readahead_size = atoi(ra_val); ! 332: break; ! 333: } else { ! 334: break; ! 335: } ! 336: } ! 337: } ! 338: ra--; ! 339: } ! 340: ! 341: if ((s->readahead_size & 0x1ff) != 0) { ! 342: fprintf(stderr, "HTTP_READAHEAD_SIZE %Zd is not a multiple of 512\n", ! 343: s->readahead_size); ! 344: goto out_noclean; ! 345: } ! 346: ! 347: if (!inited) { ! 348: curl_global_init(CURL_GLOBAL_ALL); ! 349: inited = 1; ! 350: } ! 351: ! 352: dprintf("CURL: Opening %s\n", file); ! 353: s->url = file; ! 354: state = curl_init_state(s); ! 355: if (!state) ! 356: goto out_noclean; ! 357: ! 358: // Get file size ! 359: ! 360: curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1); ! 361: curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_size_cb); ! 362: if (curl_easy_perform(state->curl)) ! 363: goto out; ! 364: curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d); ! 365: curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_read_cb); ! 366: curl_easy_setopt(state->curl, CURLOPT_NOBODY, 0); ! 367: if (d) ! 368: s->len = (size_t)d; ! 369: else if(!s->len) ! 370: goto out; ! 371: dprintf("CURL: Size = %lld\n", (long long)s->len); ! 372: ! 373: curl_clean_state(state); ! 374: curl_easy_cleanup(state->curl); ! 375: state->curl = NULL; ! 376: ! 377: // Now we know the file exists and its size, so let's ! 378: // initialize the multi interface! ! 379: ! 380: s->multi = curl_multi_init(); ! 381: curl_multi_setopt( s->multi, CURLMOPT_SOCKETDATA, s); ! 382: curl_multi_setopt( s->multi, CURLMOPT_SOCKETFUNCTION, curl_sock_cb ); ! 383: curl_multi_do(s); ! 384: ! 385: return 0; ! 386: ! 387: out: ! 388: fprintf(stderr, "CURL: Error opening file: %s\n", state->errmsg); ! 389: curl_easy_cleanup(state->curl); ! 390: state->curl = NULL; ! 391: out_noclean: ! 392: qemu_free(file); ! 393: return -EINVAL; ! 394: } ! 395: ! 396: static void curl_aio_cancel(BlockDriverAIOCB *blockacb) ! 397: { ! 398: // Do we have to implement canceling? Seems to work without... ! 399: } ! 400: ! 401: static AIOPool curl_aio_pool = { ! 402: .aiocb_size = sizeof(CURLAIOCB), ! 403: .cancel = curl_aio_cancel, ! 404: }; ! 405: ! 406: static BlockDriverAIOCB *curl_aio_readv(BlockDriverState *bs, ! 407: int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, ! 408: BlockDriverCompletionFunc *cb, void *opaque) ! 409: { ! 410: BDRVCURLState *s = bs->opaque; ! 411: CURLAIOCB *acb; ! 412: size_t start = sector_num * SECTOR_SIZE; ! 413: size_t end; ! 414: CURLState *state; ! 415: ! 416: acb = qemu_aio_get(&curl_aio_pool, bs, cb, opaque); ! 417: if (!acb) ! 418: return NULL; ! 419: ! 420: acb->qiov = qiov; ! 421: ! 422: // In case we have the requested data already (e.g. read-ahead), ! 423: // we can just call the callback and be done. ! 424: ! 425: switch (curl_find_buf(s, start, nb_sectors * SECTOR_SIZE, acb)) { ! 426: case FIND_RET_OK: ! 427: qemu_aio_release(acb); ! 428: // fall through ! 429: case FIND_RET_WAIT: ! 430: return &acb->common; ! 431: default: ! 432: break; ! 433: } ! 434: ! 435: // No cache found, so let's start a new request ! 436: ! 437: state = curl_init_state(s); ! 438: if (!state) ! 439: return NULL; ! 440: ! 441: acb->start = 0; ! 442: acb->end = (nb_sectors * SECTOR_SIZE); ! 443: ! 444: state->buf_off = 0; ! 445: if (state->orig_buf) ! 446: qemu_free(state->orig_buf); ! 447: state->buf_start = start; ! 448: state->buf_len = acb->end + s->readahead_size; ! 449: end = MIN(start + state->buf_len, s->len) - 1; ! 450: state->orig_buf = qemu_malloc(state->buf_len); ! 451: state->acb[0] = acb; ! 452: ! 453: snprintf(state->range, 127, "%lld-%lld", (long long)start, (long long)end); ! 454: dprintf("CURL (AIO): Reading %d at %lld (%s)\n", (nb_sectors * SECTOR_SIZE), start, state->range); ! 455: curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range); ! 456: ! 457: curl_multi_add_handle(s->multi, state->curl); ! 458: curl_multi_do(s); ! 459: ! 460: return &acb->common; ! 461: } ! 462: ! 463: static void curl_close(BlockDriverState *bs) ! 464: { ! 465: BDRVCURLState *s = bs->opaque; ! 466: int i; ! 467: ! 468: dprintf("CURL: Close\n"); ! 469: for (i=0; i<CURL_NUM_STATES; i++) { ! 470: if (s->states[i].in_use) ! 471: curl_clean_state(&s->states[i]); ! 472: if (s->states[i].curl) { ! 473: curl_easy_cleanup(s->states[i].curl); ! 474: s->states[i].curl = NULL; ! 475: } ! 476: if (s->states[i].orig_buf) { ! 477: qemu_free(s->states[i].orig_buf); ! 478: s->states[i].orig_buf = NULL; ! 479: } ! 480: } ! 481: if (s->multi) ! 482: curl_multi_cleanup(s->multi); ! 483: if (s->url) ! 484: free(s->url); ! 485: } ! 486: ! 487: static int64_t curl_getlength(BlockDriverState *bs) ! 488: { ! 489: BDRVCURLState *s = bs->opaque; ! 490: return s->len; ! 491: } ! 492: ! 493: static BlockDriver bdrv_http = { ! 494: .format_name = "http", ! 495: .protocol_name = "http", ! 496: ! 497: .instance_size = sizeof(BDRVCURLState), ! 498: .bdrv_open = curl_open, ! 499: .bdrv_close = curl_close, ! 500: .bdrv_getlength = curl_getlength, ! 501: ! 502: .bdrv_aio_readv = curl_aio_readv, ! 503: }; ! 504: ! 505: static BlockDriver bdrv_https = { ! 506: .format_name = "https", ! 507: .protocol_name = "https", ! 508: ! 509: .instance_size = sizeof(BDRVCURLState), ! 510: .bdrv_open = curl_open, ! 511: .bdrv_close = curl_close, ! 512: .bdrv_getlength = curl_getlength, ! 513: ! 514: .bdrv_aio_readv = curl_aio_readv, ! 515: }; ! 516: ! 517: static BlockDriver bdrv_ftp = { ! 518: .format_name = "ftp", ! 519: .protocol_name = "ftp", ! 520: ! 521: .instance_size = sizeof(BDRVCURLState), ! 522: .bdrv_open = curl_open, ! 523: .bdrv_close = curl_close, ! 524: .bdrv_getlength = curl_getlength, ! 525: ! 526: .bdrv_aio_readv = curl_aio_readv, ! 527: }; ! 528: ! 529: static BlockDriver bdrv_ftps = { ! 530: .format_name = "ftps", ! 531: .protocol_name = "ftps", ! 532: ! 533: .instance_size = sizeof(BDRVCURLState), ! 534: .bdrv_open = curl_open, ! 535: .bdrv_close = curl_close, ! 536: .bdrv_getlength = curl_getlength, ! 537: ! 538: .bdrv_aio_readv = curl_aio_readv, ! 539: }; ! 540: ! 541: static BlockDriver bdrv_tftp = { ! 542: .format_name = "tftp", ! 543: .protocol_name = "tftp", ! 544: ! 545: .instance_size = sizeof(BDRVCURLState), ! 546: .bdrv_open = curl_open, ! 547: .bdrv_close = curl_close, ! 548: .bdrv_getlength = curl_getlength, ! 549: ! 550: .bdrv_aio_readv = curl_aio_readv, ! 551: }; ! 552: ! 553: static void curl_block_init(void) ! 554: { ! 555: bdrv_register(&bdrv_http); ! 556: bdrv_register(&bdrv_https); ! 557: bdrv_register(&bdrv_ftp); ! 558: bdrv_register(&bdrv_ftps); ! 559: bdrv_register(&bdrv_tftp); ! 560: } ! 561: ! 562: block_init(curl_block_init);
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.