Commit fefb28a4 authored by Stefan Hajnoczi's avatar Stefan Hajnoczi
Browse files

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: default avatarStefan Hajnoczi <stefanha@redhat.com>
parents cdece046 2bb5c936
Loading
Loading
Loading
Loading
+144 −97
Original line number Diff line number Diff line
@@ -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,
/* 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;
    int i;

    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;

        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);
}
@@ -623,6 +643,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,
@@ -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);
    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:
    if (curl_find_buf(s, start, acb->bytes, acb)) {
        goto out;
        default:
            break;
    }

    // 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;
    CURLAIOCB acb = {
        .co = qemu_coroutine_self(),
        .ret = -EINPROGRESS,
        .qiov = qiov,
        .offset = offset,
        .bytes = bytes
    };

    aio_bh_schedule_oneshot(bdrv_get_aio_context(bs), curl_readv_bh_cb, acb);
    return &acb->common;
    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,
+10 −2
Original line number Diff line number Diff line
@@ -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: