Annotation of qemu/block/curl.c, revision 1.1.1.6

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.