@@ -17,8 +17,12 @@
#include <gnutls/crypto.h>
#include "block/block_int.h"
#include "qapi/qmp/qjson.h"
+#include "qapi/qmp/types.h"
+#include "qemu-common.h"
#define HASH_LENGTH 32
+#define KEY_PREFIX "children."
+#define KEY_FILENAME_SUFFIX ".file.filename"
/* This union holds a vote hash value */
typedef union QuorumVoteValue {
@@ -702,12 +706,177 @@ static bool quorum_recurse_is_first_non_filter(BlockDriverState *bs,
return false;
}
+static int quorum_valid_threshold(int threshold,
+ int total,
+ Error **errp)
+{
+
+ if (threshold < 1) {
+ error_set(errp, QERR_INVALID_PARAMETER_VALUE,
+ "vote-threshold", "value >= 1");
+ return -ERANGE;
+ }
+
+ if (threshold > total) {
+ error_setg(errp, "threshold may not exceed children count");
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static int quorum_open(BlockDriverState *bs,
+ QDict *options,
+ int flags,
+ Error **errp)
+{
+ BDRVQuorumState *s = bs->opaque;
+ Error *local_err = NULL;
+ bool *opened;
+ QDict *sub = NULL;
+ QList *list = NULL;
+ const QListEntry *lentry;
+ const QDictEntry *dentry;
+ const char *value;
+ char *next;
+ int i;
+ int ret = 0;
+ unsigned long long threshold = 0;
+
+ qdict_extract_subqdict(options, &sub, "children.");
+ qdict_array_split(sub, &list);
+
+ /* count how many different children are present and validate */
+ s->total = !qlist_size(list) ? qdict_size(sub) : qlist_size(list);
+ if (s->total < 2) {
+ error_setg(&local_err,
+ "Number of provided children must be greater than 1");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ ret = qdict_get_try_int(options, "vote-threshold", -1);
+ /* from QMP */
+ if (ret != -1) {
+ qdict_del(options, "vote-threshold");
+ s->threshold = ret;
+ /* from command line */
+ } else {
+ /* retrieve the threshold option from the command line */
+ value = qdict_get_try_str(options, "vote_threshold");
+ if (!value) {
+ error_setg(&local_err,
+ "vote_threshold must be provided");
+ ret = -EINVAL;
+ goto exit;
+ }
+ qdict_del(options, "vote_threshold");
+
+ ret = parse_uint(value, &threshold, &next, 10);
+
+ /* no int found -> scan fail */
+ if (ret < 0) {
+ error_setg(&local_err,
+ "invalid vote_threshold specified");
+ ret = -EINVAL;
+ goto exit;
+ }
+ s->threshold = threshold;
+ }
+
+ /* and validate it againt s->total */
+ ret = quorum_valid_threshold(s->threshold, s->total, &local_err);
+ if (ret < 0) {
+ goto exit;
+ }
+
+ /* is the driver in blkverify mode */
+ value = qdict_get_try_str(options, "blkverify");
+ if (value && !strcmp(value, "on") &&
+ s->total == 2 && s->threshold == 2) {
+ s->is_blkverify = true;
+ } else if (value && strcmp(value, "off")) {
+ fprintf(stderr, "blkverify mode is set by setting blkverify=on "
+ "and using two files with vote_threshold=2");
+ }
+ qdict_del(options, "blkverify");
+
+ /* allocate the children BlockDriverState array */
+ s->bs = g_new0(BlockDriverState *, s->total);
+ opened = g_new0(bool, s->total);
+
+ /* Opening by file name or options */
+ for (i = 0, lentry = qlist_first(list);
+ s->total == qlist_size(list) && lentry;
+ lentry = qlist_next(lentry), i++) {
+ ret = bdrv_open(&s->bs[i], NULL, NULL, qobject_to_qdict(lentry->value),
+ flags, NULL, &local_err);
+ if (ret < 0) {
+ goto close_exit;
+ }
+ opened[i] = true;
+ }
+
+ /* Opening by reference */
+ for (i = 0, dentry = qdict_first(sub);
+ s->total == qdict_size(sub) && dentry;
+ dentry = qdict_next(sub, dentry), i++) {
+ ret = bdrv_open(&s->bs[i], NULL,
+ qstring_get_str(qobject_to_qstring(dentry->value)),
+ NULL, flags, NULL, &local_err);
+ if (ret < 0) {
+ goto close_exit;
+ }
+ opened[i] = true;
+ }
+
+ g_free(opened);
+ goto exit;
+
+close_exit:
+ /* cleanup on error */
+ for (i = 0; i < s->total; i++) {
+ if (!opened[i]) {
+ continue;
+ }
+ bdrv_unref(s->bs[i]);
+ }
+ g_free(s->bs);
+ g_free(opened);
+exit:
+ /* propagate error */
+ if (error_is_set(&local_err)) {
+ error_propagate(errp, local_err);
+ }
+ QDECREF(sub);
+ QDECREF(list);
+ return ret;
+}
+
+static void quorum_close(BlockDriverState *bs)
+{
+ BDRVQuorumState *s = bs->opaque;
+ int i;
+
+ for (i = 0; i < s->total; i++) {
+ /* Ensure writes reach stable storage */
+ bdrv_flush(s->bs[i]);
+ /* close manually because we'll free s->bs */
+ bdrv_unref(s->bs[i]);
+ }
+
+ g_free(s->bs);
+}
+
static BlockDriver bdrv_quorum = {
.format_name = "quorum",
.protocol_name = "quorum",
.instance_size = sizeof(BDRVQuorumState),
+ .bdrv_file_open = quorum_open,
+ .bdrv_close = quorum_close,
+
.bdrv_co_flush_to_disk = quorum_co_flush,
.bdrv_getlength = quorum_getlength,
@@ -4352,6 +4352,24 @@
'raw': 'BlockdevRef' } }
##
+# @BlockdevOptionsQuorum
+#
+# Driver specific block device options for Quorum
+#
+# @blkverify: #optional true if the driver must print content mismatch
+#
+# @children: the children block device to use
+#
+# @vote_threshold: the vote limit under which a read will fail
+#
+# Since: 2.0
+##
+{ 'type': 'BlockdevOptionsQuorum',
+ 'data': { '*blkverify': 'bool',
+ 'children': [ 'BlockdevRef' ],
+ 'vote-threshold': 'int' } }
+
+##
# @BlockdevOptions
#
# Options for creating a block device.
@@ -4389,7 +4407,8 @@
'vdi': 'BlockdevOptionsGenericFormat',
'vhdx': 'BlockdevOptionsGenericFormat',
'vmdk': 'BlockdevOptionsGenericCOWFormat',
- 'vpc': 'BlockdevOptionsGenericFormat'
+ 'vpc': 'BlockdevOptionsGenericFormat',
+ 'quorum': 'BlockdevOptionsQuorum'
} }
##