X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/ab9125c021e0901794dca7c55702d8ee5efd75fa..4a960ece17c94989d4082f5d3d8c32b34eded57c:/block/curl.c diff --git a/block/curl.c b/block/curl.c index 0404c1b5fa..b7ac265d3a 100644 --- a/block/curl.c +++ b/block/curl.c @@ -21,32 +21,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + #include "qemu/osdep.h" #include "qapi/error.h" -#include "qemu-common.h" #include "qemu/error-report.h" +#include "qemu/option.h" #include "block/block_int.h" -#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qdict.h" #include "qapi/qmp/qstring.h" #include "crypto/secret.h" #include #include "qemu/cutils.h" +#include "trace.h" -// #define DEBUG_CURL // #define DEBUG_VERBOSE -#ifdef DEBUG_CURL -#define DEBUG_CURL_PRINT 1 -#else -#define DEBUG_CURL_PRINT 0 -#endif -#define DPRINTF(fmt, ...) \ - do { \ - if (DEBUG_CURL_PRINT) { \ - fprintf(stderr, fmt, ## __VA_ARGS__); \ - } \ - } while (0) - #if LIBCURL_VERSION_NUM >= 0x071000 /* The multi interface timer callback was introduced in 7.16.0 */ #define NEED_CURL_TIMER_CALLBACK @@ -76,15 +65,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" @@ -92,12 +78,15 @@ static CURLMcode __curl_multi_socket_action(CURLM *multi_handle, struct BDRVCURLState; +static bool libcurl_initialized; + 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; @@ -115,7 +104,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 +115,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; @@ -135,6 +124,8 @@ typedef struct BDRVCURLState { char *cookie; bool accept_range; AioContext *aio_context; + QemuMutex mutex; + CoQueue free_state_waitq; char *username; char *password; char *proxyusername; @@ -146,11 +137,12 @@ 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; - DPRINTF("CURL: timer callback timeout_ms %ld\n", timeout_ms); + trace_curl_timer_cb(timeout_ms); if (timeout_ms == -1) { timer_del(&s->timer); } else { @@ -162,6 +154,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) { @@ -188,29 +181,30 @@ static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action, } socket = NULL; - DPRINTF("CURL (AIO): Sock action %d on fd %d\n", action, (int)fd); + trace_curl_sock_cb(action, (int)fd); switch (action) { case CURL_POLL_IN: aio_set_fd_handler(s->aio_context, fd, false, - curl_multi_read, NULL, state); + curl_multi_read, NULL, NULL, state); break; case CURL_POLL_OUT: aio_set_fd_handler(s->aio_context, fd, false, - NULL, curl_multi_do, state); + NULL, curl_multi_do, NULL, state); break; case CURL_POLL_INOUT: aio_set_fd_handler(s->aio_context, fd, false, - curl_multi_read, curl_multi_do, state); + curl_multi_read, curl_multi_do, NULL, state); break; case CURL_POLL_REMOVE: aio_set_fd_handler(s->aio_context, fd, false, - NULL, NULL, NULL); + NULL, NULL, NULL, NULL); break; } 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; @@ -225,13 +219,14 @@ 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); size_t realsize = size * nmemb; int i; - DPRINTF("CURL: Just reading %zd bytes\n", realsize); + trace_curl_read_cb(realsize); if (!s || !s->orig_buf) { goto read_end; @@ -252,7 +247,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); @@ -263,9 +258,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); } } @@ -274,18 +271,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; istates[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; @@ -304,9 +302,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 @@ -324,15 +321,16 @@ static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len, for (j=0; jacb[j]) { state->acb[j] = acb; - return FIND_RET_WAIT; + return true; } } } } - return FIND_RET_NONE; + return false; } +/* Called with s->mutex held. */ static void curl_multi_check_completion(BDRVCURLState *s) { int msgs_in_queue; @@ -374,9 +372,11 @@ static void curl_multi_check_completion(BDRVCURLState *s) continue; } - acb->common.cb(acb->common.opaque, -EPROTO); - qemu_aio_unref(acb); + acb->ret = -EIO; state->acb[i] = NULL; + qemu_mutex_unlock(&s->mutex); + aio_co_wake(acb->co); + qemu_mutex_lock(&s->mutex); } } @@ -386,9 +386,9 @@ static void curl_multi_check_completion(BDRVCURLState *s) } } -static void curl_multi_do(void *arg) +/* Called with s->mutex held. */ +static void curl_multi_do_locked(CURLState *s) { - CURLState *s = (CURLState *)arg; CURLSocket *socket, *next_socket; int running; int r; @@ -406,12 +406,23 @@ static void curl_multi_do(void *arg) } } +static void curl_multi_do(void *arg) +{ + CURLState *s = (CURLState *)arg; + + qemu_mutex_lock(&s->s->mutex); + curl_multi_do_locked(s); + qemu_mutex_unlock(&s->s->mutex); +} + static void curl_multi_read(void *arg) { CURLState *s = (CURLState *)arg; - curl_multi_do(arg); + qemu_mutex_lock(&s->s->mutex); + curl_multi_do_locked(s); curl_multi_check_completion(s->s); + qemu_mutex_unlock(&s->s->mutex); } static void curl_multi_timeout_do(void *arg) @@ -424,44 +435,44 @@ static void curl_multi_timeout_do(void *arg) return; } + qemu_mutex_lock(&s->mutex); curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running); curl_multi_check_completion(s); + qemu_mutex_unlock(&s->mutex); #else abort(); #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; istates[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, (long) s->sslverify); + curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYHOST, + s->sslverify ? 2L : 0L); if (s->cookie) { curl_easy_setopt(state->curl, CURLOPT_COOKIE, s->cookie); } @@ -510,11 +521,17 @@ 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) { + 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); @@ -526,12 +543,14 @@ static void curl_clean_state(CURLState *s) } s->in_use = 0; + + qemu_co_enter_next(&s->s->free_state_waitq, &s->s->mutex); } static void curl_parse_filename(const char *filename, QDict *options, Error **errp) { - qdict_put(options, CURL_BLOCK_OPT_URL, qstring_from_str(filename)); + qdict_put_str(options, CURL_BLOCK_OPT_URL, filename); } static void curl_detach_aio_context(BlockDriverState *bs) @@ -539,6 +558,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]); @@ -554,6 +574,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); } @@ -606,6 +627,11 @@ static QemuOptsList runtime_opts = { .type = QEMU_OPT_STRING, .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, @@ -640,16 +666,28 @@ 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; + int ret; - static int inited = 0; + ret = bdrv_apply_auto_read_only(bs, "curl driver does not support writes", + errp); + if (ret < 0) { + return ret; + } - if (flags & BDRV_O_RDWR) { - error_setg(errp, "curl block device does not support writes"); - return -EROFS; + if (!libcurl_initialized) { + ret = curl_global_init(CURL_GLOBAL_ALL); + if (ret) { + error_setg(errp, "libcurl initialization failed with %d", ret); + return -EIO; + } + libcurl_initialized = true; } + 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) { @@ -675,7 +713,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) { @@ -683,6 +736,15 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, goto out_noclean; } + if (!strstart(file, bs->drv->protocol_name, &protocol_delimiter) || + !strstart(protocol_delimiter, "://", NULL)) + { + error_setg(errp, "%s curl driver cannot handle the URL '%s' (does not " + "start with '%s://')", bs->drv->protocol_name, file, + bs->drv->protocol_name); + goto out_noclean; + } + s->username = g_strdup(qemu_opt_get(opts, CURL_BLOCK_OPT_USERNAME)); secretid = qemu_opt_get(opts, CURL_BLOCK_OPT_PASSWORD_SECRET); @@ -703,20 +765,23 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, } } - if (!inited) { - curl_global_init(CURL_GLOBAL_ALL); - inited = 1; - } - - DPRINTF("CURL: Opening %s\n", file); + trace_curl_open(file); + qemu_co_queue_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, @@ -729,7 +794,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags, } /* Prior CURL 7.19.4 return value of 0 could mean that the file size is not * know or the size is zero. From 7.19.4 CURL returns -1 if size is not - * known and zero if it is realy zero-length file. */ + * known and zero if it is really zero-length file. */ #if LIBCURL_VERSION_NUM >= 0x071304 if (d < 0) { pstrcpy(state->errmsg, CURL_ERROR_SIZE, @@ -744,7 +809,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://"))) @@ -753,9 +818,11 @@ 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); + trace_curl_open_size(s->len); + qemu_mutex_lock(&s->mutex); curl_clean_state(state); + qemu_mutex_unlock(&s->mutex); curl_easy_cleanup(state->curl); state->curl = NULL; @@ -769,50 +836,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); + g_free(s->username); + g_free(s->proxyusername); + g_free(s->proxypassword); 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; - CURLAIOCB *acb = p; - BDRVCURLState *s = acb->common.bs->opaque; + BDRVCURLState *s = bs->opaque; + + uint64_t start = acb->offset; + uint64_t end; - size_t start = acb->sector_num * BDRV_SECTOR_SIZE; - size_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: - return; - 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) { - acb->common.cb(acb->common.opaque, -EIO); - qemu_aio_unref(acb); - return; + for (;;) { + state = curl_find_state(s); + if (state) { + break; + } + qemu_co_queue_wait(&s->free_state_waitq, &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); @@ -822,48 +890,55 @@ 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); - acb->common.cb(acb->common.opaque, -ENOMEM); - qemu_aio_unref(acb); - return; + 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); + trace_curl_setup_preadv(acb->bytes, start, state->range); curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range); curl_multi_add_handle(s->multi, state->curl); /* Tell curl it needs to kick things off */ curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running); + +out: + qemu_mutex_unlock(&s->mutex); } -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) { BDRVCURLState *s = bs->opaque; - DPRINTF("CURL: Close\n"); + trace_curl_close(); curl_detach_aio_context(bs); + qemu_mutex_destroy(&s->mutex); g_free(s->cookie); g_free(s->url); + g_free(s->username); + g_free(s->proxyusername); + g_free(s->proxypassword); } static int64_t curl_getlength(BlockDriverState *bs) @@ -882,7 +957,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, @@ -898,7 +973,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, @@ -914,7 +989,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, @@ -930,7 +1005,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,