#include "qemu/error-report.h"
#include "qemu/option.h"
#include "block/block_int.h"
+#include "block/qdict.h"
#include "crypto/secret.h"
#include "qemu/cutils.h"
#include "qapi/qmp/qstring.h"
char *snap;
} BDRVRBDState;
+static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
+ BlockdevOptionsRbd *opts, bool cache,
+ const char *keypairs, const char *secretid,
+ Error **errp);
+
static char *qemu_rbd_next_tok(char *src, char delim, char **p)
{
char *end;
done:
g_free(buf);
- QDECREF(keypairs);
+ qobject_unref(keypairs);
return;
}
-static int qemu_rbd_set_auth(rados_t cluster, const char *secretid,
+static void qemu_rbd_refresh_limits(BlockDriverState *bs, Error **errp)
+{
+ /* XXX Does RBD support AIO on less than 512-byte alignment? */
+ bs->bl.request_alignment = 512;
+}
+
+
+static int qemu_rbd_set_auth(rados_t cluster, BlockdevOptionsRbd *opts,
Error **errp)
{
- if (secretid == 0) {
- return 0;
- }
+ char *key, *acr;
+ int r;
+ GString *accu;
+ RbdAuthModeList *auth;
- gchar *secret = qcrypto_secret_lookup_as_base64(secretid,
- errp);
- if (!secret) {
- return -1;
+ if (opts->key_secret) {
+ key = qcrypto_secret_lookup_as_base64(opts->key_secret, errp);
+ if (!key) {
+ return -EIO;
+ }
+ r = rados_conf_set(cluster, "key", key);
+ g_free(key);
+ if (r < 0) {
+ error_setg_errno(errp, -r, "Could not set 'key'");
+ return r;
+ }
}
- rados_conf_set(cluster, "key", secret);
- g_free(secret);
+ if (opts->has_auth_client_required) {
+ accu = g_string_new("");
+ for (auth = opts->auth_client_required; auth; auth = auth->next) {
+ if (accu->str[0]) {
+ g_string_append_c(accu, ';');
+ }
+ g_string_append(accu, RbdAuthMode_str(auth->value));
+ }
+ acr = g_string_free(accu, FALSE);
+ r = rados_conf_set(cluster, "auth_client_required", acr);
+ g_free(acr);
+ if (r < 0) {
+ error_setg_errno(errp, -r,
+ "Could not set 'auth_client_required'");
+ return r;
+ }
+ }
return 0;
}
if (!keypairs_json) {
return ret;
}
- keypairs = qobject_to_qlist(qobject_from_json(keypairs_json,
- &error_abort));
+ keypairs = qobject_to(QList,
+ qobject_from_json(keypairs_json, &error_abort));
remaining = qlist_size(keypairs) / 2;
assert(remaining);
while (remaining--) {
- name = qobject_to_qstring(qlist_pop(keypairs));
- value = qobject_to_qstring(qlist_pop(keypairs));
+ name = qobject_to(QString, qlist_pop(keypairs));
+ value = qobject_to(QString, qlist_pop(keypairs));
assert(name && value);
key = qstring_get_str(name);
ret = rados_conf_set(cluster, key, qstring_get_str(value));
- QDECREF(value);
+ qobject_unref(value);
if (ret < 0) {
error_setg_errno(errp, -ret, "invalid conf option %s", key);
- QDECREF(name);
+ qobject_unref(name);
ret = -EINVAL;
break;
}
- QDECREF(name);
+ qobject_unref(name);
}
- QDECREF(keypairs);
+ qobject_unref(keypairs);
return ret;
}
},
};
-static int coroutine_fn qemu_rbd_co_create_opts(const char *filename,
- QemuOpts *opts,
- Error **errp)
+/* FIXME Deprecate and remove keypairs or make it available in QMP. */
+static int qemu_rbd_do_create(BlockdevCreateOptions *options,
+ const char *keypairs, const char *password_secret,
+ Error **errp)
{
- Error *local_err = NULL;
- int64_t bytes = 0;
- int64_t objsize;
- int obj_order = 0;
- const char *pool, *image_name, *conf, *user, *keypairs;
- const char *secretid;
+ BlockdevCreateOptionsRbd *opts = &options->u.rbd;
rados_t cluster;
rados_ioctx_t io_ctx;
- QDict *options = NULL;
- int ret = 0;
+ int obj_order = 0;
+ int ret;
- secretid = qemu_opt_get(opts, "password-secret");
+ assert(options->driver == BLOCKDEV_DRIVER_RBD);
+ if (opts->location->has_snapshot) {
+ error_setg(errp, "Can't use snapshot name for image creation");
+ return -EINVAL;
+ }
- /* Read out options */
- bytes = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
- BDRV_SECTOR_SIZE);
- objsize = qemu_opt_get_size_del(opts, BLOCK_OPT_CLUSTER_SIZE, 0);
- if (objsize) {
+ if (opts->has_cluster_size) {
+ int64_t objsize = opts->cluster_size;
if ((objsize - 1) & objsize) { /* not a power of 2? */
error_setg(errp, "obj size needs to be power of 2");
- ret = -EINVAL;
- goto exit;
+ return -EINVAL;
}
if (objsize < 4096) {
error_setg(errp, "obj size too small");
- ret = -EINVAL;
- goto exit;
+ return -EINVAL;
}
obj_order = ctz32(objsize);
}
+ ret = qemu_rbd_connect(&cluster, &io_ctx, opts->location, false, keypairs,
+ password_secret, errp);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = rbd_create(io_ctx, opts->location->image, opts->size, &obj_order);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "error rbd create");
+ goto out;
+ }
+
+ ret = 0;
+out:
+ rados_ioctx_destroy(io_ctx);
+ rados_shutdown(cluster);
+ return ret;
+}
+
+static int qemu_rbd_co_create(BlockdevCreateOptions *options, Error **errp)
+{
+ return qemu_rbd_do_create(options, NULL, NULL, errp);
+}
+
+static int coroutine_fn qemu_rbd_co_create_opts(const char *filename,
+ QemuOpts *opts,
+ Error **errp)
+{
+ BlockdevCreateOptions *create_options;
+ BlockdevCreateOptionsRbd *rbd_opts;
+ BlockdevOptionsRbd *loc;
+ Error *local_err = NULL;
+ const char *keypairs, *password_secret;
+ QDict *options = NULL;
+ int ret = 0;
+
+ create_options = g_new0(BlockdevCreateOptions, 1);
+ create_options->driver = BLOCKDEV_DRIVER_RBD;
+ rbd_opts = &create_options->u.rbd;
+
+ rbd_opts->location = g_new0(BlockdevOptionsRbd, 1);
+
+ password_secret = qemu_opt_get(opts, "password-secret");
+
+ /* Read out options */
+ rbd_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
+ BDRV_SECTOR_SIZE);
+ rbd_opts->cluster_size = qemu_opt_get_size_del(opts,
+ BLOCK_OPT_CLUSTER_SIZE, 0);
+ rbd_opts->has_cluster_size = (rbd_opts->cluster_size != 0);
+
options = qdict_new();
qemu_rbd_parse_filename(filename, options, &local_err);
if (local_err) {
* or blockdev_add, its members are typed according to the QAPI
* schema, but when they come from -drive, they're all QString.
*/
- pool = qdict_get_try_str(options, "pool");
- conf = qdict_get_try_str(options, "conf");
- user = qdict_get_try_str(options, "user");
- image_name = qdict_get_try_str(options, "image");
- keypairs = qdict_get_try_str(options, "=keyvalue-pairs");
-
- ret = rados_create(&cluster, user);
+ loc = rbd_opts->location;
+ loc->pool = g_strdup(qdict_get_try_str(options, "pool"));
+ loc->conf = g_strdup(qdict_get_try_str(options, "conf"));
+ loc->has_conf = !!loc->conf;
+ loc->user = g_strdup(qdict_get_try_str(options, "user"));
+ loc->has_user = !!loc->user;
+ loc->image = g_strdup(qdict_get_try_str(options, "image"));
+ keypairs = qdict_get_try_str(options, "=keyvalue-pairs");
+
+ ret = qemu_rbd_do_create(create_options, keypairs, password_secret, errp);
if (ret < 0) {
- error_setg_errno(errp, -ret, "error initializing");
goto exit;
}
- /* try default location when conf=NULL, but ignore failure */
- ret = rados_conf_read_file(cluster, conf);
- if (conf && ret < 0) {
- error_setg_errno(errp, -ret, "error reading conf file %s", conf);
- ret = -EIO;
- goto shutdown;
- }
-
- ret = qemu_rbd_set_keypairs(cluster, keypairs, errp);
- if (ret < 0) {
- ret = -EIO;
- goto shutdown;
- }
-
- if (qemu_rbd_set_auth(cluster, secretid, errp) < 0) {
- ret = -EIO;
- goto shutdown;
- }
-
- ret = rados_connect(cluster);
- if (ret < 0) {
- error_setg_errno(errp, -ret, "error connecting");
- goto shutdown;
- }
-
- ret = rados_ioctx_create(cluster, pool, &io_ctx);
- if (ret < 0) {
- error_setg_errno(errp, -ret, "error opening pool %s", pool);
- goto shutdown;
- }
-
- ret = rbd_create(io_ctx, image_name, bytes, &obj_order);
- if (ret < 0) {
- error_setg_errno(errp, -ret, "error rbd create");
- }
-
- rados_ioctx_destroy(io_ctx);
-
-shutdown:
- rados_shutdown(cluster);
-
exit:
- QDECREF(options);
+ qobject_unref(options);
+ qapi_free_BlockdevCreateOptions(create_options);
return ret;
}
}
static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
- char **s_snap, char **s_image_name,
BlockdevOptionsRbd *opts, bool cache,
const char *keypairs, const char *secretid,
Error **errp)
Error *local_err = NULL;
int r;
+ if (secretid) {
+ if (opts->key_secret) {
+ error_setg(errp,
+ "Legacy 'password-secret' clashes with 'key-secret'");
+ return -EINVAL;
+ }
+ opts->key_secret = g_strdup(secretid);
+ opts->has_key_secret = true;
+ }
+
mon_host = qemu_rbd_mon_host(opts, &local_err);
if (local_err) {
error_propagate(errp, local_err);
goto failed_opts;
}
- *s_snap = g_strdup(opts->snapshot);
- *s_image_name = g_strdup(opts->image);
-
/* try default location when conf=NULL, but ignore failure */
r = rados_conf_read_file(*cluster, opts->conf);
if (opts->has_conf && r < 0) {
}
}
- if (qemu_rbd_set_auth(*cluster, secretid, errp) < 0) {
- r = -EIO;
+ r = qemu_rbd_set_auth(*cluster, opts, errp);
+ if (r < 0) {
goto failed_shutdown;
}
failed_shutdown:
rados_shutdown(*cluster);
- g_free(*s_snap);
- g_free(*s_image_name);
failed_opts:
g_free(mon_host);
return r;
}
+static int qemu_rbd_convert_options(QDict *options, BlockdevOptionsRbd **opts,
+ Error **errp)
+{
+ Visitor *v;
+ Error *local_err = NULL;
+
+ /* Convert the remaining options into a QAPI object */
+ v = qobject_input_visitor_new_flat_confused(options, errp);
+ if (!v) {
+ return -EINVAL;
+ }
+
+ visit_type_BlockdevOptionsRbd(v, NULL, opts, &local_err);
+ visit_free(v);
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qemu_rbd_attempt_legacy_options(QDict *options,
+ BlockdevOptionsRbd **opts,
+ char **keypairs)
+{
+ char *filename;
+ int r;
+
+ filename = g_strdup(qdict_get_try_str(options, "filename"));
+ if (!filename) {
+ return -EINVAL;
+ }
+ qdict_del(options, "filename");
+
+ qemu_rbd_parse_filename(filename, options, NULL);
+
+ /* keypairs freed by caller */
+ *keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs"));
+ if (*keypairs) {
+ qdict_del(options, "=keyvalue-pairs");
+ }
+
+ r = qemu_rbd_convert_options(options, opts, NULL);
+
+ g_free(filename);
+ return r;
+}
+
static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
Error **errp)
{
BDRVRBDState *s = bs->opaque;
BlockdevOptionsRbd *opts = NULL;
- Visitor *v;
- QObject *crumpled = NULL;
+ const QDictEntry *e;
Error *local_err = NULL;
- const char *filename;
char *keypairs, *secretid;
int r;
- /* If we are given a filename, parse the filename, with precedence given to
- * filename encoded options */
- filename = qdict_get_try_str(options, "filename");
- if (filename) {
- warn_report("'filename' option specified. "
- "This is an unsupported option, and may be deprecated "
- "in the future");
- qemu_rbd_parse_filename(filename, options, &local_err);
- qdict_del(options, "filename");
- if (local_err) {
- error_propagate(errp, local_err);
- return -EINVAL;
- }
- }
-
keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs"));
if (keypairs) {
qdict_del(options, "=keyvalue-pairs");
qdict_del(options, "password-secret");
}
- /* Convert the remaining options into a QAPI object */
- crumpled = qdict_crumple(options, errp);
- if (crumpled == NULL) {
- r = -EINVAL;
- goto out;
- }
+ r = qemu_rbd_convert_options(options, &opts, &local_err);
+ if (local_err) {
+ /* If keypairs are present, that means some options are present in
+ * the modern option format. Don't attempt to parse legacy option
+ * formats, as we won't support mixed usage. */
+ if (keypairs) {
+ error_propagate(errp, local_err);
+ goto out;
+ }
- v = qobject_input_visitor_new_keyval(crumpled);
- visit_type_BlockdevOptionsRbd(v, NULL, &opts, &local_err);
- visit_free(v);
- qobject_decref(crumpled);
+ /* If the initial attempt to convert and process the options failed,
+ * we may be attempting to open an image file that has the rbd options
+ * specified in the older format consisting of all key/value pairs
+ * encoded in the filename. Go ahead and attempt to parse the
+ * filename, and see if we can pull out the required options. */
+ r = qemu_rbd_attempt_legacy_options(options, &opts, &keypairs);
+ if (r < 0) {
+ /* Propagate the original error, not the legacy parsing fallback
+ * error, as the latter was just a best-effort attempt. */
+ error_propagate(errp, local_err);
+ goto out;
+ }
+ /* Take care whenever deciding to actually deprecate; once this ability
+ * is removed, we will not be able to open any images with legacy-styled
+ * backing image strings. */
+ warn_report("RBD options encoded in the filename as keyvalue pairs "
+ "is deprecated");
+ }
- if (local_err) {
- error_propagate(errp, local_err);
- r = -EINVAL;
- goto out;
+ /* 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);
}
- r = qemu_rbd_connect(&s->cluster, &s->io_ctx, &s->snap, &s->image_name,
- opts, !(flags & BDRV_O_NOCACHE), keypairs, secretid,
- errp);
+ r = qemu_rbd_connect(&s->cluster, &s->io_ctx, opts,
+ !(flags & BDRV_O_NOCACHE), keypairs, secretid, errp);
if (r < 0) {
goto out;
}
+ s->snap = g_strdup(opts->snapshot);
+ s->image_name = g_strdup(opts->image);
+
/* rbd_open is always r/w */
r = rbd_open(s->io_ctx, s->image_name, &s->image, s->snap);
if (r < 0) {
/* If we are using an rbd snapshot, we must be r/o, otherwise
* leave as-is */
if (s->snap != NULL) {
- if (!bdrv_is_read_only(bs)) {
- error_report("Opening rbd snapshots without an explicit "
- "read-only=on option is deprecated. Future versions "
- "will refuse to open the image instead of "
- "automatically marking the image read-only.");
- r = bdrv_set_read_only(bs, true, &local_err);
- if (r < 0) {
- error_propagate(errp, local_err);
- goto failed_open;
- }
+ r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp);
+ if (r < 0) {
+ rbd_close(s->image);
+ goto failed_open;
}
}
return NULL;
}
-static BlockAIOCB *qemu_rbd_aio_readv(BlockDriverState *bs,
- int64_t sector_num,
- QEMUIOVector *qiov,
- int nb_sectors,
- BlockCompletionFunc *cb,
- void *opaque)
+static BlockAIOCB *qemu_rbd_aio_preadv(BlockDriverState *bs,
+ uint64_t offset, uint64_t bytes,
+ QEMUIOVector *qiov, int flags,
+ BlockCompletionFunc *cb,
+ void *opaque)
{
- return rbd_start_aio(bs, sector_num << BDRV_SECTOR_BITS, qiov,
- (int64_t) nb_sectors << BDRV_SECTOR_BITS, cb, opaque,
+ return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque,
RBD_AIO_READ);
}
-static BlockAIOCB *qemu_rbd_aio_writev(BlockDriverState *bs,
- int64_t sector_num,
- QEMUIOVector *qiov,
- int nb_sectors,
- BlockCompletionFunc *cb,
- void *opaque)
+static BlockAIOCB *qemu_rbd_aio_pwritev(BlockDriverState *bs,
+ uint64_t offset, uint64_t bytes,
+ QEMUIOVector *qiov, int flags,
+ BlockCompletionFunc *cb,
+ void *opaque)
{
- return rbd_start_aio(bs, sector_num << BDRV_SECTOR_BITS, qiov,
- (int64_t) nb_sectors << BDRV_SECTOR_BITS, cb, opaque,
+ return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque,
RBD_AIO_WRITE);
}
return info.size;
}
-static int qemu_rbd_truncate(BlockDriverState *bs, int64_t offset,
- PreallocMode prealloc, Error **errp)
+static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs,
+ int64_t offset,
+ PreallocMode prealloc,
+ Error **errp)
{
BDRVRBDState *s = bs->opaque;
int r;
}
};
+static const char *const qemu_rbd_strong_runtime_opts[] = {
+ "pool",
+ "image",
+ "conf",
+ "snapshot",
+ "user",
+ "server.",
+ "password-secret",
+
+ NULL
+};
+
static BlockDriver bdrv_rbd = {
.format_name = "rbd",
.instance_size = sizeof(BDRVRBDState),
.bdrv_parse_filename = qemu_rbd_parse_filename,
+ .bdrv_refresh_limits = qemu_rbd_refresh_limits,
.bdrv_file_open = qemu_rbd_open,
.bdrv_close = qemu_rbd_close,
.bdrv_reopen_prepare = qemu_rbd_reopen_prepare,
+ .bdrv_co_create = qemu_rbd_co_create,
.bdrv_co_create_opts = qemu_rbd_co_create_opts,
.bdrv_has_zero_init = bdrv_has_zero_init_1,
.bdrv_get_info = qemu_rbd_getinfo,
.create_opts = &qemu_rbd_create_opts,
.bdrv_getlength = qemu_rbd_getlength,
- .bdrv_truncate = qemu_rbd_truncate,
+ .bdrv_co_truncate = qemu_rbd_co_truncate,
.protocol_name = "rbd",
- .bdrv_aio_readv = qemu_rbd_aio_readv,
- .bdrv_aio_writev = qemu_rbd_aio_writev,
+ .bdrv_aio_preadv = qemu_rbd_aio_preadv,
+ .bdrv_aio_pwritev = qemu_rbd_aio_pwritev,
#ifdef LIBRBD_SUPPORTS_AIO_FLUSH
.bdrv_aio_flush = qemu_rbd_aio_flush,
#ifdef LIBRBD_SUPPORTS_INVALIDATE
.bdrv_co_invalidate_cache = qemu_rbd_co_invalidate_cache,
#endif
+
+ .strong_runtime_opts = qemu_rbd_strong_runtime_opts,
};
static void bdrv_rbd_init(void)