666a3af9c8
Start the new generic I/O channel framework by defining a QIOChannel abstract base class. This is designed to feel similar to GLib's GIOChannel, but with the addition of support for using iovecs, qemu error reporting, file descriptor passing, coroutine integration and use of the QOM framework for easier sub-classing. The intention is that anywhere in QEMU that almost anywhere that deals with sockets will use this new I/O infrastructure, so that it becomes trivial to then layer in support for TLS encryption. This will at least include the VNC server, char device backend and migration code. Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
292 lines
7.6 KiB
C
292 lines
7.6 KiB
C
/*
|
|
* QEMU I/O channels
|
|
*
|
|
* Copyright (c) 2015 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "io/channel.h"
|
|
#include "qemu/coroutine.h"
|
|
|
|
bool qio_channel_has_feature(QIOChannel *ioc,
|
|
QIOChannelFeature feature)
|
|
{
|
|
return ioc->features & (1 << feature);
|
|
}
|
|
|
|
|
|
ssize_t qio_channel_readv_full(QIOChannel *ioc,
|
|
const struct iovec *iov,
|
|
size_t niov,
|
|
int **fds,
|
|
size_t *nfds,
|
|
Error **errp)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
|
|
if ((fds || nfds) &&
|
|
!(ioc->features & (1 << QIO_CHANNEL_FEATURE_FD_PASS))) {
|
|
error_setg_errno(errp, EINVAL,
|
|
"Channel does not support file descriptor passing");
|
|
return -1;
|
|
}
|
|
|
|
return klass->io_readv(ioc, iov, niov, fds, nfds, errp);
|
|
}
|
|
|
|
|
|
ssize_t qio_channel_writev_full(QIOChannel *ioc,
|
|
const struct iovec *iov,
|
|
size_t niov,
|
|
int *fds,
|
|
size_t nfds,
|
|
Error **errp)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
|
|
if ((fds || nfds) &&
|
|
!(ioc->features & (1 << QIO_CHANNEL_FEATURE_FD_PASS))) {
|
|
error_setg_errno(errp, EINVAL,
|
|
"Channel does not support file descriptor passing");
|
|
return -1;
|
|
}
|
|
|
|
return klass->io_writev(ioc, iov, niov, fds, nfds, errp);
|
|
}
|
|
|
|
|
|
ssize_t qio_channel_readv(QIOChannel *ioc,
|
|
const struct iovec *iov,
|
|
size_t niov,
|
|
Error **errp)
|
|
{
|
|
return qio_channel_readv_full(ioc, iov, niov, NULL, NULL, errp);
|
|
}
|
|
|
|
|
|
ssize_t qio_channel_writev(QIOChannel *ioc,
|
|
const struct iovec *iov,
|
|
size_t niov,
|
|
Error **errp)
|
|
{
|
|
return qio_channel_writev_full(ioc, iov, niov, NULL, 0, errp);
|
|
}
|
|
|
|
|
|
ssize_t qio_channel_read(QIOChannel *ioc,
|
|
char *buf,
|
|
size_t buflen,
|
|
Error **errp)
|
|
{
|
|
struct iovec iov = { .iov_base = buf, .iov_len = buflen };
|
|
return qio_channel_readv_full(ioc, &iov, 1, NULL, NULL, errp);
|
|
}
|
|
|
|
|
|
ssize_t qio_channel_write(QIOChannel *ioc,
|
|
const char *buf,
|
|
size_t buflen,
|
|
Error **errp)
|
|
{
|
|
struct iovec iov = { .iov_base = (char *)buf, .iov_len = buflen };
|
|
return qio_channel_writev_full(ioc, &iov, 1, NULL, 0, errp);
|
|
}
|
|
|
|
|
|
int qio_channel_set_blocking(QIOChannel *ioc,
|
|
bool enabled,
|
|
Error **errp)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
return klass->io_set_blocking(ioc, enabled, errp);
|
|
}
|
|
|
|
|
|
int qio_channel_close(QIOChannel *ioc,
|
|
Error **errp)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
return klass->io_close(ioc, errp);
|
|
}
|
|
|
|
|
|
GSource *qio_channel_create_watch(QIOChannel *ioc,
|
|
GIOCondition condition)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
return klass->io_create_watch(ioc, condition);
|
|
}
|
|
|
|
|
|
guint qio_channel_add_watch(QIOChannel *ioc,
|
|
GIOCondition condition,
|
|
QIOChannelFunc func,
|
|
gpointer user_data,
|
|
GDestroyNotify notify)
|
|
{
|
|
GSource *source;
|
|
guint id;
|
|
|
|
source = qio_channel_create_watch(ioc, condition);
|
|
|
|
g_source_set_callback(source, (GSourceFunc)func, user_data, notify);
|
|
|
|
id = g_source_attach(source, NULL);
|
|
g_source_unref(source);
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
int qio_channel_shutdown(QIOChannel *ioc,
|
|
QIOChannelShutdown how,
|
|
Error **errp)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
|
|
if (!klass->io_shutdown) {
|
|
error_setg(errp, "Data path shutdown not supported");
|
|
return -1;
|
|
}
|
|
|
|
return klass->io_shutdown(ioc, how, errp);
|
|
}
|
|
|
|
|
|
void qio_channel_set_delay(QIOChannel *ioc,
|
|
bool enabled)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
|
|
if (klass->io_set_delay) {
|
|
klass->io_set_delay(ioc, enabled);
|
|
}
|
|
}
|
|
|
|
|
|
void qio_channel_set_cork(QIOChannel *ioc,
|
|
bool enabled)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
|
|
if (klass->io_set_cork) {
|
|
klass->io_set_cork(ioc, enabled);
|
|
}
|
|
}
|
|
|
|
|
|
off_t qio_channel_io_seek(QIOChannel *ioc,
|
|
off_t offset,
|
|
int whence,
|
|
Error **errp)
|
|
{
|
|
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
|
|
|
if (!klass->io_seek) {
|
|
error_setg(errp, "Channel does not support random access");
|
|
return -1;
|
|
}
|
|
|
|
return klass->io_seek(ioc, offset, whence, errp);
|
|
}
|
|
|
|
|
|
typedef struct QIOChannelYieldData QIOChannelYieldData;
|
|
struct QIOChannelYieldData {
|
|
QIOChannel *ioc;
|
|
Coroutine *co;
|
|
};
|
|
|
|
|
|
static gboolean qio_channel_yield_enter(QIOChannel *ioc,
|
|
GIOCondition condition,
|
|
gpointer opaque)
|
|
{
|
|
QIOChannelYieldData *data = opaque;
|
|
qemu_coroutine_enter(data->co, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void coroutine_fn qio_channel_yield(QIOChannel *ioc,
|
|
GIOCondition condition)
|
|
{
|
|
QIOChannelYieldData data;
|
|
|
|
assert(qemu_in_coroutine());
|
|
data.ioc = ioc;
|
|
data.co = qemu_coroutine_self();
|
|
qio_channel_add_watch(ioc,
|
|
condition,
|
|
qio_channel_yield_enter,
|
|
&data,
|
|
NULL);
|
|
qemu_coroutine_yield();
|
|
}
|
|
|
|
|
|
static gboolean qio_channel_wait_complete(QIOChannel *ioc,
|
|
GIOCondition condition,
|
|
gpointer opaque)
|
|
{
|
|
GMainLoop *loop = opaque;
|
|
|
|
g_main_loop_quit(loop);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void qio_channel_wait(QIOChannel *ioc,
|
|
GIOCondition condition)
|
|
{
|
|
GMainContext *ctxt = g_main_context_new();
|
|
GMainLoop *loop = g_main_loop_new(ctxt, TRUE);
|
|
GSource *source;
|
|
|
|
source = qio_channel_create_watch(ioc, condition);
|
|
|
|
g_source_set_callback(source,
|
|
(GSourceFunc)qio_channel_wait_complete,
|
|
loop,
|
|
NULL);
|
|
|
|
g_source_attach(source, ctxt);
|
|
|
|
g_main_loop_run(loop);
|
|
|
|
g_source_unref(source);
|
|
g_main_loop_unref(loop);
|
|
g_main_context_unref(ctxt);
|
|
}
|
|
|
|
|
|
static const TypeInfo qio_channel_info = {
|
|
.parent = TYPE_OBJECT,
|
|
.name = TYPE_QIO_CHANNEL,
|
|
.instance_size = sizeof(QIOChannel),
|
|
.abstract = true,
|
|
.class_size = sizeof(QIOChannelClass),
|
|
};
|
|
|
|
|
|
static void qio_channel_register_types(void)
|
|
{
|
|
type_register_static(&qio_channel_info);
|
|
}
|
|
|
|
|
|
type_init(qio_channel_register_types);
|