#include "qemu/error-report.h"
#include "qapi/error.h"
#include "block/block_int.h"
+#include "block/qdict.h"
#include "trace.h"
#include "qemu/iov.h"
#include "qemu/option.h"
#include "qemu/uri.h"
#include "qemu/cutils.h"
#include "sysemu/sysemu.h"
+#include "qapi/qapi-visit-block-core.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qstring.h"
-#include "qapi-visit.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
#include <nfsc/libnfs.h>
return task.ret;
}
-static QemuOptsList runtime_opts = {
- .name = "nfs",
- .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
- .desc = {
- {
- .name = "path",
- .type = QEMU_OPT_STRING,
- .help = "Path of the image on the host",
- },
- {
- .name = "user",
- .type = QEMU_OPT_NUMBER,
- .help = "UID value to use when talking to the server",
- },
- {
- .name = "group",
- .type = QEMU_OPT_NUMBER,
- .help = "GID value to use when talking to the server",
- },
- {
- .name = "tcp-syn-count",
- .type = QEMU_OPT_NUMBER,
- .help = "Number of SYNs to send during the session establish",
- },
- {
- .name = "readahead-size",
- .type = QEMU_OPT_NUMBER,
- .help = "Set the readahead size in bytes",
- },
- {
- .name = "page-cache-size",
- .type = QEMU_OPT_NUMBER,
- .help = "Set the pagecache size in bytes",
- },
- {
- .name = "debug",
- .type = QEMU_OPT_NUMBER,
- .help = "Set the NFS debug level (max 2)",
- },
- { /* end of list */ }
- },
-};
-
static void nfs_detach_aio_context(BlockDriverState *bs)
{
NFSClient *client = bs->opaque;
nfs_client_close(client);
}
-static NFSServer *nfs_config(QDict *options, Error **errp)
-{
- NFSServer *server = NULL;
- QDict *addr = NULL;
- QObject *crumpled_addr = NULL;
- Visitor *iv = NULL;
- Error *local_error = NULL;
-
- qdict_extract_subqdict(options, &addr, "server.");
- if (!qdict_size(addr)) {
- error_setg(errp, "NFS server address missing");
- goto out;
- }
-
- crumpled_addr = qdict_crumple(addr, errp);
- if (!crumpled_addr) {
- goto out;
- }
-
- /*
- * Caution: this works only because all scalar members of
- * NFSServer are QString in @crumpled_addr. The visitor expects
- * @crumpled_addr to be typed according to the QAPI schema. It
- * is when @options come from -blockdev or blockdev_add. But when
- * they come from -drive, they're all QString.
- */
- iv = qobject_input_visitor_new(crumpled_addr);
- visit_type_NFSServer(iv, NULL, &server, &local_error);
- if (local_error) {
- error_propagate(errp, local_error);
- goto out;
- }
-
-out:
- QDECREF(addr);
- qobject_decref(crumpled_addr);
- visit_free(iv);
- return server;
-}
-
-
-static int64_t nfs_client_open(NFSClient *client, QDict *options,
+static int64_t nfs_client_open(NFSClient *client, BlockdevOptionsNfs *opts,
int flags, int open_flags, Error **errp)
{
int64_t ret = -EINVAL;
- QemuOpts *opts = NULL;
- Error *local_err = NULL;
struct stat st;
char *file = NULL, *strp = NULL;
qemu_mutex_init(&client->mutex);
- opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
- qemu_opts_absorb_qdict(opts, options, &local_err);
- if (local_err) {
- error_propagate(errp, local_err);
- ret = -EINVAL;
- goto fail;
- }
- client->path = g_strdup(qemu_opt_get(opts, "path"));
- if (!client->path) {
- ret = -EINVAL;
- error_setg(errp, "No path was specified");
- goto fail;
- }
+ client->path = g_strdup(opts->path);
strp = strrchr(client->path, '/');
if (strp == NULL) {
file = g_strdup(strp);
*strp = 0;
- /* Pop the config into our state object, Exit if invalid */
- client->server = nfs_config(options, errp);
- if (!client->server) {
- ret = -EINVAL;
- goto fail;
- }
+ /* Steal the NFSServer object from opts; set the original pointer to NULL
+ * to avoid use after free and double free. */
+ client->server = opts->server;
+ opts->server = NULL;
client->context = nfs_init_context();
if (client->context == NULL) {
goto fail;
}
- if (qemu_opt_get(opts, "user")) {
- client->uid = qemu_opt_get_number(opts, "user", 0);
+ if (opts->has_user) {
+ client->uid = opts->user;
nfs_set_uid(client->context, client->uid);
}
- if (qemu_opt_get(opts, "group")) {
- client->gid = qemu_opt_get_number(opts, "group", 0);
+ if (opts->has_group) {
+ client->gid = opts->group;
nfs_set_gid(client->context, client->gid);
}
- if (qemu_opt_get(opts, "tcp-syn-count")) {
- client->tcp_syncnt = qemu_opt_get_number(opts, "tcp-syn-count", 0);
+ if (opts->has_tcp_syn_count) {
+ client->tcp_syncnt = opts->tcp_syn_count;
nfs_set_tcp_syncnt(client->context, client->tcp_syncnt);
}
#ifdef LIBNFS_FEATURE_READAHEAD
- if (qemu_opt_get(opts, "readahead-size")) {
+ if (opts->has_readahead_size) {
if (open_flags & BDRV_O_NOCACHE) {
error_setg(errp, "Cannot enable NFS readahead "
"if cache.direct = on");
goto fail;
}
- client->readahead = qemu_opt_get_number(opts, "readahead-size", 0);
+ client->readahead = opts->readahead_size;
if (client->readahead > QEMU_NFS_MAX_READAHEAD_SIZE) {
warn_report("Truncating NFS readahead size to %d",
QEMU_NFS_MAX_READAHEAD_SIZE);
#endif
#ifdef LIBNFS_FEATURE_PAGECACHE
- if (qemu_opt_get(opts, "page-cache-size")) {
+ if (opts->has_page_cache_size) {
if (open_flags & BDRV_O_NOCACHE) {
error_setg(errp, "Cannot enable NFS pagecache "
"if cache.direct = on");
goto fail;
}
- client->pagecache = qemu_opt_get_number(opts, "page-cache-size", 0);
+ client->pagecache = opts->page_cache_size;
if (client->pagecache > QEMU_NFS_MAX_PAGECACHE_SIZE) {
warn_report("Truncating NFS pagecache size to %d pages",
QEMU_NFS_MAX_PAGECACHE_SIZE);
#endif
#ifdef LIBNFS_FEATURE_DEBUG
- if (qemu_opt_get(opts, "debug")) {
- client->debug = qemu_opt_get_number(opts, "debug", 0);
+ if (opts->has_debug) {
+ client->debug = opts->debug;
/* limit the maximum debug level to avoid potential flooding
* of our log files. */
if (client->debug > QEMU_NFS_MAX_DEBUG_LEVEL) {
fail:
nfs_client_close(client);
out:
- qemu_opts_del(opts);
g_free(file);
return ret;
}
+static BlockdevOptionsNfs *nfs_options_qdict_to_qapi(QDict *options,
+ Error **errp)
+{
+ BlockdevOptionsNfs *opts = NULL;
+ QObject *crumpled = NULL;
+ Visitor *v;
+ const QDictEntry *e;
+ Error *local_err = NULL;
+
+ crumpled = qdict_crumple_for_keyval_qiv(options, errp);
+ if (crumpled == NULL) {
+ return NULL;
+ }
+
+ v = qobject_input_visitor_new_keyval(crumpled);
+ visit_type_BlockdevOptionsNfs(v, NULL, &opts, &local_err);
+ visit_free(v);
+ qobject_unref(crumpled);
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ /* Remove the processed options from the QDict (the visitor processes
+ * _all_ options in the QDict) */
+ while ((e = qdict_first(options))) {
+ qdict_del(options, e->key);
+ }
+
+ return opts;
+}
+
+static int64_t nfs_client_open_qdict(NFSClient *client, QDict *options,
+ int flags, int open_flags, Error **errp)
+{
+ BlockdevOptionsNfs *opts;
+ int ret;
+
+ opts = nfs_options_qdict_to_qapi(options, errp);
+ if (opts == NULL) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = nfs_client_open(client, opts, flags, open_flags, errp);
+fail:
+ qapi_free_BlockdevOptionsNfs(opts);
+ return ret;
+}
+
static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
Error **errp) {
NFSClient *client = bs->opaque;
client->aio_context = bdrv_get_aio_context(bs);
- ret = nfs_client_open(client, options,
- (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
- bs->open_flags, errp);
+ ret = nfs_client_open_qdict(client, options,
+ (flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
+ bs->open_flags, errp);
if (ret < 0) {
return ret;
}
}
};
-static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
+static int nfs_file_co_create(BlockdevCreateOptions *options, Error **errp)
{
- int64_t ret, total_size;
+ BlockdevCreateOptionsNfs *opts = &options->u.nfs;
NFSClient *client = g_new0(NFSClient, 1);
- QDict *options = NULL;
+ int ret;
+
+ assert(options->driver == BLOCKDEV_DRIVER_NFS);
client->aio_context = qemu_get_aio_context();
+ ret = nfs_client_open(client, opts->location, O_CREAT, 0, errp);
+ if (ret < 0) {
+ goto out;
+ }
+ ret = nfs_ftruncate(client->context, client->fh, opts->size);
+ nfs_client_close(client);
+
+out:
+ g_free(client);
+ return ret;
+}
+
+static int coroutine_fn nfs_file_co_create_opts(const char *url, QemuOpts *opts,
+ Error **errp)
+{
+ BlockdevCreateOptions *create_options;
+ BlockdevCreateOptionsNfs *nfs_opts;
+ QDict *options;
+ int ret;
+
+ create_options = g_new0(BlockdevCreateOptions, 1);
+ create_options->driver = BLOCKDEV_DRIVER_NFS;
+ nfs_opts = &create_options->u.nfs;
+
/* Read out options */
- total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
- BDRV_SECTOR_SIZE);
+ nfs_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
+ BDRV_SECTOR_SIZE);
options = qdict_new();
ret = nfs_parse_uri(url, options, errp);
goto out;
}
- ret = nfs_client_open(client, options, O_CREAT, 0, errp);
+ nfs_opts->location = nfs_options_qdict_to_qapi(options, errp);
+ if (nfs_opts->location == NULL) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = nfs_file_co_create(create_options, errp);
if (ret < 0) {
goto out;
}
- ret = nfs_ftruncate(client->context, client->fh, total_size);
- nfs_client_close(client);
+
+ ret = 0;
out:
- QDECREF(options);
- g_free(client);
+ qobject_unref(options);
+ qapi_free_BlockdevCreateOptions(create_options);
return ret;
}
}
#ifdef LIBNFS_FEATURE_PAGECACHE
-static void nfs_invalidate_cache(BlockDriverState *bs,
- Error **errp)
+static void coroutine_fn nfs_co_invalidate_cache(BlockDriverState *bs,
+ Error **errp)
{
NFSClient *client = bs->opaque;
nfs_pagecache_invalidate(client->context, client->fh);
.bdrv_file_open = nfs_file_open,
.bdrv_close = nfs_file_close,
- .bdrv_create = nfs_file_create,
+ .bdrv_co_create = nfs_file_co_create,
+ .bdrv_co_create_opts = nfs_file_co_create_opts,
.bdrv_reopen_prepare = nfs_reopen_prepare,
.bdrv_co_preadv = nfs_co_preadv,
.bdrv_refresh_filename = nfs_refresh_filename,
#ifdef LIBNFS_FEATURE_PAGECACHE
- .bdrv_invalidate_cache = nfs_invalidate_cache,
+ .bdrv_co_invalidate_cache = nfs_co_invalidate_cache,
#endif
};