diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2017-02-21 15:48:22 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2017-02-21 15:48:22 +0000 |
commit | 796b288f7be875045670f963ce99991b3c8e96ac (patch) | |
tree | ae3087ed13791940d5cecdd7fe7809440cf088f7 | |
parent | a1cf5fac2b929ffa2abd1285401f2535ff8c6fea (diff) | |
parent | 6135c5e12606b8413708384e3e7d43f6010c5941 (diff) |
Merge remote-tracking branch 'remotes/cody/tags/block-pull-request' into staging
# gpg: Signature made Tue 21 Feb 2017 15:40:05 GMT
# 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
* remotes/cody/tags/block-pull-request:
qemu-options: Fix broken sheepdog URL
mirror: do not increase offset during initial zero_or_discard phase
QAPI: Fix blockdev-add example documentation
iscsi: Add blockdev-add support
iscsi: Add timeout option
iscsi: Add header-digest option
iscsi: Add initiator-name option
iscsi: Handle -iscsi user/password in bdrv_parse_filename()
iscsi: Split URL into individual options
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r-- | block/iscsi.c | 353 | ||||
-rw-r--r-- | block/mirror.c | 9 | ||||
-rw-r--r-- | qapi/block-core.json | 125 | ||||
-rw-r--r-- | qemu-options.hx | 2 |
4 files changed, 317 insertions, 172 deletions
diff --git a/block/iscsi.c b/block/iscsi.c index 2561be90de..c4f813bfd2 100644 --- a/block/iscsi.c +++ b/block/iscsi.c @@ -1245,29 +1245,14 @@ retry: return 0; } -static void parse_chap(struct iscsi_context *iscsi, const char *target, +static void apply_chap(struct iscsi_context *iscsi, QemuOpts *opts, Error **errp) { - QemuOptsList *list; - QemuOpts *opts; const char *user = NULL; const char *password = NULL; const char *secretid; char *secret = NULL; - list = qemu_find_opts("iscsi"); - if (!list) { - return; - } - - opts = qemu_opts_find(list, target); - if (opts == NULL) { - opts = QTAILQ_FIRST(&list->head); - if (!opts) { - return; - } - } - user = qemu_opt_get(opts, "user"); if (!user) { return; @@ -1298,64 +1283,36 @@ static void parse_chap(struct iscsi_context *iscsi, const char *target, g_free(secret); } -static void parse_header_digest(struct iscsi_context *iscsi, const char *target, +static void apply_header_digest(struct iscsi_context *iscsi, QemuOpts *opts, Error **errp) { - QemuOptsList *list; - QemuOpts *opts; const char *digest = NULL; - list = qemu_find_opts("iscsi"); - if (!list) { - return; - } - - opts = qemu_opts_find(list, target); - if (opts == NULL) { - opts = QTAILQ_FIRST(&list->head); - if (!opts) { - return; - } - } - digest = qemu_opt_get(opts, "header-digest"); if (!digest) { - return; - } - - if (!strcmp(digest, "CRC32C")) { + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); + } else if (!strcmp(digest, "crc32c")) { iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_CRC32C); - } else if (!strcmp(digest, "NONE")) { + } else if (!strcmp(digest, "none")) { iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE); - } else if (!strcmp(digest, "CRC32C-NONE")) { + } else if (!strcmp(digest, "crc32c-none")) { iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_CRC32C_NONE); - } else if (!strcmp(digest, "NONE-CRC32C")) { + } else if (!strcmp(digest, "none-crc32c")) { iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); } else { error_setg(errp, "Invalid header-digest setting : %s", digest); } } -static char *parse_initiator_name(const char *target) +static char *get_initiator_name(QemuOpts *opts) { - QemuOptsList *list; - QemuOpts *opts; const char *name; char *iscsi_name; UuidInfo *uuid_info; - list = qemu_find_opts("iscsi"); - if (list) { - opts = qemu_opts_find(list, target); - if (!opts) { - opts = QTAILQ_FIRST(&list->head); - } - if (opts) { - name = qemu_opt_get(opts, "initiator-name"); - if (name) { - return g_strdup(name); - } - } + name = qemu_opt_get(opts, "initiator-name"); + if (name) { + return g_strdup(name); } uuid_info = qmp_query_uuid(NULL); @@ -1370,29 +1327,6 @@ static char *parse_initiator_name(const char *target) return iscsi_name; } -static int parse_timeout(const char *target) -{ - QemuOptsList *list; - QemuOpts *opts; - const char *timeout; - - list = qemu_find_opts("iscsi"); - if (list) { - opts = qemu_opts_find(list, target); - if (!opts) { - opts = QTAILQ_FIRST(&list->head); - } - if (opts) { - timeout = qemu_opt_get(opts, "timeout"); - if (timeout) { - return atoi(timeout); - } - } - } - - return 0; -} - static void iscsi_nop_timed_event(void *opaque) { IscsiLun *iscsilun = opaque; @@ -1483,20 +1417,6 @@ static void iscsi_readcapacity_sync(IscsiLun *iscsilun, Error **errp) } } -/* TODO Convert to fine grained options */ -static QemuOptsList runtime_opts = { - .name = "iscsi", - .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head), - .desc = { - { - .name = "filename", - .type = QEMU_OPT_STRING, - .help = "URL to the iscsi image", - }, - { /* end of list */ } - }, -}; - static struct scsi_task *iscsi_do_inquiry(struct iscsi_context *iscsi, int lun, int evpd, int pc, void **inq, Error **errp) { @@ -1614,24 +1534,178 @@ out: } } +static void iscsi_parse_iscsi_option(const char *target, QDict *options) +{ + QemuOptsList *list; + QemuOpts *opts; + const char *user, *password, *password_secret, *initiator_name, + *header_digest, *timeout; + + list = qemu_find_opts("iscsi"); + if (!list) { + return; + } + + opts = qemu_opts_find(list, target); + if (opts == NULL) { + opts = QTAILQ_FIRST(&list->head); + if (!opts) { + return; + } + } + + user = qemu_opt_get(opts, "user"); + if (user) { + qdict_set_default_str(options, "user", user); + } + + password = qemu_opt_get(opts, "password"); + if (password) { + qdict_set_default_str(options, "password", password); + } + + password_secret = qemu_opt_get(opts, "password-secret"); + if (password_secret) { + qdict_set_default_str(options, "password-secret", password_secret); + } + + initiator_name = qemu_opt_get(opts, "initiator-name"); + if (initiator_name) { + qdict_set_default_str(options, "initiator-name", initiator_name); + } + + header_digest = qemu_opt_get(opts, "header-digest"); + if (header_digest) { + /* -iscsi takes upper case values, but QAPI only supports lower case + * enum constant names, so we have to convert here. */ + char *qapi_value = g_ascii_strdown(header_digest, -1); + qdict_set_default_str(options, "header-digest", qapi_value); + g_free(qapi_value); + } + + timeout = qemu_opt_get(opts, "timeout"); + if (timeout) { + qdict_set_default_str(options, "timeout", timeout); + } +} + /* * We support iscsi url's on the form * iscsi://[<username>%<password>@]<host>[:<port>]/<targetname>/<lun> */ +static void iscsi_parse_filename(const char *filename, QDict *options, + Error **errp) +{ + struct iscsi_url *iscsi_url; + const char *transport_name; + char *lun_str; + + iscsi_url = iscsi_parse_full_url(NULL, filename); + if (iscsi_url == NULL) { + error_setg(errp, "Failed to parse URL : %s", filename); + return; + } + +#if LIBISCSI_API_VERSION >= (20160603) + switch (iscsi_url->transport) { + case TCP_TRANSPORT: + transport_name = "tcp"; + break; + case ISER_TRANSPORT: + transport_name = "iser"; + break; + default: + error_setg(errp, "Unknown transport type (%d)", + iscsi_url->transport); + return; + } +#else + transport_name = "tcp"; +#endif + + qdict_set_default_str(options, "transport", transport_name); + qdict_set_default_str(options, "portal", iscsi_url->portal); + qdict_set_default_str(options, "target", iscsi_url->target); + + lun_str = g_strdup_printf("%d", iscsi_url->lun); + qdict_set_default_str(options, "lun", lun_str); + g_free(lun_str); + + /* User/password from -iscsi take precedence over those from the URL */ + iscsi_parse_iscsi_option(iscsi_url->target, options); + + if (iscsi_url->user[0] != '\0') { + qdict_set_default_str(options, "user", iscsi_url->user); + qdict_set_default_str(options, "password", iscsi_url->passwd); + } + + iscsi_destroy_url(iscsi_url); +} + +static QemuOptsList runtime_opts = { + .name = "iscsi", + .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head), + .desc = { + { + .name = "transport", + .type = QEMU_OPT_STRING, + }, + { + .name = "portal", + .type = QEMU_OPT_STRING, + }, + { + .name = "target", + .type = QEMU_OPT_STRING, + }, + { + .name = "user", + .type = QEMU_OPT_STRING, + }, + { + .name = "password", + .type = QEMU_OPT_STRING, + }, + { + .name = "password-secret", + .type = QEMU_OPT_STRING, + }, + { + .name = "lun", + .type = QEMU_OPT_NUMBER, + }, + { + .name = "initiator-name", + .type = QEMU_OPT_STRING, + }, + { + .name = "header-digest", + .type = QEMU_OPT_STRING, + }, + { + .name = "timeout", + .type = QEMU_OPT_NUMBER, + }, + { /* end of list */ } + }, +}; + static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, Error **errp) { IscsiLun *iscsilun = bs->opaque; struct iscsi_context *iscsi = NULL; - struct iscsi_url *iscsi_url = NULL; struct scsi_task *task = NULL; struct scsi_inquiry_standard *inq = NULL; struct scsi_inquiry_supported_pages *inq_vpd; char *initiator_name = NULL; QemuOpts *opts; Error *local_err = NULL; - const char *filename; - int i, ret = 0, timeout = 0; + const char *transport_name, *portal, *target; +#if LIBISCSI_API_VERSION >= (20160603) + enum iscsi_transport_type transport; +#endif + int i, ret = 0, timeout = 0, lun; opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort); qemu_opts_absorb_qdict(opts, options, &local_err); @@ -1641,18 +1715,34 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, goto out; } - filename = qemu_opt_get(opts, "filename"); + transport_name = qemu_opt_get(opts, "transport"); + portal = qemu_opt_get(opts, "portal"); + target = qemu_opt_get(opts, "target"); + lun = qemu_opt_get_number(opts, "lun", 0); - iscsi_url = iscsi_parse_full_url(iscsi, filename); - if (iscsi_url == NULL) { - error_setg(errp, "Failed to parse URL : %s", filename); + if (!transport_name || !portal || !target) { + error_setg(errp, "Need all of transport, portal and target options"); + ret = -EINVAL; + goto out; + } + + if (!strcmp(transport_name, "tcp")) { +#if LIBISCSI_API_VERSION >= (20160603) + transport = TCP_TRANSPORT; + } else if (!strcmp(transport_name, "iser")) { + transport = ISER_TRANSPORT; +#else + /* TCP is what older libiscsi versions always use */ +#endif + } else { + error_setg(errp, "Unknown transport: %s", transport_name); ret = -EINVAL; goto out; } memset(iscsilun, 0, sizeof(IscsiLun)); - initiator_name = parse_initiator_name(iscsi_url->target); + initiator_name = get_initiator_name(opts); iscsi = iscsi_create_context(initiator_name); if (iscsi == NULL) { @@ -1661,30 +1751,20 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, goto out; } #if LIBISCSI_API_VERSION >= (20160603) - if (iscsi_init_transport(iscsi, iscsi_url->transport)) { + if (iscsi_init_transport(iscsi, transport)) { error_setg(errp, ("Error initializing transport.")); ret = -EINVAL; goto out; } #endif - if (iscsi_set_targetname(iscsi, iscsi_url->target)) { + if (iscsi_set_targetname(iscsi, target)) { error_setg(errp, "iSCSI: Failed to set target name."); ret = -EINVAL; goto out; } - if (iscsi_url->user[0] != '\0') { - ret = iscsi_set_initiator_username_pwd(iscsi, iscsi_url->user, - iscsi_url->passwd); - if (ret != 0) { - error_setg(errp, "Failed to set initiator username and password"); - ret = -EINVAL; - goto out; - } - } - /* check if we got CHAP username/password via the options */ - parse_chap(iscsi, iscsi_url->target, &local_err); + apply_chap(iscsi, opts, &local_err); if (local_err != NULL) { error_propagate(errp, local_err); ret = -EINVAL; @@ -1697,10 +1777,8 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, goto out; } - iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); - /* check if we got HEADER_DIGEST via the options */ - parse_header_digest(iscsi, iscsi_url->target, &local_err); + apply_header_digest(iscsi, opts, &local_err); if (local_err != NULL) { error_propagate(errp, local_err); ret = -EINVAL; @@ -1708,7 +1786,7 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, } /* timeout handling is broken in libiscsi before 1.15.0 */ - timeout = parse_timeout(iscsi_url->target); + timeout = qemu_opt_get_number(opts, "timeout", 0); #if LIBISCSI_API_VERSION >= 20150621 iscsi_set_timeout(iscsi, timeout); #else @@ -1717,7 +1795,7 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, } #endif - if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) != 0) { + if (iscsi_full_connect_sync(iscsi, portal, lun) != 0) { error_setg(errp, "iSCSI: Failed to connect to LUN : %s", iscsi_get_error(iscsi)); ret = -EINVAL; @@ -1726,7 +1804,7 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, iscsilun->iscsi = iscsi; iscsilun->aio_context = bdrv_get_aio_context(bs); - iscsilun->lun = iscsi_url->lun; + iscsilun->lun = lun; iscsilun->has_write_same = true; task = iscsi_do_inquiry(iscsilun->iscsi, iscsilun->lun, 0, 0, @@ -1829,9 +1907,6 @@ static int iscsi_open(BlockDriverState *bs, QDict *options, int flags, out: qemu_opts_del(opts); g_free(initiator_name); - if (iscsi_url != NULL) { - iscsi_destroy_url(iscsi_url); - } if (task != NULL) { scsi_free_scsi_task(task); } @@ -2040,15 +2115,15 @@ static BlockDriver bdrv_iscsi = { .format_name = "iscsi", .protocol_name = "iscsi", - .instance_size = sizeof(IscsiLun), - .bdrv_needs_filename = true, - .bdrv_file_open = iscsi_open, - .bdrv_close = iscsi_close, - .bdrv_create = iscsi_create, - .create_opts = &iscsi_create_opts, - .bdrv_reopen_prepare = iscsi_reopen_prepare, - .bdrv_reopen_commit = iscsi_reopen_commit, - .bdrv_invalidate_cache = iscsi_invalidate_cache, + .instance_size = sizeof(IscsiLun), + .bdrv_parse_filename = iscsi_parse_filename, + .bdrv_file_open = iscsi_open, + .bdrv_close = iscsi_close, + .bdrv_create = iscsi_create, + .create_opts = &iscsi_create_opts, + .bdrv_reopen_prepare = iscsi_reopen_prepare, + .bdrv_reopen_commit = iscsi_reopen_commit, + .bdrv_invalidate_cache = iscsi_invalidate_cache, .bdrv_getlength = iscsi_getlength, .bdrv_get_info = iscsi_get_info, @@ -2075,15 +2150,15 @@ static BlockDriver bdrv_iser = { .format_name = "iser", .protocol_name = "iser", - .instance_size = sizeof(IscsiLun), - .bdrv_needs_filename = true, - .bdrv_file_open = iscsi_open, - .bdrv_close = iscsi_close, - .bdrv_create = iscsi_create, - .create_opts = &iscsi_create_opts, - .bdrv_reopen_prepare = iscsi_reopen_prepare, - .bdrv_reopen_commit = iscsi_reopen_commit, - .bdrv_invalidate_cache = iscsi_invalidate_cache, + .instance_size = sizeof(IscsiLun), + .bdrv_parse_filename = iscsi_parse_filename, + .bdrv_file_open = iscsi_open, + .bdrv_close = iscsi_close, + .bdrv_create = iscsi_create, + .create_opts = &iscsi_create_opts, + .bdrv_reopen_prepare = iscsi_reopen_prepare, + .bdrv_reopen_commit = iscsi_reopen_commit, + .bdrv_invalidate_cache = iscsi_invalidate_cache, .bdrv_getlength = iscsi_getlength, .bdrv_get_info = iscsi_get_info, diff --git a/block/mirror.c b/block/mirror.c index 698a54e50f..ca8547b1d3 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -69,6 +69,7 @@ typedef struct MirrorBlockJob { bool waiting_for_io; int target_cluster_sectors; int max_iov; + bool initial_zeroing_ongoing; } MirrorBlockJob; typedef struct MirrorOp { @@ -117,9 +118,10 @@ static void mirror_iteration_done(MirrorOp *op, int ret) if (s->cow_bitmap) { bitmap_set(s->cow_bitmap, chunk_num, nb_chunks); } - s->common.offset += (uint64_t)op->nb_sectors * BDRV_SECTOR_SIZE; + if (!s->initial_zeroing_ongoing) { + s->common.offset += (uint64_t)op->nb_sectors * BDRV_SECTOR_SIZE; + } } - qemu_iovec_destroy(&op->qiov); g_free(op); @@ -572,6 +574,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s) return 0; } + s->initial_zeroing_ongoing = true; for (sector_num = 0; sector_num < end; ) { int nb_sectors = MIN(end - sector_num, QEMU_ALIGN_DOWN(INT_MAX, s->granularity) >> BDRV_SECTOR_BITS); @@ -579,6 +582,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s) mirror_throttle(s); if (block_job_is_cancelled(&s->common)) { + s->initial_zeroing_ongoing = false; return 0; } @@ -593,6 +597,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s) } mirror_wait_for_all_io(s); + s->initial_zeroing_ongoing = false; } /* First part, loop on the sectors and initialize the dirty bitmap. */ diff --git a/qapi/block-core.json b/qapi/block-core.json index 932f5bb3b4..5f82d35fab 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -2110,16 +2110,17 @@ # @nfs: Since 2.8 # @replication: Since 2.8 # @ssh: Since 2.8 +# @iscsi: Since 2.9 # # Since: 2.0 ## { 'enum': 'BlockdevDriver', 'data': [ 'archipelago', 'blkdebug', 'blkverify', 'bochs', 'cloop', 'dmg', 'file', 'ftp', 'ftps', 'gluster', 'host_cdrom', - 'host_device', 'http', 'https', 'luks', 'nbd', 'nfs', 'null-aio', - 'null-co', 'parallels', 'qcow', 'qcow2', 'qed', 'quorum', 'raw', - 'replication', 'ssh', 'vdi', 'vhdx', 'vmdk', 'vpc', - 'vvfat' ] } + 'host_device', 'http', 'https', 'iscsi', 'luks', 'nbd', 'nfs', + 'null-aio', 'null-co', 'parallels', 'qcow', 'qcow2', 'qed', + 'quorum', 'raw', 'replication', 'ssh', 'vdi', 'vhdx', 'vmdk', + 'vpc', 'vvfat' ] } ## # @BlockdevOptionsFile: @@ -2601,6 +2602,70 @@ '*logfile': 'str' } } ## +# @IscsiTransport: +# +# An enumeration of libiscsi transport types +# +# Since: 2.9 +## +{ 'enum': 'IscsiTransport', + 'data': [ 'tcp', 'iser' ] } + +## +# @IscsiHeaderDigest: +# +# An enumeration of header digests supported by libiscsi +# +# Since: 2.9 +## +{ 'enum': 'IscsiHeaderDigest', + 'prefix': 'QAPI_ISCSI_HEADER_DIGEST', + 'data': [ 'crc32c', 'none', 'crc32c-none', 'none-crc32c' ] } + +## +# @BlockdevOptionsIscsi: +# +# @transport The iscsi transport type +# +# @portal The address of the iscsi portal +# +# @target The target iqn name +# +# @lun #optional LUN to connect to. Defaults to 0. +# +# @user #optional User name to log in with. If omitted, no CHAP +# authentication is performed. +# +# @password-secret #optional The ID of a QCryptoSecret object providing +# the password for the login. This option is required if +# @user is specified. +# +# @initiator-name #optional The iqn name we want to identify to the target +# as. If this option is not specified, an initiator name is +# generated automatically. +# +# @header-digest #optional The desired header digest. Defaults to +# none-crc32c. +# +# @timeout #optional Timeout in seconds after which a request will +# timeout. 0 means no timeout and is the default. +# +# Driver specific block device options for iscsi +# +# Since: 2.9 +## +{ 'struct': 'BlockdevOptionsIscsi', + 'data': { 'transport': 'IscsiTransport', + 'portal': 'str', + 'target': 'str', + '*lun': 'int', + '*user': 'str', + '*password-secret': 'str', + '*initiator-name': 'str', + '*header-digest': 'IscsiHeaderDigest', + '*timeout': 'int' } } + +## # @ReplicationMode: # # An enumeration of replication modes. @@ -2786,7 +2851,7 @@ 'host_device':'BlockdevOptionsFile', 'http': 'BlockdevOptionsCurl', 'https': 'BlockdevOptionsCurl', -# TODO iscsi: Wait for structured options + 'iscsi': 'BlockdevOptionsIscsi', 'luks': 'BlockdevOptionsLUKS', 'nbd': 'BlockdevOptionsNbd', 'nfs': 'BlockdevOptionsNfs', @@ -2845,34 +2910,36 @@ # 1. # -> { "execute": "blockdev-add", # "arguments": { -# "options" : { "driver": "qcow2", -# "file": { "driver": "file", -# "filename": "test.qcow2" } } } } +# "driver": "qcow2", +# "node-name": "test1", +# "file": { +# "driver": "file", +# "filename": "test.qcow2" +# } +# } +# } # <- { "return": {} } # # 2. # -> { "execute": "blockdev-add", # "arguments": { -# "options": { -# "driver": "qcow2", -# "node-name": "node0", -# "discard": "unmap", -# "cache": { -# "direct": true, -# "writeback": true +# "driver": "qcow2", +# "node-name": "node0", +# "discard": "unmap", +# "cache": { +# "direct": true # }, # "file": { -# "driver": "file", -# "filename": "/tmp/test.qcow2" +# "driver": "file", +# "filename": "/tmp/test.qcow2" # }, # "backing": { -# "driver": "raw", -# "file": { -# "driver": "file", -# "filename": "/dev/fdset/4" +# "driver": "raw", +# "file": { +# "driver": "file", +# "filename": "/dev/fdset/4" # } # } -# } # } # } # @@ -2900,14 +2967,12 @@ # # -> { "execute": "blockdev-add", # "arguments": { -# "options": { -# "driver": "qcow2", -# "node-name": "node0", -# "file": { -# "driver": "file", -# "filename": "test.qcow2" -# } -# } +# "driver": "qcow2", +# "node-name": "node0", +# "file": { +# "driver": "file", +# "filename": "test.qcow2" +# } # } # } # <- { "return": {} } diff --git a/qemu-options.hx b/qemu-options.hx index 1e815ef520..9936cf38f3 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2586,7 +2586,7 @@ Example qemu-system-i386 --drive file=sheepdog://192.0.2.1:30000/MyVirtualMachine @end example -See also @url{http://http://www.osrg.net/sheepdog/}. +See also @url{https://sheepdog.github.io/sheepdog/}. @item GlusterFS GlusterFS is a user space distributed file system. |