Block layer patches

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v2.0.22 (GNU/Linux)
 
 iQIcBAABAgAGBQJYEjZDAAoJEH8JsnLIjy/WSXQQALz5t/O6jnLkLMceuJvAdxzk
 1w3cwcc2I2VOSubqnUXuK9KCFK0R8ifN0UmYNENbh9DCNEhDpbD16hbkO5LvnRwk
 IaFpt1jAP7Y9epZ3GnJ0JdAsk+PDnqj3zQW6PSJLyzXVOnyVCE7aU+fjEv3Khoa7
 88ye3xBx8F9RDaQ4EBOSm55q/gEk0RHnFbgq/YurHzWg5go23VwunVBP7XALG6Bs
 2jm5/iEKXdoIoJ57dADQzUf2WWMgE73CO9kd/c9iaFmd2FWOHHRsuFyj7a38bKmn
 N7kRDBrC3MlOPxP6zB2jBNmAa70cdQO9Ktqm7geTTb1WWSxxUHBuX1TtAsish6d7
 aYVSZyNoaSdwcRSnNTnJkscNItldAUtoPvgrYCbniWVRU7YiY+yUXsQyWdmpKbTE
 JLy06p4mGZEuDR5RDMWZfaJbw+eNtmpiL9vMRBM+A9EzMIhuVm1hu34/SRyvcPSM
 fyzW5gAYsPA7E+nZT1Jkpw/f8jxxUo1vdhQpWWGijCMK5kpkumfWXZKnuSKwvRYz
 xcHdGE1nbLfWaBgyClZCLRMNb3CFuDEsr4NCrjwGr4xdLs9VbxXxvNqvFl8DOWR/
 amVsQd6fvTfcusDxY9hY6BOlgID/dqfi7wzvxowDhsTG6ewSsqwjYLzUwADzcKn0
 sAmLBX3P3sJNrf7Y9UaT
 =BtvM
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging

Block layer patches

# gpg: Signature made Thu 27 Oct 2016 18:15:47 BST
# gpg:                using RSA key 0x7F09B272C88F2FD6
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>"
# Primary key fingerprint: DC3D EB15 9A9A F95D 3D74  56FE 7F09 B272 C88F 2FD6

* remotes/kevin/tags/for-upstream: (23 commits)
  iotests: Add test for NBD's blockdev-add interface
  iotests: Add assert_json_filename_equal() method
  socket_scm_helper: Accept fd directly
  iotests.py: Allow concurrent qemu instances
  iotests.py: Add qemu_nbd function
  qapi: Allow blockdev-add for NBD
  block/nbd: Use SocketAddress options
  block/nbd: Accept SocketAddress
  block/nbd: Add nbd_has_filename_options_conflict()
  block/nbd: Use qdict_put()
  block/nbd: Default port in nbd_refresh_filename()
  block/nbd: Reject port parameter without host
  block/nbd: Drop trailing "." in error messages
  qemu-iotests: Fix typo for NFS with IMGOPTSSYNTAX
  block: Remove bdrv_aio_ioctl()
  raw: Implement .bdrv_co_ioctl instead of .bdrv_aio_ioctl
  block: Introduce .bdrv_co_ioctl() driver callback
  block: Remove bdrv_ioctl()
  raw-posix: Don't use bdrv_ioctl()
  block: Use blk_co_ioctl() for all BB level ioctls
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2016-10-28 12:06:41 +01:00
commit 9879b75873
18 changed files with 536 additions and 259 deletions

View File

@ -1099,26 +1099,36 @@ BlockAIOCB *blk_aio_pwritev(BlockBackend *blk, int64_t offset,
blk_aio_write_entry, flags, cb, opaque);
}
static void blk_aio_flush_entry(void *opaque)
{
BlkAioEmAIOCB *acb = opaque;
BlkRwCo *rwco = &acb->rwco;
rwco->ret = blk_co_flush(rwco->blk);
blk_aio_complete(acb);
}
BlockAIOCB *blk_aio_flush(BlockBackend *blk,
BlockCompletionFunc *cb, void *opaque)
{
if (!blk_is_available(blk)) {
return blk_abort_aio_request(blk, cb, opaque, -ENOMEDIUM);
}
return blk_aio_prwv(blk, 0, 0, NULL, blk_aio_flush_entry, 0, cb, opaque);
}
return bdrv_aio_flush(blk_bs(blk), cb, opaque);
static void blk_aio_pdiscard_entry(void *opaque)
{
BlkAioEmAIOCB *acb = opaque;
BlkRwCo *rwco = &acb->rwco;
rwco->ret = blk_co_pdiscard(rwco->blk, rwco->offset, acb->bytes);
blk_aio_complete(acb);
}
BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk,
int64_t offset, int count,
BlockCompletionFunc *cb, void *opaque)
{
int ret = blk_check_byte_request(blk, offset, count);
if (ret < 0) {
return blk_abort_aio_request(blk, cb, opaque, ret);
}
return bdrv_aio_pdiscard(blk_bs(blk), offset, count, cb, opaque);
return blk_aio_prwv(blk, offset, count, NULL, blk_aio_pdiscard_entry, 0,
cb, opaque);
}
void blk_aio_cancel(BlockAIOCB *acb)
@ -1131,23 +1141,50 @@ void blk_aio_cancel_async(BlockAIOCB *acb)
bdrv_aio_cancel_async(acb);
}
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
int blk_co_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
{
if (!blk_is_available(blk)) {
return -ENOMEDIUM;
}
return bdrv_ioctl(blk_bs(blk), req, buf);
return bdrv_co_ioctl(blk_bs(blk), req, buf);
}
static void blk_ioctl_entry(void *opaque)
{
BlkRwCo *rwco = opaque;
rwco->ret = blk_co_ioctl(rwco->blk, rwco->offset,
rwco->qiov->iov[0].iov_base);
}
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf)
{
return blk_prw(blk, req, buf, 0, blk_ioctl_entry, 0);
}
static void blk_aio_ioctl_entry(void *opaque)
{
BlkAioEmAIOCB *acb = opaque;
BlkRwCo *rwco = &acb->rwco;
rwco->ret = blk_co_ioctl(rwco->blk, rwco->offset,
rwco->qiov->iov[0].iov_base);
blk_aio_complete(acb);
}
BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
BlockCompletionFunc *cb, void *opaque)
{
if (!blk_is_available(blk)) {
return blk_abort_aio_request(blk, cb, opaque, -ENOMEDIUM);
}
QEMUIOVector qiov;
struct iovec iov;
return bdrv_aio_ioctl(blk_bs(blk), req, buf, cb, opaque);
iov = (struct iovec) {
.iov_base = buf,
.iov_len = 0,
};
qemu_iovec_init_external(&qiov, &iov, 1);
return blk_aio_prwv(blk, req, 0, &qiov, blk_aio_ioctl_entry, 0, cb, opaque);
}
int blk_co_pdiscard(BlockBackend *blk, int64_t offset, int count)
@ -1169,13 +1206,15 @@ int blk_co_flush(BlockBackend *blk)
return bdrv_co_flush(blk_bs(blk));
}
static void blk_flush_entry(void *opaque)
{
BlkRwCo *rwco = opaque;
rwco->ret = blk_co_flush(rwco->blk);
}
int blk_flush(BlockBackend *blk)
{
if (!blk_is_available(blk)) {
return -ENOMEDIUM;
}
return bdrv_flush(blk_bs(blk));
return blk_prw(blk, 0, NULL, 0, blk_flush_entry, 0);
}
void blk_drain(BlockBackend *blk)
@ -1555,14 +1594,15 @@ int blk_truncate(BlockBackend *blk, int64_t offset)
return bdrv_truncate(blk_bs(blk), offset);
}
static void blk_pdiscard_entry(void *opaque)
{
BlkRwCo *rwco = opaque;
rwco->ret = blk_co_pdiscard(rwco->blk, rwco->offset, rwco->qiov->size);
}
int blk_pdiscard(BlockBackend *blk, int64_t offset, int count)
{
int ret = blk_check_byte_request(blk, offset, count);
if (ret < 0) {
return ret;
}
return bdrv_pdiscard(blk_bs(blk), offset, count);
return blk_prw(blk, offset, NULL, count, blk_pdiscard_entry, 0);
}
int blk_save_vmstate(BlockBackend *blk, const uint8_t *buf,

View File

@ -2196,35 +2196,6 @@ BlockAIOCB *bdrv_aio_flush(BlockDriverState *bs,
return &acb->common;
}
static void coroutine_fn bdrv_aio_pdiscard_co_entry(void *opaque)
{
BlockAIOCBCoroutine *acb = opaque;
BlockDriverState *bs = acb->common.bs;
acb->req.error = bdrv_co_pdiscard(bs, acb->req.offset, acb->req.bytes);
bdrv_co_complete(acb);
}
BlockAIOCB *bdrv_aio_pdiscard(BlockDriverState *bs, int64_t offset, int count,
BlockCompletionFunc *cb, void *opaque)
{
Coroutine *co;
BlockAIOCBCoroutine *acb;
trace_bdrv_aio_pdiscard(bs, offset, count, opaque);
acb = qemu_aio_get(&bdrv_em_co_aiocb_info, bs, cb, opaque);
acb->need_bh = true;
acb->req.error = -EINPROGRESS;
acb->req.offset = offset;
acb->req.bytes = count;
co = qemu_coroutine_create(bdrv_aio_pdiscard_co_entry, acb);
qemu_coroutine_enter(co);
bdrv_co_maybe_schedule_bh(acb);
return &acb->common;
}
void *qemu_aio_get(const AIOCBInfo *aiocb_info, BlockDriverState *bs,
BlockCompletionFunc *cb, void *opaque)
{
@ -2521,7 +2492,7 @@ int bdrv_pdiscard(BlockDriverState *bs, int64_t offset, int count)
return rwco.ret;
}
static int bdrv_co_do_ioctl(BlockDriverState *bs, int req, void *buf)
int bdrv_co_ioctl(BlockDriverState *bs, int req, void *buf)
{
BlockDriver *drv = bs->drv;
BdrvTrackedRequest tracked_req;
@ -2531,86 +2502,26 @@ static int bdrv_co_do_ioctl(BlockDriverState *bs, int req, void *buf)
BlockAIOCB *acb;
tracked_request_begin(&tracked_req, bs, 0, 0, BDRV_TRACKED_IOCTL);
if (!drv || !drv->bdrv_aio_ioctl) {
if (!drv || (!drv->bdrv_aio_ioctl && !drv->bdrv_co_ioctl)) {
co.ret = -ENOTSUP;
goto out;
}
acb = drv->bdrv_aio_ioctl(bs, req, buf, bdrv_co_io_em_complete, &co);
if (!acb) {
co.ret = -ENOTSUP;
goto out;
if (drv->bdrv_co_ioctl) {
co.ret = drv->bdrv_co_ioctl(bs, req, buf);
} else {
acb = drv->bdrv_aio_ioctl(bs, req, buf, bdrv_co_io_em_complete, &co);
if (!acb) {
co.ret = -ENOTSUP;
goto out;
}
qemu_coroutine_yield();
}
qemu_coroutine_yield();
out:
tracked_request_end(&tracked_req);
return co.ret;
}
typedef struct {
BlockDriverState *bs;
int req;
void *buf;
int ret;
} BdrvIoctlCoData;
static void coroutine_fn bdrv_co_ioctl_entry(void *opaque)
{
BdrvIoctlCoData *data = opaque;
data->ret = bdrv_co_do_ioctl(data->bs, data->req, data->buf);
}
/* needed for generic scsi interface */
int bdrv_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
{
BdrvIoctlCoData data = {
.bs = bs,
.req = req,
.buf = buf,
.ret = -EINPROGRESS,
};
if (qemu_in_coroutine()) {
/* Fast-path if already in coroutine context */
bdrv_co_ioctl_entry(&data);
} else {
Coroutine *co = qemu_coroutine_create(bdrv_co_ioctl_entry, &data);
qemu_coroutine_enter(co);
while (data.ret == -EINPROGRESS) {
aio_poll(bdrv_get_aio_context(bs), true);
}
}
return data.ret;
}
static void coroutine_fn bdrv_co_aio_ioctl_entry(void *opaque)
{
BlockAIOCBCoroutine *acb = opaque;
acb->req.error = bdrv_co_do_ioctl(acb->common.bs,
acb->req.req, acb->req.buf);
bdrv_co_complete(acb);
}
BlockAIOCB *bdrv_aio_ioctl(BlockDriverState *bs,
unsigned long int req, void *buf,
BlockCompletionFunc *cb, void *opaque)
{
BlockAIOCBCoroutine *acb = qemu_aio_get(&bdrv_em_co_aiocb_info,
bs, cb, opaque);
Coroutine *co;
acb->need_bh = true;
acb->req.error = -EINPROGRESS;
acb->req.req = req;
acb->req.buf = buf;
co = qemu_coroutine_create(bdrv_co_aio_ioctl_entry, acb);
qemu_coroutine_enter(co);
bdrv_co_maybe_schedule_bh(acb);
return &acb->common;
}
void *qemu_blockalign(BlockDriverState *bs, size_t size)
{
return qemu_memalign(bdrv_opt_mem_align(bs), size);

View File

@ -32,6 +32,9 @@
#include "qemu/uri.h"
#include "block/block_int.h"
#include "qemu/module.h"
#include "qapi-visit.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qint.h"
@ -44,7 +47,8 @@ typedef struct BDRVNBDState {
NbdClientSession client;
/* For nbd_refresh_filename() */
char *path, *host, *port, *export, *tlscredsid;
SocketAddress *saddr;
char *export, *tlscredsid;
} BDRVNBDState;
static int nbd_parse_uri(const char *filename, QDict *options)
@ -90,9 +94,13 @@ static int nbd_parse_uri(const char *filename, QDict *options)
ret = -EINVAL;
goto out;
}
qdict_put(options, "path", qstring_from_str(qp->p[0].value));
qdict_put(options, "server.type", qstring_from_str("unix"));
qdict_put(options, "server.data.path",
qstring_from_str(qp->p[0].value));
} else {
QString *host;
char *port_str;
/* nbd[+tcp]://host[:port]/export */
if (!uri->server) {
ret = -EINVAL;
@ -107,12 +115,12 @@ static int nbd_parse_uri(const char *filename, QDict *options)
host = qstring_from_str(uri->server);
}
qdict_put(options, "host", host);
if (uri->port) {
char* port_str = g_strdup_printf("%d", uri->port);
qdict_put(options, "port", qstring_from_str(port_str));
g_free(port_str);
}
qdict_put(options, "server.type", qstring_from_str("inet"));
qdict_put(options, "server.data.host", host);
port_str = g_strdup_printf("%d", uri->port ?: NBD_DEFAULT_PORT);
qdict_put(options, "server.data.port", qstring_from_str(port_str));
g_free(port_str);
}
out:
@ -123,6 +131,26 @@ out:
return ret;
}
static bool nbd_has_filename_options_conflict(QDict *options, Error **errp)
{
const QDictEntry *e;
for (e = qdict_first(options); e; e = qdict_next(options, e)) {
if (!strcmp(e->key, "host") ||
!strcmp(e->key, "port") ||
!strcmp(e->key, "path") ||
!strcmp(e->key, "export") ||
strstart(e->key, "server.", NULL))
{
error_setg(errp, "Option '%s' cannot be used with a file name",
e->key);
return true;
}
}
return false;
}
static void nbd_parse_filename(const char *filename, QDict *options,
Error **errp)
{
@ -131,12 +159,7 @@ static void nbd_parse_filename(const char *filename, QDict *options,
const char *host_spec;
const char *unixpath;
if (qdict_haskey(options, "host")
|| qdict_haskey(options, "port")
|| qdict_haskey(options, "path"))
{
error_setg(errp, "host/port/path and a file name may not be specified "
"at the same time");
if (nbd_has_filename_options_conflict(options, errp)) {
return;
}
@ -173,7 +196,8 @@ static void nbd_parse_filename(const char *filename, QDict *options,
/* are we a UNIX or TCP socket? */
if (strstart(host_spec, "unix:", &unixpath)) {
qdict_put(options, "path", qstring_from_str(unixpath));
qdict_put(options, "server.type", qstring_from_str("unix"));
qdict_put(options, "server.data.path", qstring_from_str(unixpath));
} else {
InetSocketAddress *addr = NULL;
@ -182,8 +206,9 @@ static void nbd_parse_filename(const char *filename, QDict *options,
goto out;
}
qdict_put(options, "host", qstring_from_str(addr->host));
qdict_put(options, "port", qstring_from_str(addr->port));
qdict_put(options, "server.type", qstring_from_str("inet"));
qdict_put(options, "server.data.host", qstring_from_str(addr->host));
qdict_put(options, "server.data.port", qstring_from_str(addr->port));
qapi_free_InetSocketAddress(addr);
}
@ -191,47 +216,81 @@ out:
g_free(file);
}
static SocketAddress *nbd_config(BDRVNBDState *s, QemuOpts *opts, Error **errp)
static bool nbd_process_legacy_socket_options(QDict *output_options,
QemuOpts *legacy_opts,
Error **errp)
{
SocketAddress *saddr;
const char *path = qemu_opt_get(legacy_opts, "path");
const char *host = qemu_opt_get(legacy_opts, "host");
const char *port = qemu_opt_get(legacy_opts, "port");
const QDictEntry *e;
s->path = g_strdup(qemu_opt_get(opts, "path"));
s->host = g_strdup(qemu_opt_get(opts, "host"));
if (!s->path == !s->host) {
if (s->path) {
error_setg(errp, "path and host may not be used at the same time.");
} else {
error_setg(errp, "one of path and host must be specified.");
}
return NULL;
if (!path && !host && !port) {
return true;
}
saddr = g_new0(SocketAddress, 1);
if (s->path) {
UnixSocketAddress *q_unix;
saddr->type = SOCKET_ADDRESS_KIND_UNIX;
q_unix = saddr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
q_unix->path = g_strdup(s->path);
} else {
InetSocketAddress *inet;
s->port = g_strdup(qemu_opt_get(opts, "port"));
saddr->type = SOCKET_ADDRESS_KIND_INET;
inet = saddr->u.inet.data = g_new0(InetSocketAddress, 1);
inet->host = g_strdup(s->host);
inet->port = g_strdup(s->port);
if (!inet->port) {
inet->port = g_strdup_printf("%d", NBD_DEFAULT_PORT);
for (e = qdict_first(output_options); e; e = qdict_next(output_options, e))
{
if (strstart(e->key, "server.", NULL)) {
error_setg(errp, "Cannot use 'server' and path/host/port at the "
"same time");
return false;
}
}
if (path && host) {
error_setg(errp, "path and host may not be used at the same time");
return false;
} else if (path) {
if (port) {
error_setg(errp, "port may not be used without host");
return false;
}
qdict_put(output_options, "server.type", qstring_from_str("unix"));
qdict_put(output_options, "server.data.path", qstring_from_str(path));
} else if (host) {
qdict_put(output_options, "server.type", qstring_from_str("inet"));
qdict_put(output_options, "server.data.host", qstring_from_str(host));
qdict_put(output_options, "server.data.port",
qstring_from_str(port ?: stringify(NBD_DEFAULT_PORT)));
}
return true;
}
static SocketAddress *nbd_config(BDRVNBDState *s, QDict *options, Error **errp)
{
SocketAddress *saddr = NULL;
QDict *addr = NULL;
QObject *crumpled_addr = NULL;
Visitor *iv = NULL;
Error *local_err = NULL;
qdict_extract_subqdict(options, &addr, "server.");
if (!qdict_size(addr)) {
error_setg(errp, "NBD server address missing");
goto done;
}
crumpled_addr = qdict_crumple(addr, errp);
if (!crumpled_addr) {
goto done;
}
iv = qobject_input_visitor_new(crumpled_addr, true);
visit_type_SocketAddress(iv, NULL, &saddr, &local_err);
if (local_err) {
error_propagate(errp, local_err);
goto done;
}
s->client.is_unix = saddr->type == SOCKET_ADDRESS_KIND_UNIX;
s->export = g_strdup(qemu_opt_get(opts, "export"));
done:
QDECREF(addr);
qobject_decref(crumpled_addr);
visit_free(iv);
return saddr;
}
@ -332,7 +391,6 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
QemuOpts *opts = NULL;
Error *local_err = NULL;
QIOChannelSocket *sioc = NULL;
SocketAddress *saddr = NULL;
QCryptoTLSCreds *tlscreds = NULL;
const char *hostname = NULL;
int ret = -EINVAL;
@ -344,12 +402,19 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
goto error;
}
/* Pop the config into our state object. Exit if invalid. */
saddr = nbd_config(s, opts, errp);
if (!saddr) {
/* Translate @host, @port, and @path to a SocketAddress */
if (!nbd_process_legacy_socket_options(options, opts, errp)) {
goto error;
}
/* Pop the config into our state object. Exit if invalid. */
s->saddr = nbd_config(s, options, errp);
if (!s->saddr) {
goto error;
}
s->export = g_strdup(qemu_opt_get(opts, "export"));
s->tlscredsid = g_strdup(qemu_opt_get(opts, "tls-creds"));
if (s->tlscredsid) {
tlscreds = nbd_get_tls_creds(s->tlscredsid, errp);
@ -357,17 +422,17 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
goto error;
}
if (saddr->type != SOCKET_ADDRESS_KIND_INET) {
if (s->saddr->type != SOCKET_ADDRESS_KIND_INET) {
error_setg(errp, "TLS only supported over IP sockets");
goto error;
}
hostname = saddr->u.inet.data->host;
hostname = s->saddr->u.inet.data->host;
}
/* establish TCP connection, return error if it fails
* TODO: Configurable retry-until-timeout behaviour.
*/
sioc = nbd_establish_connection(saddr, errp);
sioc = nbd_establish_connection(s->saddr, errp);
if (!sioc) {
ret = -ECONNREFUSED;
goto error;
@ -384,13 +449,10 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
object_unref(OBJECT(tlscreds));
}
if (ret < 0) {
g_free(s->path);
g_free(s->host);
g_free(s->port);
qapi_free_SocketAddress(s->saddr);
g_free(s->export);
g_free(s->tlscredsid);
}
qapi_free_SocketAddress(saddr);
qemu_opts_del(opts);
return ret;
}
@ -412,9 +474,7 @@ static void nbd_close(BlockDriverState *bs)
nbd_client_close(bs);
g_free(s->path);
g_free(s->host);
g_free(s->port);
qapi_free_SocketAddress(s->saddr);
g_free(s->export);
g_free(s->tlscredsid);
}
@ -441,45 +501,51 @@ static void nbd_refresh_filename(BlockDriverState *bs, QDict *options)
{
BDRVNBDState *s = bs->opaque;
QDict *opts = qdict_new();
QObject *saddr_qdict;
Visitor *ov;
const char *host = NULL, *port = NULL, *path = NULL;
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("nbd")));
if (s->path && s->export) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd+unix:///%s?socket=%s", s->export, s->path);
} else if (s->path && !s->export) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd+unix://?socket=%s", s->path);
} else if (!s->path && s->export && s->port) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd://%s:%s/%s", s->host, s->port, s->export);
} else if (!s->path && s->export && !s->port) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd://%s/%s", s->host, s->export);
} else if (!s->path && !s->export && s->port) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd://%s:%s", s->host, s->port);
} else if (!s->path && !s->export && !s->port) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd://%s", s->host);
if (s->saddr->type == SOCKET_ADDRESS_KIND_INET) {
const InetSocketAddress *inet = s->saddr->u.inet.data;
if (!inet->has_ipv4 && !inet->has_ipv6 && !inet->has_to) {
host = inet->host;
port = inet->port;
}
} else if (s->saddr->type == SOCKET_ADDRESS_KIND_UNIX) {
path = s->saddr->u.q_unix.data->path;
}
if (s->path) {
qdict_put_obj(opts, "path", QOBJECT(qstring_from_str(s->path)));
} else if (s->port) {
qdict_put_obj(opts, "host", QOBJECT(qstring_from_str(s->host)));
qdict_put_obj(opts, "port", QOBJECT(qstring_from_str(s->port)));
} else {
qdict_put_obj(opts, "host", QOBJECT(qstring_from_str(s->host)));
qdict_put(opts, "driver", qstring_from_str("nbd"));
if (path && s->export) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd+unix:///%s?socket=%s", s->export, path);
} else if (path && !s->export) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd+unix://?socket=%s", path);
} else if (host && s->export) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd://%s:%s/%s", host, port, s->export);
} else if (host && !s->export) {
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
"nbd://%s:%s", host, port);
}
ov = qobject_output_visitor_new(&saddr_qdict);
visit_type_SocketAddress(ov, NULL, &s->saddr, &error_abort);
visit_complete(ov, &saddr_qdict);
assert(qobject_type(saddr_qdict) == QTYPE_QDICT);
qdict_put_obj(opts, "server", saddr_qdict);
if (s->export) {
qdict_put_obj(opts, "export", QOBJECT(qstring_from_str(s->export)));
qdict_put(opts, "export", qstring_from_str(s->export));
}
if (s->tlscredsid) {
qdict_put_obj(opts, "tls-creds",
QOBJECT(qstring_from_str(s->tlscredsid)));
qdict_put(opts, "tls-creds", qstring_from_str(s->tlscredsid));
}
qdict_flatten(opts);
bs->full_open_options = opts;
}

View File

@ -2069,13 +2069,23 @@ static bool hdev_is_sg(BlockDriverState *bs)
#if defined(__linux__)
BDRVRawState *s = bs->opaque;
struct stat st;
struct sg_scsi_id scsiid;
int sg_version;
int ret;
if (stat(bs->filename, &st) >= 0 && S_ISCHR(st.st_mode) &&
!bdrv_ioctl(bs, SG_GET_VERSION_NUM, &sg_version) &&
!bdrv_ioctl(bs, SG_GET_SCSI_ID, &scsiid)) {
if (stat(bs->filename, &st) < 0 || !S_ISCHR(st.st_mode)) {
return false;
}
ret = ioctl(s->fd, SG_GET_VERSION_NUM, &sg_version);
if (ret < 0) {
return false;
}
ret = ioctl(s->fd, SG_GET_SCSI_ID, &scsiid);
if (ret >= 0) {
DPRINTF("SG device found: type=%d, version=%d\n",
scsiid.scsi_type, sg_version);
return true;

View File

@ -176,12 +176,9 @@ static void raw_lock_medium(BlockDriverState *bs, bool locked)
bdrv_lock_medium(bs->file->bs, locked);
}
static BlockAIOCB *raw_aio_ioctl(BlockDriverState *bs,
unsigned long int req, void *buf,
BlockCompletionFunc *cb,
void *opaque)
static int raw_co_ioctl(BlockDriverState *bs, unsigned long int req, void *buf)
{
return bdrv_aio_ioctl(bs->file->bs, req, buf, cb, opaque);
return bdrv_co_ioctl(bs->file->bs, req, buf);
}
static int raw_has_zero_init(BlockDriverState *bs)
@ -261,7 +258,7 @@ BlockDriver bdrv_raw = {
.bdrv_media_changed = &raw_media_changed,
.bdrv_eject = &raw_eject,
.bdrv_lock_medium = &raw_lock_medium,
.bdrv_aio_ioctl = &raw_aio_ioctl,
.bdrv_co_ioctl = &raw_co_ioctl,
.create_opts = &raw_create_opts,
.bdrv_has_zero_init = &raw_has_zero_init
};

View File

@ -9,7 +9,6 @@ blk_co_preadv(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags
blk_co_pwritev(void *blk, void *bs, int64_t offset, unsigned int bytes, int flags) "blk %p bs %p offset %"PRId64" bytes %u flags %x"
# block/io.c
bdrv_aio_pdiscard(void *bs, int64_t offset, int count, void *opaque) "bs %p offset %"PRId64" count %d opaque %p"
bdrv_aio_flush(void *bs, void *opaque) "bs %p opaque %p"
bdrv_aio_readv(void *bs, int64_t sector_num, int nb_sectors, void *opaque) "bs %p sector_num %"PRId64" nb_sectors %d opaque %p"
bdrv_aio_writev(void *bs, int64_t sector_num, int nb_sectors, void *opaque) "bs %p sector_num %"PRId64" nb_sectors %d opaque %p"

View File

@ -314,17 +314,11 @@ BlockAIOCB *bdrv_aio_writev(BdrvChild *child, int64_t sector_num,
BlockCompletionFunc *cb, void *opaque);
BlockAIOCB *bdrv_aio_flush(BlockDriverState *bs,
BlockCompletionFunc *cb, void *opaque);
BlockAIOCB *bdrv_aio_pdiscard(BlockDriverState *bs,
int64_t offset, int count,
BlockCompletionFunc *cb, void *opaque);
void bdrv_aio_cancel(BlockAIOCB *acb);
void bdrv_aio_cancel_async(BlockAIOCB *acb);
/* sg packet commands */
int bdrv_ioctl(BlockDriverState *bs, unsigned long int req, void *buf);
BlockAIOCB *bdrv_aio_ioctl(BlockDriverState *bs,
unsigned long int req, void *buf,
BlockCompletionFunc *cb, void *opaque);
int bdrv_co_ioctl(BlockDriverState *bs, int req, void *buf);
/* Invalidate any cached metadata used by image formats */
void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp);

View File

@ -244,6 +244,8 @@ struct BlockDriver {
BlockAIOCB *(*bdrv_aio_ioctl)(BlockDriverState *bs,
unsigned long int req, void *buf,
BlockCompletionFunc *cb, void *opaque);
int coroutine_fn (*bdrv_co_ioctl)(BlockDriverState *bs,
unsigned long int req, void *buf);
/* List of options for creating images, terminated by name == NULL */
QemuOptsList *create_opts;

View File

@ -146,6 +146,7 @@ BlockAIOCB *blk_aio_pdiscard(BlockBackend *blk, int64_t offset, int count,
BlockCompletionFunc *cb, void *opaque);
void blk_aio_cancel(BlockAIOCB *acb);
void blk_aio_cancel_async(BlockAIOCB *acb);
int blk_co_ioctl(BlockBackend *blk, unsigned long int req, void *buf);
int blk_ioctl(BlockBackend *blk, unsigned long int req, void *buf);
BlockAIOCB *blk_aio_ioctl(BlockBackend *blk, unsigned long int req, void *buf,
BlockCompletionFunc *cb, void *opaque);

View File

@ -1703,15 +1703,16 @@
#
# @host_device, @host_cdrom: Since 2.1
# @gluster: Since 2.7
# @nbd: Since 2.8
#
# Since: 2.0
##
{ 'enum': 'BlockdevDriver',
'data': [ 'archipelago', 'blkdebug', 'blkverify', 'bochs', 'cloop',
'dmg', 'file', 'ftp', 'ftps', 'gluster', 'host_cdrom',
'host_device', 'http', 'https', 'luks', 'null-aio', 'null-co',
'parallels', 'qcow', 'qcow2', 'qed', 'quorum', 'raw',
'replication', 'tftp', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
'host_device', 'http', 'https', 'luks', 'nbd', 'null-aio',
'null-co', 'parallels', 'qcow', 'qcow2', 'qed', 'quorum', 'raw',
'replication', 'tftp', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
##
# @BlockdevOptionsFile
@ -2219,6 +2220,24 @@
{ 'struct': 'BlockdevOptionsCurl',
'data': { 'filename': 'str' } }
##
# @BlockdevOptionsNbd
#
# Driver specific block device options for NBD.
#
# @server: NBD server address
#
# @export: #optional export name
#
# @tls-creds: #optional TLS credentials ID
#
# Since: 2.8
##
{ 'struct': 'BlockdevOptionsNbd',
'data': { 'server': 'SocketAddress',
'*export': 'str',
'*tls-creds': 'str' } }
##
# @BlockdevOptions
#
@ -2264,7 +2283,7 @@
'https': 'BlockdevOptionsCurl',
# TODO iscsi: Wait for structured options
'luks': 'BlockdevOptionsLUKS',
# TODO nbd: Should take InetSocketAddress for 'host'?
'nbd': 'BlockdevOptionsNbd',
# TODO nfs: Wait for structured options
'null-aio': 'BlockdevOptionsNull',
'null-co': 'BlockdevOptionsNull',

View File

@ -222,7 +222,7 @@ Testing: -drive driver=file
QEMU_PROG: -drive driver=file: The 'file' block driver requires a file name
Testing: -drive driver=nbd
QEMU_PROG: -drive driver=nbd: one of path and host must be specified.
QEMU_PROG: -drive driver=nbd: NBD server address missing
Testing: -drive driver=raw
QEMU_PROG: -drive driver=raw: Can't use 'raw' as a block driver for the protocol level
@ -231,7 +231,7 @@ Testing: -drive file.driver=file
QEMU_PROG: -drive file.driver=file: The 'file' block driver requires a file name
Testing: -drive file.driver=nbd
QEMU_PROG: -drive file.driver=nbd: one of path and host must be specified.
QEMU_PROG: -drive file.driver=nbd: NBD server address missing
Testing: -drive file.driver=raw
QEMU_PROG: -drive file.driver=raw: Can't use 'raw' as a block driver for the protocol level

View File

@ -316,7 +316,7 @@ Testing: -drive driver=file
QEMU_PROG: -drive driver=file: The 'file' block driver requires a file name
Testing: -drive driver=nbd
QEMU_PROG: -drive driver=nbd: one of path and host must be specified.
QEMU_PROG: -drive driver=nbd: NBD server address missing
Testing: -drive driver=raw
QEMU_PROG: -drive driver=raw: Can't use 'raw' as a block driver for the protocol level
@ -325,7 +325,7 @@ Testing: -drive file.driver=file
QEMU_PROG: -drive file.driver=file: The 'file' block driver requires a file name
Testing: -drive file.driver=nbd
QEMU_PROG: -drive file.driver=nbd: one of path and host must be specified.
QEMU_PROG: -drive file.driver=nbd: NBD server address missing
Testing: -drive file.driver=raw
QEMU_PROG: -drive file.driver=raw: Can't use 'raw' as a block driver for the protocol level

195
tests/qemu-iotests/147 Executable file
View File

@ -0,0 +1,195 @@
#!/usr/bin/env python
#
# Test case for NBD's blockdev-add interface
#
# Copyright (C) 2016 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os
import socket
import stat
import time
import iotests
from iotests import cachemode, imgfmt, qemu_img, qemu_nbd
NBD_PORT = 10811
test_img = os.path.join(iotests.test_dir, 'test.img')
unix_socket = os.path.join(iotests.test_dir, 'nbd.socket')
class NBDBlockdevAddBase(iotests.QMPTestCase):
def blockdev_add_options(self, address, export=None):
options = { 'node-name': 'nbd-blockdev',
'driver': 'raw',
'file': {
'driver': 'nbd',
'server': address
} }
if export is not None:
options['file']['export'] = export
return options
def client_test(self, filename, address, export=None):
bao = self.blockdev_add_options(address, export)
result = self.vm.qmp('blockdev-add', **bao)
self.assert_qmp(result, 'return', {})
result = self.vm.qmp('query-named-block-nodes')
for node in result['return']:
if node['node-name'] == 'nbd-blockdev':
if isinstance(filename, str):
self.assert_qmp(node, 'image/filename', filename)
else:
self.assert_json_filename_equal(node['image']['filename'],
filename)
break
result = self.vm.qmp('x-blockdev-del', node_name='nbd-blockdev')
self.assert_qmp(result, 'return', {})
class QemuNBD(NBDBlockdevAddBase):
def setUp(self):
qemu_img('create', '-f', iotests.imgfmt, test_img, '64k')
self.vm = iotests.VM()
self.vm.launch()
def tearDown(self):
self.vm.shutdown()
os.remove(test_img)
try:
os.remove(unix_socket)
except OSError:
pass
def _server_up(self, *args):
self.assertEqual(qemu_nbd('-f', imgfmt, test_img, *args), 0)
def test_inet(self):
self._server_up('-p', str(NBD_PORT))
address = { 'type': 'inet',
'data': {
'host': 'localhost',
'port': str(NBD_PORT)
} }
self.client_test('nbd://localhost:%i' % NBD_PORT, address)
def test_unix(self):
self._server_up('-k', unix_socket)
address = { 'type': 'unix',
'data': { 'path': unix_socket } }
self.client_test('nbd+unix://?socket=' + unix_socket, address)
class BuiltinNBD(NBDBlockdevAddBase):
def setUp(self):
qemu_img('create', '-f', iotests.imgfmt, test_img, '64k')
self.vm = iotests.VM()
self.vm.launch()
self.server = iotests.VM('.server')
self.server.add_drive_raw('if=none,id=nbd-export,' +
'file=%s,' % test_img +
'format=%s,' % imgfmt +
'cache=%s' % cachemode)
self.server.launch()
def tearDown(self):
self.vm.shutdown()
self.server.shutdown()
os.remove(test_img)
try:
os.remove(unix_socket)
except OSError:
pass
def _server_up(self, address):
result = self.server.qmp('nbd-server-start', addr=address)
self.assert_qmp(result, 'return', {})
result = self.server.qmp('nbd-server-add', device='nbd-export')
self.assert_qmp(result, 'return', {})
def _server_down(self):
result = self.server.qmp('nbd-server-stop')
self.assert_qmp(result, 'return', {})
def test_inet(self):
address = { 'type': 'inet',
'data': {
'host': 'localhost',
'port': str(NBD_PORT)
} }
self._server_up(address)
self.client_test('nbd://localhost:%i/nbd-export' % NBD_PORT,
address, 'nbd-export')
self._server_down()
def test_inet6(self):
address = { 'type': 'inet',
'data': {
'host': '::1',
'port': str(NBD_PORT),
'ipv4': False,
'ipv6': True
} }
filename = { 'driver': 'raw',
'file': {
'driver': 'nbd',
'export': 'nbd-export',
'server': address
} }
self._server_up(address)
self.client_test(filename, address, 'nbd-export')
self._server_down()
def test_unix(self):
address = { 'type': 'unix',
'data': { 'path': unix_socket } }
self._server_up(address)
self.client_test('nbd+unix:///nbd-export?socket=' + unix_socket,
address, 'nbd-export')
self._server_down()
def test_fd(self):
self._server_up({ 'type': 'unix',
'data': { 'path': unix_socket } })
sockfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sockfd.connect(unix_socket)
result = self.vm.send_fd_scm(str(sockfd.fileno()))
self.assertEqual(result, 0, 'Failed to send socket FD')
result = self.vm.qmp('getfd', fdname='nbd-fifo')
self.assert_qmp(result, 'return', {})
address = { 'type': 'fd',
'data': { 'str': 'nbd-fifo' } }
filename = { 'driver': 'raw',
'file': {
'driver': 'nbd',
'export': 'nbd-export',
'server': address
} }
self.client_test(filename, address, 'nbd-export')
self._server_down()
if __name__ == '__main__':
# Need to support image creation
iotests.main(supported_fmts=['vpc', 'parallels', 'qcow', 'vdi', 'qcow2',
'vmdk', 'raw', 'vhdx', 'qed'])

View File

@ -0,0 +1,5 @@
......
----------------------------------------------------------------------
Ran 6 tests
OK

View File

@ -69,7 +69,7 @@ if [ "$IMGOPTSSYNTAX" = "true" ]; then
TEST_IMG="$DRIVER,file.driver=ssh,file.host=127.0.0.1,file.path=$TEST_IMG_FILE"
elif [ "$IMGPROTO" = "nfs" ]; then
TEST_DIR="$DRIVER,file.driver=nfs,file.filename=nfs://127.0.0.1/$TEST_DIR"
TEST_IMG=$TEST_DIR_OPTS/t.$IMGFMT
TEST_IMG=$TEST_DIR/t.$IMGFMT
elif [ "$IMGPROTO" = "archipelago" ]; then
TEST_IMG="$DRIVER,file.driver=archipelago,file.volume=:at.$IMGFMT"
else

View File

@ -149,6 +149,7 @@
144 rw auto quick
145 auto quick
146 auto quick
147 auto
148 rw auto quick
149 rw auto sudo
150 rw auto quick

View File

@ -39,6 +39,10 @@ qemu_io_args = [os.environ.get('QEMU_IO_PROG', 'qemu-io')]
if os.environ.get('QEMU_IO_OPTIONS'):
qemu_io_args += os.environ['QEMU_IO_OPTIONS'].strip().split(' ')
qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')]
if os.environ.get('QEMU_NBD_OPTIONS'):
qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
qemu_prog = os.environ.get('QEMU_PROG', 'qemu')
qemu_opts = os.environ.get('QEMU_OPTIONS', '').strip().split(' ')
@ -87,6 +91,10 @@ def qemu_io(*args):
sys.stderr.write('qemu-io received signal %i: %s\n' % (-exitcode, ' '.join(args)))
return subp.communicate()[0]
def qemu_nbd(*args):
'''Run qemu-nbd in daemon mode and return the parent's exit code'''
return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
def compare_images(img1, img2, fmt1=imgfmt, fmt2=imgfmt):
'''Return True if two image files are identical'''
return qemu_img('compare', '-f', fmt1,
@ -132,8 +140,10 @@ def log(msg, filters=[]):
class VM(qtest.QEMUQtestMachine):
'''A QEMU VM'''
def __init__(self):
super(VM, self).__init__(qemu_prog, qemu_opts, test_dir=test_dir,
def __init__(self, path_suffix=''):
name = "qemu%s-%d" % (path_suffix, os.getpid())
super(VM, self).__init__(qemu_prog, qemu_opts, name=name,
test_dir=test_dir,
socket_scm_helper=socket_scm_helper)
if debug:
self._debug = True
@ -212,6 +222,19 @@ class QMPTestCase(unittest.TestCase):
self.fail('invalid index "%s" in path "%s" in "%s"' % (idx, path, str(d)))
return d
def flatten_qmp_object(self, obj, output=None, basestr=''):
if output is None:
output = dict()
if isinstance(obj, list):
for i in range(len(obj)):
self.flatten_qmp_object(obj[i], output, basestr + str(i) + '.')
elif isinstance(obj, dict):
for key in obj:
self.flatten_qmp_object(obj[key], output, basestr + key + '.')
else:
output[basestr[:-1]] = obj # Strip trailing '.'
return output
def assert_qmp_absent(self, d, path):
try:
result = self.dictpath(d, path)
@ -242,6 +265,13 @@ class QMPTestCase(unittest.TestCase):
self.assertTrue(False, "Cannot find %s %s in result:\n%s" % \
(node_name, file_name, result))
def assert_json_filename_equal(self, json_filename, reference):
'''Asserts that the given filename is a json: filename and that its
content is equal to the given reference object'''
self.assertEqual(json_filename[:5], 'json:')
self.assertEqual(self.flatten_qmp_object(json.loads(json_filename[5:])),
self.flatten_qmp_object(reference))
def cancel_and_wait(self, drive='drive0', force=False, resume=False):
'''Cancel a block job and wait for it to finish, returning the event'''
result = self.vm.qmp('block-job-cancel', device=drive, force=force)

View File

@ -60,7 +60,7 @@ static int send_fd(int fd, int fd_to_send)
}
/* Convert string to fd number. */
static int get_fd_num(const char *fd_str)
static int get_fd_num(const char *fd_str, bool silent)
{
int sock;
char *err;
@ -68,12 +68,16 @@ static int get_fd_num(const char *fd_str)
errno = 0;
sock = strtol(fd_str, &err, 10);
if (errno) {
fprintf(stderr, "Failed in strtol for socket fd, reason: %s\n",
strerror(errno));
if (!silent) {
fprintf(stderr, "Failed in strtol for socket fd, reason: %s\n",
strerror(errno));
}
return -1;
}
if (!*fd_str || *err || sock < 0) {
fprintf(stderr, "bad numerical value for socket fd '%s'\n", fd_str);
if (!silent) {
fprintf(stderr, "bad numerical value for socket fd '%s'\n", fd_str);
}
return -1;
}
@ -104,18 +108,21 @@ int main(int argc, char **argv, char **envp)
}
sock = get_fd_num(argv[1]);
sock = get_fd_num(argv[1], false);
if (sock < 0) {
return EXIT_FAILURE;
}
/* Now only open a file in readonly mode for test purpose. If more precise
control is needed, use python script in file operation, which is
supposed to fork and exec this program. */
fd = open(argv[2], O_RDONLY);
fd = get_fd_num(argv[2], true);
if (fd < 0) {
fprintf(stderr, "Failed to open file '%s'\n", argv[2]);
return EXIT_FAILURE;
/* Now only open a file in readonly mode for test purpose. If more
precise control is needed, use python script in file operation, which
is supposed to fork and exec this program. */
fd = open(argv[2], O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open file '%s'\n", argv[2]);
return EXIT_FAILURE;
}
}
ret = send_fd(sock, fd);