#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
+#include "qemu/module.h"
#include "qemu/option.h"
#include "block/block_int.h"
#include "qapi/qmp/qdict.h"
#include "crypto/secret.h"
#include <curl/curl.h>
#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
#define CURL_NUM_STATES 8
#define CURL_NUM_ACB 8
-#define READ_AHEAD_DEFAULT (256 * 1024)
-#define CURL_TIMEOUT_DEFAULT 5
#define CURL_TIMEOUT_MAX 10000
#define CURL_BLOCK_OPT_URL "url"
#define CURL_BLOCK_OPT_PROXY_USERNAME "proxy-username"
#define CURL_BLOCK_OPT_PROXY_PASSWORD_SECRET "proxy-password-secret"
+#define CURL_BLOCK_OPT_READAHEAD_DEFAULT (256 * 1024)
+#define CURL_BLOCK_OPT_SSLVERIFY_DEFAULT true
+#define CURL_BLOCK_OPT_TIMEOUT_DEFAULT 5
+
struct BDRVCURLState;
+struct CURLState;
static bool libcurl_initialized;
typedef struct CURLSocket {
int fd;
+ struct CURLState *state;
QLIST_ENTRY(CURLSocket) next;
} CURLSocket;
static void curl_clean_state(CURLState *s);
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. */
{
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 {
QLIST_FOREACH(socket, &state->sockets, next) {
if (socket->fd == fd) {
- if (action == CURL_POLL_REMOVE) {
- QLIST_REMOVE(socket, next);
- g_free(socket);
- }
break;
}
}
if (!socket) {
socket = g_new0(CURLSocket, 1);
socket->fd = fd;
+ socket->state = state;
QLIST_INSERT_HEAD(&state->sockets, socket, next);
}
- 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, NULL, state);
+ curl_multi_do, NULL, NULL, socket);
break;
case CURL_POLL_OUT:
aio_set_fd_handler(s->aio_context, fd, false,
- NULL, curl_multi_do, NULL, state);
+ NULL, curl_multi_do, NULL, socket);
break;
case CURL_POLL_INOUT:
aio_set_fd_handler(s->aio_context, fd, false,
- curl_multi_read, curl_multi_do, NULL, state);
+ curl_multi_do, curl_multi_do, NULL, socket);
break;
case CURL_POLL_REMOVE:
aio_set_fd_handler(s->aio_context, fd, false,
break;
}
+ if (action == CURL_POLL_REMOVE) {
+ QLIST_REMOVE(socket, next);
+ g_free(socket);
+ }
+
return 0;
}
{
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;
memcpy(s->orig_buf + s->buf_off, ptr, realsize);
s->buf_off += realsize;
- for(i=0; i<CURL_NUM_ACB; i++) {
- CURLAIOCB *acb = s->acb[i];
-
- if (!acb)
- continue;
-
- if ((s->buf_off >= acb->end)) {
- size_t request_length = acb->bytes;
-
- qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start,
- acb->end - acb->start);
-
- if (acb->end - acb->start < request_length) {
- size_t offset = acb->end - acb->start;
- qemu_iovec_memset(acb->qiov, offset, 0,
- request_length - offset);
- }
-
- acb->ret = 0;
- s->acb[i] = NULL;
- qemu_mutex_unlock(&s->s->mutex);
- aio_co_wake(acb->co);
- qemu_mutex_lock(&s->s->mutex);
- }
- }
-
read_end:
/* curl will error out if we do not return this value */
return size * nmemb;
break;
if (msg->msg == CURLMSG_DONE) {
+ int i;
CURLState *state = NULL;
+ bool error = msg->data.result != CURLE_OK;
+
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE,
(char **)&state);
- /* ACBs for successful messages get completed in curl_read_cb */
- if (msg->data.result != CURLE_OK) {
- int i;
+ if (error) {
static int errcount = 100;
/* Don't lose the original error message from curl, since
error_report("curl: further errors suppressed");
}
}
+ }
- for (i = 0; i < CURL_NUM_ACB; i++) {
- CURLAIOCB *acb = state->acb[i];
+ for (i = 0; i < CURL_NUM_ACB; i++) {
+ CURLAIOCB *acb = state->acb[i];
- if (acb == NULL) {
- continue;
- }
+ if (acb == NULL) {
+ continue;
+ }
+
+ if (!error) {
+ /* Assert that we have read all data */
+ assert(state->buf_off >= acb->end);
+
+ qemu_iovec_from_buf(acb->qiov, 0,
+ state->orig_buf + acb->start,
+ acb->end - acb->start);
- acb->ret = -EIO;
- state->acb[i] = NULL;
- qemu_mutex_unlock(&s->mutex);
- aio_co_wake(acb->co);
- qemu_mutex_lock(&s->mutex);
+ if (acb->end - acb->start < acb->bytes) {
+ size_t offset = acb->end - acb->start;
+ qemu_iovec_memset(acb->qiov, offset, 0,
+ acb->bytes - offset);
+ }
}
+
+ acb->ret = error ? -EIO : 0;
+ state->acb[i] = NULL;
+ qemu_mutex_unlock(&s->mutex);
+ aio_co_wake(acb->co);
+ qemu_mutex_lock(&s->mutex);
}
curl_clean_state(state);
}
/* Called with s->mutex held. */
-static void curl_multi_do_locked(CURLState *s)
+static void curl_multi_do_locked(CURLSocket *socket)
{
- CURLSocket *socket, *next_socket;
+ BDRVCURLState *s = socket->state->s;
int running;
int r;
- if (!s->s->multi) {
+ if (!s->multi) {
return;
}
- /* Need to use _SAFE because curl_multi_socket_action() may trigger
- * curl_sock_cb() which might modify this list */
- QLIST_FOREACH_SAFE(socket, &s->sockets, next, next_socket) {
- do {
- r = curl_multi_socket_action(s->s->multi, socket->fd, 0, &running);
- } while (r == CURLM_CALL_MULTI_PERFORM);
- }
+ do {
+ r = curl_multi_socket_action(s->multi, socket->fd, 0, &running);
+ } while (r == CURLM_CALL_MULTI_PERFORM);
}
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;
+ CURLSocket *socket = arg;
+ BDRVCURLState *s = socket->state->s;
- qemu_mutex_lock(&s->s->mutex);
- curl_multi_do_locked(s);
- curl_multi_check_completion(s->s);
- qemu_mutex_unlock(&s->s->mutex);
+ qemu_mutex_lock(&s->mutex);
+ curl_multi_do_locked(socket);
+ curl_multi_check_completion(s);
+ qemu_mutex_unlock(&s->mutex);
}
static void curl_multi_timeout_do(void *arg)
}
s->readahead_size = qemu_opt_get_size(opts, CURL_BLOCK_OPT_READAHEAD,
- READ_AHEAD_DEFAULT);
+ CURL_BLOCK_OPT_READAHEAD_DEFAULT);
if ((s->readahead_size & 0x1ff) != 0) {
error_setg(errp, "HTTP_READAHEAD_SIZE %zd is not a multiple of 512",
s->readahead_size);
}
s->timeout = qemu_opt_get_number(opts, CURL_BLOCK_OPT_TIMEOUT,
- CURL_TIMEOUT_DEFAULT);
+ CURL_BLOCK_OPT_TIMEOUT_DEFAULT);
if (s->timeout > CURL_TIMEOUT_MAX) {
error_setg(errp, "timeout parameter is too large or negative");
goto out_noclean;
}
- s->sslverify = qemu_opt_get_bool(opts, CURL_BLOCK_OPT_SSLVERIFY, true);
+ s->sslverify = qemu_opt_get_bool(opts, CURL_BLOCK_OPT_SSLVERIFY,
+ CURL_BLOCK_OPT_SSLVERIFY_DEFAULT);
cookie = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE);
cookie_secret = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE_SECRET);
}
}
- 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);
"Server does not support 'range' (byte ranges).");
goto out;
}
- DPRINTF("CURL: Size = %" PRIu64 "\n", s->len);
+ trace_curl_open_size(s->len);
qemu_mutex_lock(&s->mutex);
curl_clean_state(state);
state->acb[0] = acb;
snprintf(state->range, 127, "%" PRIu64 "-%" PRIu64, start, end);
- DPRINTF("CURL (AIO): Reading %" PRIu64 " at %" PRIu64 " (%s)\n",
- acb->bytes, start, state->range);
+ 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);
+ if (curl_multi_add_handle(s->multi, state->curl) != CURLM_OK) {
+ state->acb[0] = NULL;
+ acb->ret = -EIO;
+
+ curl_clean_state(state);
+ goto out;
+ }
/* Tell curl it needs to kick things off */
curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running);
{
BDRVCURLState *s = bs->opaque;
- DPRINTF("CURL: Close\n");
+ trace_curl_close();
curl_detach_aio_context(bs);
qemu_mutex_destroy(&s->mutex);
return s->len;
}
+static void curl_refresh_filename(BlockDriverState *bs)
+{
+ BDRVCURLState *s = bs->opaque;
+
+ /* "readahead" and "timeout" do not change the guest-visible data,
+ * so ignore them */
+ if (s->sslverify != CURL_BLOCK_OPT_SSLVERIFY_DEFAULT ||
+ s->cookie || s->username || s->password || s->proxyusername ||
+ s->proxypassword)
+ {
+ return;
+ }
+
+ pstrcpy(bs->exact_filename, sizeof(bs->exact_filename), s->url);
+}
+
+
+static const char *const curl_strong_runtime_opts[] = {
+ CURL_BLOCK_OPT_URL,
+ CURL_BLOCK_OPT_SSLVERIFY,
+ CURL_BLOCK_OPT_COOKIE,
+ CURL_BLOCK_OPT_COOKIE_SECRET,
+ CURL_BLOCK_OPT_USERNAME,
+ CURL_BLOCK_OPT_PASSWORD_SECRET,
+ CURL_BLOCK_OPT_PROXY_USERNAME,
+ CURL_BLOCK_OPT_PROXY_PASSWORD_SECRET,
+
+ NULL
+};
+
static BlockDriver bdrv_http = {
.format_name = "http",
.protocol_name = "http",
.bdrv_detach_aio_context = curl_detach_aio_context,
.bdrv_attach_aio_context = curl_attach_aio_context,
+
+ .bdrv_refresh_filename = curl_refresh_filename,
+ .strong_runtime_opts = curl_strong_runtime_opts,
};
static BlockDriver bdrv_https = {
.bdrv_detach_aio_context = curl_detach_aio_context,
.bdrv_attach_aio_context = curl_attach_aio_context,
+
+ .bdrv_refresh_filename = curl_refresh_filename,
+ .strong_runtime_opts = curl_strong_runtime_opts,
};
static BlockDriver bdrv_ftp = {
.bdrv_detach_aio_context = curl_detach_aio_context,
.bdrv_attach_aio_context = curl_attach_aio_context,
+
+ .bdrv_refresh_filename = curl_refresh_filename,
+ .strong_runtime_opts = curl_strong_runtime_opts,
};
static BlockDriver bdrv_ftps = {
.bdrv_detach_aio_context = curl_detach_aio_context,
.bdrv_attach_aio_context = curl_attach_aio_context,
+
+ .bdrv_refresh_filename = curl_refresh_filename,
+ .strong_runtime_opts = curl_strong_runtime_opts,
};
static void curl_block_init(void)