diff options
author | Stefan Hajnoczi <stefanha@redhat.com> | 2017-05-17 13:52:02 +0100 |
---|---|---|
committer | Stefan Hajnoczi <stefanha@redhat.com> | 2017-05-17 13:52:07 +0100 |
commit | fefb28a4712cf39140481f9525a63aac94b61186 (patch) | |
tree | 10379e413f9fda78542eaddbc4cc8c5fe41c67fe | |
parent | cdece0467c00007cf8e3f4b3c3f0b13bf2c4fea9 (diff) | |
parent | 2bb5c936c5827e1d831002f7a7517cb8c2c2201d (diff) |
Merge remote-tracking branch 'jtc/tags/block-pull-request' into staging
# gpg: Signature made Tue 16 May 2017 04:47:09 PM BST
# gpg: using RSA key 0xBDBE7B27C0DE3057
# gpg: Good signature from "Jeffrey Cody <jcody@redhat.com>"
# gpg: aka "Jeffrey Cody <jeff@codyprime.org>"
# gpg: aka "Jeffrey Cody <codyprime@gmail.com>"
# Primary key fingerprint: 9957 4B4D 3474 90E7 9D98 D624 BDBE 7B27 C0DE 3057
* jtc/tags/block-pull-request:
curl: do not do aio_poll when waiting for a free CURLState
curl: convert readv to coroutines
curl: convert CURLAIOCB to byte values
curl: split curl_find_state/curl_init_state
curl: avoid recursive locking of BDRVCURLState mutex
curl: never invoke callbacks with s->mutex held
curl: strengthen assertion in curl_clean_state
block: curl: Allow passing cookies via QCryptoSecret
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-rw-r--r-- | block/curl.c | 241 | ||||
-rw-r--r-- | qapi/block-core.json | 12 |
2 files changed, 154 insertions, 99 deletions
diff --git a/block/curl.c b/block/curl.c index aa6e8cc0e5..2a244e2439 100644 --- a/block/curl.c +++ b/block/curl.c @@ -76,15 +76,12 @@ static CURLMcode __curl_multi_socket_action(CURLM *multi_handle, #define CURL_TIMEOUT_DEFAULT 5 #define CURL_TIMEOUT_MAX 10000 -#define FIND_RET_NONE 0 -#define FIND_RET_OK 1 -#define FIND_RET_WAIT 2 - #define CURL_BLOCK_OPT_URL "url" #define CURL_BLOCK_OPT_READAHEAD "readahead" #define CURL_BLOCK_OPT_SSLVERIFY "sslverify" #define CURL_BLOCK_OPT_TIMEOUT "timeout" #define CURL_BLOCK_OPT_COOKIE "cookie" +#define CURL_BLOCK_OPT_COOKIE_SECRET "cookie-secret" #define CURL_BLOCK_OPT_USERNAME "username" #define CURL_BLOCK_OPT_PASSWORD_SECRET "password-secret" #define CURL_BLOCK_OPT_PROXY_USERNAME "proxy-username" @@ -93,14 +90,17 @@ static CURLMcode __curl_multi_socket_action(CURLM *multi_handle, struct BDRVCURLState; typedef struct CURLAIOCB { - BlockAIOCB common; + Coroutine *co; QEMUIOVector *qiov; - int64_t sector_num; - int nb_sectors; + uint64_t offset; + uint64_t bytes; + int ret; size_t start; size_t end; + + QSIMPLEQ_ENTRY(CURLAIOCB) next; } CURLAIOCB; typedef struct CURLSocket { @@ -115,7 +115,7 @@ typedef struct CURLState CURL *curl; QLIST_HEAD(, CURLSocket) sockets; char *orig_buf; - size_t buf_start; + uint64_t buf_start; size_t buf_off; size_t buf_len; char range[128]; @@ -126,7 +126,7 @@ typedef struct CURLState typedef struct BDRVCURLState { CURLM *multi; QEMUTimer timer; - size_t len; + uint64_t len; CURLState states[CURL_NUM_STATES]; char *url; size_t readahead_size; @@ -136,6 +136,7 @@ typedef struct BDRVCURLState { bool accept_range; AioContext *aio_context; QemuMutex mutex; + QSIMPLEQ_HEAD(, CURLAIOCB) free_state_waitq; char *username; char *password; char *proxyusername; @@ -147,6 +148,7 @@ static void curl_multi_do(void *arg); static void curl_multi_read(void *arg); #ifdef NEED_CURL_TIMER_CALLBACK +/* Called from curl_multi_do_locked, with s->mutex held. */ static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque) { BDRVCURLState *s = opaque; @@ -163,6 +165,7 @@ static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque) } #endif +/* Called from curl_multi_do_locked, with s->mutex held. */ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action, void *userp, void *sp) { @@ -212,6 +215,7 @@ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action, return 0; } +/* Called from curl_multi_do_locked, with s->mutex held. */ static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque) { BDRVCURLState *s = opaque; @@ -226,6 +230,7 @@ static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque) return realsize; } +/* Called from curl_multi_do_locked, with s->mutex held. */ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque) { CURLState *s = ((CURLState*)opaque); @@ -253,7 +258,7 @@ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque) continue; if ((s->buf_off >= acb->end)) { - size_t request_length = acb->nb_sectors * BDRV_SECTOR_SIZE; + size_t request_length = acb->bytes; qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start, acb->end - acb->start); @@ -264,9 +269,11 @@ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque) request_length - offset); } - acb->common.cb(acb->common.opaque, 0); - qemu_aio_unref(acb); + acb->ret = 0; s->acb[i] = NULL; + qemu_mutex_unlock(&s->s->mutex); + aio_co_wake(acb->co); + qemu_mutex_lock(&s->s->mutex); } } @@ -275,18 +282,19 @@ read_end: return size * nmemb; } -static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len, - CURLAIOCB *acb) +/* Called with s->mutex held. */ +static bool curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len, + CURLAIOCB *acb) { int i; - size_t end = start + len; - size_t clamped_end = MIN(end, s->len); - size_t clamped_len = clamped_end - start; + uint64_t end = start + len; + uint64_t clamped_end = MIN(end, s->len); + uint64_t clamped_len = clamped_end - start; for (i=0; i<CURL_NUM_STATES; i++) { CURLState *state = &s->states[i]; - size_t buf_end = (state->buf_start + state->buf_off); - size_t buf_fend = (state->buf_start + state->buf_len); + uint64_t buf_end = (state->buf_start + state->buf_off); + uint64_t buf_fend = (state->buf_start + state->buf_len); if (!state->orig_buf) continue; @@ -305,9 +313,8 @@ static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len, if (clamped_len < len) { qemu_iovec_memset(acb->qiov, clamped_len, 0, len - clamped_len); } - acb->common.cb(acb->common.opaque, 0); - - return FIND_RET_OK; + acb->ret = 0; + return true; } // Wait for unfinished chunks @@ -325,13 +332,13 @@ static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len, for (j=0; j<CURL_NUM_ACB; j++) { if (!state->acb[j]) { state->acb[j] = acb; - return FIND_RET_WAIT; + return true; } } } } - return FIND_RET_NONE; + return false; } /* Called with s->mutex held. */ @@ -376,11 +383,11 @@ static void curl_multi_check_completion(BDRVCURLState *s) continue; } + acb->ret = -EIO; + state->acb[i] = NULL; qemu_mutex_unlock(&s->mutex); - acb->common.cb(acb->common.opaque, -EIO); + aio_co_wake(acb->co); qemu_mutex_lock(&s->mutex); - qemu_aio_unref(acb); - state->acb[i] = NULL; } } @@ -449,32 +456,28 @@ static void curl_multi_timeout_do(void *arg) #endif } -static CURLState *curl_init_state(BlockDriverState *bs, BDRVCURLState *s) +/* Called with s->mutex held. */ +static CURLState *curl_find_state(BDRVCURLState *s) { CURLState *state = NULL; - int i, j; - - do { - for (i=0; i<CURL_NUM_STATES; i++) { - for (j=0; j<CURL_NUM_ACB; j++) - if (s->states[i].acb[j]) - continue; - if (s->states[i].in_use) - continue; + int i; + for (i = 0; i < CURL_NUM_STATES; i++) { + if (!s->states[i].in_use) { state = &s->states[i]; state->in_use = 1; break; } - if (!state) { - aio_poll(bdrv_get_aio_context(bs), true); - } - } while(!state); + } + return state; +} +static int curl_init_state(BDRVCURLState *s, CURLState *state) +{ if (!state->curl) { state->curl = curl_easy_init(); if (!state->curl) { - return NULL; + return -EIO; } curl_easy_setopt(state->curl, CURLOPT_URL, s->url); curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYPEER, @@ -527,11 +530,18 @@ static CURLState *curl_init_state(BlockDriverState *bs, BDRVCURLState *s) QLIST_INIT(&state->sockets); state->s = s; - return state; + return 0; } +/* Called with s->mutex held. */ static void curl_clean_state(CURLState *s) { + CURLAIOCB *next; + int j; + for (j = 0; j < CURL_NUM_ACB; j++) { + assert(!s->acb[j]); + } + if (s->s->multi) curl_multi_remove_handle(s->s->multi, s->curl); @@ -543,6 +553,14 @@ static void curl_clean_state(CURLState *s) } s->in_use = 0; + + next = QSIMPLEQ_FIRST(&s->s->free_state_waitq); + if (next) { + QSIMPLEQ_REMOVE_HEAD(&s->s->free_state_waitq, next); + qemu_mutex_unlock(&s->s->mutex); + aio_co_wake(next->co); + qemu_mutex_lock(&s->s->mutex); + } } static void curl_parse_filename(const char *filename, QDict *options, @@ -556,6 +574,7 @@ static void curl_detach_aio_context(BlockDriverState *bs) BDRVCURLState *s = bs->opaque; int i; + qemu_mutex_lock(&s->mutex); for (i = 0; i < CURL_NUM_STATES; i++) { if (s->states[i].in_use) { curl_clean_state(&s->states[i]); @@ -571,6 +590,7 @@ static void curl_detach_aio_context(BlockDriverState *bs) curl_multi_cleanup(s->multi); s->multi = NULL; } + qemu_mutex_unlock(&s->mutex); timer_del(&s->timer); } @@ -624,6 +644,11 @@ static QemuOptsList runtime_opts = { .help = "Pass the cookie or list of cookies with each request" }, { + .name = CURL_BLOCK_OPT_COOKIE_SECRET, + .type = QEMU_OPT_STRING, + .help = "ID of secret used as cookie passed with each request" + }, + { .name = CURL_BLOCK_OPT_USERNAME, .type = QEMU_OPT_STRING, .help = "Username for HTTP auth" @@ -657,6 +682,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, Error *local_err = NULL; const char *file; const char *cookie; + const char *cookie_secret; double d; const char *secretid; const char *protocol_delimiter; @@ -668,6 +694,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, return -EROFS; } + qemu_mutex_init(&s->mutex); opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); qemu_opts_absorb_qdict(opts, options, &local_err); if (local_err) { @@ -693,7 +720,22 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, s->sslverify = qemu_opt_get_bool(opts, CURL_BLOCK_OPT_SSLVERIFY, true); cookie = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE); - s->cookie = g_strdup(cookie); + cookie_secret = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE_SECRET); + + if (cookie && cookie_secret) { + error_setg(errp, + "curl driver cannot handle both cookie and cookie secret"); + goto out_noclean; + } + + if (cookie_secret) { + s->cookie = qcrypto_secret_lookup_as_utf8(cookie_secret, errp); + if (!s->cookie) { + goto out_noclean; + } + } else { + s->cookie = g_strdup(cookie); + } file = qemu_opt_get(opts, CURL_BLOCK_OPT_URL); if (file == NULL) { @@ -736,14 +778,22 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, } DPRINTF("CURL: Opening %s\n", file); + QSIMPLEQ_INIT(&s->free_state_waitq); s->aio_context = bdrv_get_aio_context(bs); s->url = g_strdup(file); - state = curl_init_state(bs, s); - if (!state) + qemu_mutex_lock(&s->mutex); + state = curl_find_state(s); + qemu_mutex_unlock(&s->mutex); + if (!state) { goto out_noclean; + } // Get file size + if (curl_init_state(s, state) < 0) { + goto out; + } + s->accept_range = false; curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1); curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION, @@ -771,7 +821,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, } #endif - s->len = (size_t)d; + s->len = d; if ((!strncasecmp(s->url, "http://", strlen("http://")) || !strncasecmp(s->url, "https://", strlen("https://"))) @@ -780,13 +830,14 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, "Server does not support 'range' (byte ranges)."); goto out; } - DPRINTF("CURL: Size = %zd\n", s->len); + DPRINTF("CURL: Size = %" PRIu64 "\n", s->len); + qemu_mutex_lock(&s->mutex); curl_clean_state(state); + qemu_mutex_unlock(&s->mutex); curl_easy_cleanup(state->curl); state->curl = NULL; - qemu_mutex_init(&s->mutex); curl_attach_aio_context(bs, bdrv_get_aio_context(bs)); qemu_opts_del(opts); @@ -797,53 +848,51 @@ out: curl_easy_cleanup(state->curl); state->curl = NULL; out_noclean: + qemu_mutex_destroy(&s->mutex); g_free(s->cookie); g_free(s->url); qemu_opts_del(opts); return -EINVAL; } -static const AIOCBInfo curl_aiocb_info = { - .aiocb_size = sizeof(CURLAIOCB), -}; - - -static void curl_readv_bh_cb(void *p) +static void curl_setup_preadv(BlockDriverState *bs, CURLAIOCB *acb) { CURLState *state; int running; - int ret = -EINPROGRESS; - CURLAIOCB *acb = p; - BlockDriverState *bs = acb->common.bs; BDRVCURLState *s = bs->opaque; - size_t start = acb->sector_num * BDRV_SECTOR_SIZE; - size_t end; + uint64_t start = acb->offset; + uint64_t end; qemu_mutex_lock(&s->mutex); // In case we have the requested data already (e.g. read-ahead), // we can just call the callback and be done. - switch (curl_find_buf(s, start, acb->nb_sectors * BDRV_SECTOR_SIZE, acb)) { - case FIND_RET_OK: - qemu_aio_unref(acb); - // fall through - case FIND_RET_WAIT: - goto out; - default: - break; + if (curl_find_buf(s, start, acb->bytes, acb)) { + goto out; } // No cache found, so let's start a new request - state = curl_init_state(acb->common.bs, s); - if (!state) { - ret = -EIO; + for (;;) { + state = curl_find_state(s); + if (state) { + break; + } + QSIMPLEQ_INSERT_TAIL(&s->free_state_waitq, acb, next); + qemu_mutex_unlock(&s->mutex); + qemu_coroutine_yield(); + qemu_mutex_lock(&s->mutex); + } + + if (curl_init_state(s, state) < 0) { + curl_clean_state(state); + acb->ret = -EIO; goto out; } acb->start = 0; - acb->end = MIN(acb->nb_sectors * BDRV_SECTOR_SIZE, s->len - start); + acb->end = MIN(acb->bytes, s->len - start); state->buf_off = 0; g_free(state->orig_buf); @@ -853,14 +902,14 @@ static void curl_readv_bh_cb(void *p) state->orig_buf = g_try_malloc(state->buf_len); if (state->buf_len && state->orig_buf == NULL) { curl_clean_state(state); - ret = -ENOMEM; + acb->ret = -ENOMEM; goto out; } state->acb[0] = acb; - snprintf(state->range, 127, "%zd-%zd", start, end); - DPRINTF("CURL (AIO): Reading %llu at %zd (%s)\n", - (acb->nb_sectors * BDRV_SECTOR_SIZE), start, state->range); + snprintf(state->range, 127, "%" PRIu64 "-%" PRIu64, start, end); + DPRINTF("CURL (AIO): Reading %" PRIu64 " at %" PRIu64 " (%s)\n", + acb->bytes, start, state->range); curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range); curl_multi_add_handle(s->multi, state->curl); @@ -870,26 +919,24 @@ static void curl_readv_bh_cb(void *p) out: qemu_mutex_unlock(&s->mutex); - if (ret != -EINPROGRESS) { - acb->common.cb(acb->common.opaque, ret); - qemu_aio_unref(acb); - } } -static BlockAIOCB *curl_aio_readv(BlockDriverState *bs, - int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, - BlockCompletionFunc *cb, void *opaque) +static int coroutine_fn curl_co_preadv(BlockDriverState *bs, + uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags) { - CURLAIOCB *acb; - - acb = qemu_aio_get(&curl_aiocb_info, bs, cb, opaque); - - acb->qiov = qiov; - acb->sector_num = sector_num; - acb->nb_sectors = nb_sectors; - - aio_bh_schedule_oneshot(bdrv_get_aio_context(bs), curl_readv_bh_cb, acb); - return &acb->common; + CURLAIOCB acb = { + .co = qemu_coroutine_self(), + .ret = -EINPROGRESS, + .qiov = qiov, + .offset = offset, + .bytes = bytes + }; + + curl_setup_preadv(bs, &acb); + while (acb.ret == -EINPROGRESS) { + qemu_coroutine_yield(); + } + return acb.ret; } static void curl_close(BlockDriverState *bs) @@ -920,7 +967,7 @@ static BlockDriver bdrv_http = { .bdrv_close = curl_close, .bdrv_getlength = curl_getlength, - .bdrv_aio_readv = curl_aio_readv, + .bdrv_co_preadv = curl_co_preadv, .bdrv_detach_aio_context = curl_detach_aio_context, .bdrv_attach_aio_context = curl_attach_aio_context, @@ -936,7 +983,7 @@ static BlockDriver bdrv_https = { .bdrv_close = curl_close, .bdrv_getlength = curl_getlength, - .bdrv_aio_readv = curl_aio_readv, + .bdrv_co_preadv = curl_co_preadv, .bdrv_detach_aio_context = curl_detach_aio_context, .bdrv_attach_aio_context = curl_attach_aio_context, @@ -952,7 +999,7 @@ static BlockDriver bdrv_ftp = { .bdrv_close = curl_close, .bdrv_getlength = curl_getlength, - .bdrv_aio_readv = curl_aio_readv, + .bdrv_co_preadv = curl_co_preadv, .bdrv_detach_aio_context = curl_detach_aio_context, .bdrv_attach_aio_context = curl_attach_aio_context, @@ -968,7 +1015,7 @@ static BlockDriver bdrv_ftps = { .bdrv_close = curl_close, .bdrv_getlength = curl_getlength, - .bdrv_aio_readv = curl_aio_readv, + .bdrv_co_preadv = curl_co_preadv, .bdrv_detach_aio_context = curl_detach_aio_context, .bdrv_attach_aio_context = curl_attach_aio_context, diff --git a/qapi/block-core.json b/qapi/block-core.json index 6b974b952f..ea0b3e8b13 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -2813,11 +2813,15 @@ # "name1=content1; name2=content2;" as explained by # CURLOPT_COOKIE(3). Defaults to no cookies. # +# @cookie-secret: ID of a QCryptoSecret object providing the cookie data in a +# secure way. See @cookie for the format. (since 2.10) +# # Since: 2.9 ## { 'struct': 'BlockdevOptionsCurlHttp', 'base': 'BlockdevOptionsCurlBase', - 'data': { '*cookie': 'str' } } + 'data': { '*cookie': 'str', + '*cookie-secret': 'str'} } ## # @BlockdevOptionsCurlHttps: @@ -2832,12 +2836,16 @@ # @sslverify: Whether to verify the SSL certificate's validity (defaults to # true) # +# @cookie-secret: ID of a QCryptoSecret object providing the cookie data in a +# secure way. See @cookie for the format. (since 2.10) +# # Since: 2.9 ## { 'struct': 'BlockdevOptionsCurlHttps', 'base': 'BlockdevOptionsCurlBase', 'data': { '*cookie': 'str', - '*sslverify': 'bool' } } + '*sslverify': 'bool', + '*cookie-secret': 'str'} } ## # @BlockdevOptionsCurlFtp: |