mirror of https://github.com/xemu-project/xemu.git
Merge I/O channels base classes
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCAAGBQJWc/meAAoJEL6G67QVEE/fJqAP/RmN/w1vgb2WtJAxvnz1Ksr8 kl4mw891CXXav9yvP7xP3Eb4SHHi6H+yAnsUEBPSfvgZZbNiIoEV5djI+0n2Rh5m UbLt7icP1SMQ2CSpmk+wFROw7e3kCMGLAIs2HoeR1hRQgxMDLh6j/LnbtvqKMwoM 2ZEw/+sk095GtvzyipSp+N3225DnFy+1Ev4t53GtAE9D34S8FfDKWd6fPUP0oPSZ EIE471qLu0PaacwUbzHClfcmQwUJ5ZuQap/USBEDjpzTd9gZSUv9nnXsInuJfcU5 jPF7iNVeQnkpx9TR+C2ie34hbGaLhQCVPHNrcIxY0QfgwtgXkfyetEfaRS7x55hq GvWycW47Oq7aYJh7yNDW0AXJ7iCTyF+eTw+zqmhosvdyYzHX5FPpLeWOsQajdu/F u7rZq/gsFceF2QKhaXB+tqj1ZLXI47pSnIP1BGwXHKNq6yDrK7yUUOx+v4Tog0X4 1jrlDQZJpRbOh/fnuUlMkdZK3wJErnC+JTW1RrhIbq21yd524YH4EXwFIFY+myAv HhmsETPUQQKVATFarhTFrVkAz9dtwV/sOBfb+bL2DxGeBUnsLLBXUDtiyUBJLXvT 2NZ6pBTbS4leYi5/lbneTYrD+3WwSl6yUSIB/dLpUnDwqg6vLV9/+AVwAulfdCAb fUICwuuIVktHBxGWJpvu =ObZa -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/berrange/tags/pull-io-channel-base-2015-12-18-1' into staging Merge I/O channels base classes # gpg: Signature made Fri 18 Dec 2015 12:18:38 GMT using RSA key ID 15104FDF # gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>" # gpg: aka "Daniel P. Berrange <berrange@redhat.com>" * remotes/berrange/tags/pull-io-channel-base-2015-12-18-1: io: add QIOChannelBuffer class io: add QIOChannelCommand class io: add QIOChannelWebsock class io: add QIOChannelTLS class io: add QIOChannelFile class io: add QIOChannelSocket class io: add QIOTask class for async operations io: add helper module for creating watches on FDs io: add abstract QIOChannel classes Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
67a7084062
MAINTAINERSMakefileMakefile.objsMakefile.targetconfigure
include
io
channel-buffer.hchannel-command.hchannel-file.hchannel-socket.hchannel-tls.hchannel-watch.hchannel-websock.hchannel.htask.h
qemu
io
Makefile.objschannel-buffer.cchannel-command.cchannel-file.cchannel-socket.cchannel-tls.cchannel-watch.cchannel-websock.cchannel.ctask.c
scripts
tests
.gitignoreMakefileio-channel-helpers.cio-channel-helpers.htest-io-channel-buffer.ctest-io-channel-command.ctest-io-channel-file.ctest-io-channel-socket.ctest-io-channel-tls.ctest-io-task.c
trace-eventsutil
|
@ -1243,6 +1243,13 @@ S: Odd fixes
|
|||
F: util/buffer.c
|
||||
F: include/qemu/buffer.h
|
||||
|
||||
I/O Channels
|
||||
M: Daniel P. Berrange <berrange@redhat.com>
|
||||
S: Maintained
|
||||
F: io/
|
||||
F: include/io/
|
||||
F: tests/test-io-*
|
||||
|
||||
Usermode Emulation
|
||||
------------------
|
||||
Overall
|
||||
|
|
2
Makefile
2
Makefile
|
@ -159,6 +159,7 @@ dummy := $(call unnest-vars,, \
|
|||
crypto-obj-y \
|
||||
crypto-aes-obj-y \
|
||||
qom-obj-y \
|
||||
io-obj-y \
|
||||
common-obj-y \
|
||||
common-obj-m)
|
||||
|
||||
|
@ -178,6 +179,7 @@ SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
|
|||
|
||||
$(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
|
||||
$(SOFTMMU_SUBDIR_RULES): $(crypto-obj-y)
|
||||
$(SOFTMMU_SUBDIR_RULES): $(io-obj-y)
|
||||
$(SOFTMMU_SUBDIR_RULES): config-all-devices.mak
|
||||
|
||||
subdir-%:
|
||||
|
|
|
@ -28,6 +28,11 @@ crypto-aes-obj-y = crypto/
|
|||
|
||||
qom-obj-y = qom/
|
||||
|
||||
#######################################################################
|
||||
# io-obj-y is code used by both qemu system emulation and qemu-img
|
||||
|
||||
io-obj-y = io/
|
||||
|
||||
######################################################################
|
||||
# Target independent part of system emulation. The long term path is to
|
||||
# suppress *all* target specific code in case of system emulation, i.e. a
|
||||
|
|
|
@ -176,6 +176,7 @@ dummy := $(call unnest-vars,.., \
|
|||
crypto-obj-y \
|
||||
crypto-aes-obj-y \
|
||||
qom-obj-y \
|
||||
io-obj-y \
|
||||
common-obj-y \
|
||||
common-obj-m)
|
||||
target-obj-y := $(target-obj-y-save)
|
||||
|
@ -185,6 +186,7 @@ all-obj-y += $(qom-obj-y)
|
|||
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y)
|
||||
all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
|
||||
all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
|
||||
all-obj-$(CONFIG_SOFTMMU) += $(io-obj-y)
|
||||
|
||||
$(QEMU_PROG_BUILD): config-devices.mak
|
||||
|
||||
|
|
|
@ -2426,6 +2426,14 @@ else
|
|||
fi
|
||||
|
||||
|
||||
##########################################
|
||||
# getifaddrs (for tests/test-io-channel-socket )
|
||||
|
||||
have_ifaddrs_h=yes
|
||||
if ! check_include "ifaddrs.h" ; then
|
||||
have_ifaddrs_h=no
|
||||
fi
|
||||
|
||||
##########################################
|
||||
# VTE probe
|
||||
|
||||
|
@ -5137,6 +5145,9 @@ fi
|
|||
if test "$tasn1" = "yes" ; then
|
||||
echo "CONFIG_TASN1=y" >> $config_host_mak
|
||||
fi
|
||||
if test "$have_ifaddrs_h" = "yes" ; then
|
||||
echo "HAVE_IFADDRS_H=y" >> $config_host_mak
|
||||
fi
|
||||
if test "$vte" = "yes" ; then
|
||||
echo "CONFIG_VTE=y" >> $config_host_mak
|
||||
echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* QEMU I/O channels memory buffer driver
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_CHANNEL_BUFFER_H__
|
||||
#define QIO_CHANNEL_BUFFER_H__
|
||||
|
||||
#include "io/channel.h"
|
||||
|
||||
#define TYPE_QIO_CHANNEL_BUFFER "qio-channel-buffer"
|
||||
#define QIO_CHANNEL_BUFFER(obj) \
|
||||
OBJECT_CHECK(QIOChannelBuffer, (obj), TYPE_QIO_CHANNEL_BUFFER)
|
||||
|
||||
typedef struct QIOChannelBuffer QIOChannelBuffer;
|
||||
|
||||
/**
|
||||
* QIOChannelBuffer:
|
||||
*
|
||||
* The QIOChannelBuffer object provides a channel implementation
|
||||
* that is able to perform I/O to/from a memory buffer.
|
||||
*
|
||||
*/
|
||||
|
||||
struct QIOChannelBuffer {
|
||||
QIOChannel parent;
|
||||
size_t capacity; /* Total allocated memory */
|
||||
size_t usage; /* Current size of data */
|
||||
size_t offset; /* Offset for future I/O ops */
|
||||
char *data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_buffer_new:
|
||||
* @capacity: the initial buffer capacity to allocate
|
||||
*
|
||||
* Allocate a new buffer which is initially empty
|
||||
*
|
||||
* Returns: the new channel object
|
||||
*/
|
||||
QIOChannelBuffer *
|
||||
qio_channel_buffer_new(size_t capacity);
|
||||
|
||||
#endif /* QIO_CHANNEL_BUFFER_H__ */
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* QEMU I/O channels external command driver
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_CHANNEL_COMMAND_H__
|
||||
#define QIO_CHANNEL_COMMAND_H__
|
||||
|
||||
#include "io/channel.h"
|
||||
|
||||
#define TYPE_QIO_CHANNEL_COMMAND "qio-channel-command"
|
||||
#define QIO_CHANNEL_COMMAND(obj) \
|
||||
OBJECT_CHECK(QIOChannelCommand, (obj), TYPE_QIO_CHANNEL_COMMAND)
|
||||
|
||||
typedef struct QIOChannelCommand QIOChannelCommand;
|
||||
|
||||
|
||||
/**
|
||||
* QIOChannelCommand:
|
||||
*
|
||||
* The QIOChannelCommand class provides a channel implementation
|
||||
* that can transport data with an externally running command
|
||||
* via its stdio streams.
|
||||
*/
|
||||
|
||||
struct QIOChannelCommand {
|
||||
QIOChannel parent;
|
||||
int writefd;
|
||||
int readfd;
|
||||
pid_t pid;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_command_new_pid:
|
||||
* @writefd: the FD connected to the command's stdin
|
||||
* @readfd: the FD connected to the command's stdout
|
||||
* @pid: the PID of the running child command
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Create a channel for performing I/O with the
|
||||
* previously spawned command identified by @pid.
|
||||
* The two file descriptors provide the connection
|
||||
* to command's stdio streams, either one or which
|
||||
* may be -1 to indicate that stream is not open.
|
||||
*
|
||||
* The channel will take ownership of the process
|
||||
* @pid and will kill it when closing the channel.
|
||||
* Similarly it will take responsibility for
|
||||
* closing the file descriptors @writefd and @readfd.
|
||||
*
|
||||
* Returns: the command channel object, or NULL on error
|
||||
*/
|
||||
QIOChannelCommand *
|
||||
qio_channel_command_new_pid(int writefd,
|
||||
int readfd,
|
||||
pid_t pid);
|
||||
|
||||
/**
|
||||
* qio_channel_command_new_spawn:
|
||||
* @argv: the NULL terminated list of command arguments
|
||||
* @flags: the I/O mode, one of O_RDONLY, O_WRONLY, O_RDWR
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Create a channel for performing I/O with the
|
||||
* command to be spawned with arguments @argv.
|
||||
*
|
||||
* Returns: the command channel object, or NULL on error
|
||||
*/
|
||||
QIOChannelCommand *
|
||||
qio_channel_command_new_spawn(const char *const argv[],
|
||||
int flags,
|
||||
Error **errp);
|
||||
|
||||
|
||||
#endif /* QIO_CHANNEL_COMMAND_H__ */
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* QEMU I/O channels files driver
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_CHANNEL_FILE_H__
|
||||
#define QIO_CHANNEL_FILE_H__
|
||||
|
||||
#include "io/channel.h"
|
||||
|
||||
#define TYPE_QIO_CHANNEL_FILE "qio-channel-file"
|
||||
#define QIO_CHANNEL_FILE(obj) \
|
||||
OBJECT_CHECK(QIOChannelFile, (obj), TYPE_QIO_CHANNEL_FILE)
|
||||
|
||||
typedef struct QIOChannelFile QIOChannelFile;
|
||||
|
||||
/**
|
||||
* QIOChannelFile:
|
||||
*
|
||||
* The QIOChannelFile object provides a channel implementation
|
||||
* that is able to perform I/O on block devices, character
|
||||
* devices, FIFOs, pipes and plain files. While it is technically
|
||||
* able to work on sockets too on the UNIX platform, this is not
|
||||
* portable to Windows and lacks some extra sockets specific
|
||||
* functionality. So the QIOChannelSocket object is recommended
|
||||
* for that use case.
|
||||
*
|
||||
*/
|
||||
|
||||
struct QIOChannelFile {
|
||||
QIOChannel parent;
|
||||
int fd;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_file_new_fd:
|
||||
* @fd: the file descriptor
|
||||
*
|
||||
* Create a new IO channel object for a file represented
|
||||
* by the @fd parameter. @fd can be associated with a
|
||||
* block device, character device, fifo, pipe, or a
|
||||
* regular file. For sockets, the QIOChannelSocket class
|
||||
* should be used instead, as this provides greater
|
||||
* functionality and cross platform portability.
|
||||
*
|
||||
* The channel will own the passed in file descriptor
|
||||
* and will take responsibility for closing it, so the
|
||||
* caller must not close it. If appropriate the caller
|
||||
* should dup() its FD before opening the channel.
|
||||
*
|
||||
* Returns: the new channel object
|
||||
*/
|
||||
QIOChannelFile *
|
||||
qio_channel_file_new_fd(int fd);
|
||||
|
||||
/**
|
||||
* qio_channel_file_new_path:
|
||||
* @fd: the file descriptor
|
||||
* @flags: the open flags (O_RDONLY|O_WRONLY|O_RDWR, etc)
|
||||
* @mode: the file creation mode if O_WRONLY is set in @flags
|
||||
* @errp: pointer to initialized error object
|
||||
*
|
||||
* Create a new IO channel object for a file represented
|
||||
* by the @path parameter. @path can point to any
|
||||
* type of file on which sequential I/O can be
|
||||
* performed, whether it be a plain file, character
|
||||
* device or block device.
|
||||
*
|
||||
* Returns: the new channel object
|
||||
*/
|
||||
QIOChannelFile *
|
||||
qio_channel_file_new_path(const char *path,
|
||||
int flags,
|
||||
mode_t mode,
|
||||
Error **errp);
|
||||
|
||||
#endif /* QIO_CHANNEL_FILE_H__ */
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* QEMU I/O channels sockets driver
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_CHANNEL_SOCKET_H__
|
||||
#define QIO_CHANNEL_SOCKET_H__
|
||||
|
||||
#include "io/channel.h"
|
||||
#include "io/task.h"
|
||||
#include "qemu/sockets.h"
|
||||
|
||||
#define TYPE_QIO_CHANNEL_SOCKET "qio-channel-socket"
|
||||
#define QIO_CHANNEL_SOCKET(obj) \
|
||||
OBJECT_CHECK(QIOChannelSocket, (obj), TYPE_QIO_CHANNEL_SOCKET)
|
||||
|
||||
typedef struct QIOChannelSocket QIOChannelSocket;
|
||||
|
||||
/**
|
||||
* QIOChannelSocket:
|
||||
*
|
||||
* The QIOChannelSocket class provides a channel implementation
|
||||
* that can transport data over a UNIX socket or TCP socket.
|
||||
* Beyond the core channel API, it also provides functionality
|
||||
* for accepting client connections, tuning some socket
|
||||
* parameters and getting socket address strings.
|
||||
*/
|
||||
|
||||
struct QIOChannelSocket {
|
||||
QIOChannel parent;
|
||||
int fd;
|
||||
struct sockaddr_storage localAddr;
|
||||
socklen_t localAddrLen;
|
||||
struct sockaddr_storage remoteAddr;
|
||||
socklen_t remoteAddrLen;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_socket_new:
|
||||
*
|
||||
* Create a channel for performing I/O on a socket
|
||||
* connection, that is initially closed. After
|
||||
* creating the socket, it must be setup as a client
|
||||
* connection or server.
|
||||
*
|
||||
* Returns: the socket channel object
|
||||
*/
|
||||
QIOChannelSocket *
|
||||
qio_channel_socket_new(void);
|
||||
|
||||
/**
|
||||
* qio_channel_socket_new_fd:
|
||||
* @fd: the socket file descriptor
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Create a channel for performing I/O on the socket
|
||||
* connection represented by the file descriptor @fd.
|
||||
*
|
||||
* Returns: the socket channel object, or NULL on error
|
||||
*/
|
||||
QIOChannelSocket *
|
||||
qio_channel_socket_new_fd(int fd,
|
||||
Error **errp);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_socket_connect_sync:
|
||||
* @ioc: the socket channel object
|
||||
* @addr: the address to connect to
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Attempt to connect to the address @addr. This method
|
||||
* will run in the foreground so the caller will not regain
|
||||
* execution control until the connection is established or
|
||||
* an error occurs.
|
||||
*/
|
||||
int qio_channel_socket_connect_sync(QIOChannelSocket *ioc,
|
||||
SocketAddress *addr,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_socket_connect_async:
|
||||
* @ioc: the socket channel object
|
||||
* @addr: the address to connect to
|
||||
* @callback: the function to invoke on completion
|
||||
* @opaque: user data to pass to @callback
|
||||
* @destroy: the function to free @opaque
|
||||
*
|
||||
* Attempt to connect to the address @addr. This method
|
||||
* will run in the background so the caller will regain
|
||||
* execution control immediately. The function @callback
|
||||
* will be invoked on completion or failure.
|
||||
*/
|
||||
void qio_channel_socket_connect_async(QIOChannelSocket *ioc,
|
||||
SocketAddress *addr,
|
||||
QIOTaskFunc callback,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_socket_listen_sync:
|
||||
* @ioc: the socket channel object
|
||||
* @addr: the address to listen to
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Attempt to listen to the address @addr. This method
|
||||
* will run in the foreground so the caller will not regain
|
||||
* execution control until the connection is established or
|
||||
* an error occurs.
|
||||
*/
|
||||
int qio_channel_socket_listen_sync(QIOChannelSocket *ioc,
|
||||
SocketAddress *addr,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_socket_listen_async:
|
||||
* @ioc: the socket channel object
|
||||
* @addr: the address to listen to
|
||||
* @callback: the function to invoke on completion
|
||||
* @opaque: user data to pass to @callback
|
||||
* @destroy: the function to free @opaque
|
||||
*
|
||||
* Attempt to listen to the address @addr. This method
|
||||
* will run in the background so the caller will regain
|
||||
* execution control immediately. The function @callback
|
||||
* will be invoked on completion or failure.
|
||||
*/
|
||||
void qio_channel_socket_listen_async(QIOChannelSocket *ioc,
|
||||
SocketAddress *addr,
|
||||
QIOTaskFunc callback,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_socket_dgram_sync:
|
||||
* @ioc: the socket channel object
|
||||
* @localAddr: the address to local bind address
|
||||
* @remoteAddr: the address to remote peer address
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Attempt to initialize a datagram socket bound to
|
||||
* @localAddr and communicating with peer @remoteAddr.
|
||||
* This method will run in the foreground so the caller
|
||||
* will not regain execution control until the socket
|
||||
* is established or an error occurs.
|
||||
*/
|
||||
int qio_channel_socket_dgram_sync(QIOChannelSocket *ioc,
|
||||
SocketAddress *localAddr,
|
||||
SocketAddress *remoteAddr,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_socket_dgram_async:
|
||||
* @ioc: the socket channel object
|
||||
* @localAddr: the address to local bind address
|
||||
* @remoteAddr: the address to remote peer address
|
||||
* @callback: the function to invoke on completion
|
||||
* @opaque: user data to pass to @callback
|
||||
* @destroy: the function to free @opaque
|
||||
*
|
||||
* Attempt to initialize a datagram socket bound to
|
||||
* @localAddr and communicating with peer @remoteAddr.
|
||||
* This method will run in the background so the caller
|
||||
* will regain execution control immediately. The function
|
||||
* @callback will be invoked on completion or failure.
|
||||
*/
|
||||
void qio_channel_socket_dgram_async(QIOChannelSocket *ioc,
|
||||
SocketAddress *localAddr,
|
||||
SocketAddress *remoteAddr,
|
||||
QIOTaskFunc callback,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_socket_get_local_address:
|
||||
* @ioc: the socket channel object
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Get the string representation of the local socket
|
||||
* address. A pointer to the allocated address information
|
||||
* struct will be returned, which the caller is required to
|
||||
* release with a call qapi_free_SocketAddress when no
|
||||
* longer required.
|
||||
*
|
||||
* Returns: 0 on success, -1 on error
|
||||
*/
|
||||
SocketAddress *
|
||||
qio_channel_socket_get_local_address(QIOChannelSocket *ioc,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_socket_get_remote_address:
|
||||
* @ioc: the socket channel object
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Get the string representation of the local socket
|
||||
* address. A pointer to the allocated address information
|
||||
* struct will be returned, which the caller is required to
|
||||
* release with a call qapi_free_SocketAddress when no
|
||||
* longer required.
|
||||
*
|
||||
* Returns: the socket address struct, or NULL on error
|
||||
*/
|
||||
SocketAddress *
|
||||
qio_channel_socket_get_remote_address(QIOChannelSocket *ioc,
|
||||
Error **errp);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_socket_accept:
|
||||
* @ioc: the socket channel object
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* If the socket represents a server, then this accepts
|
||||
* a new client connection. The returned channel will
|
||||
* represent the connected client socket.
|
||||
*
|
||||
* Returns: the new client channel, or NULL on error
|
||||
*/
|
||||
QIOChannelSocket *
|
||||
qio_channel_socket_accept(QIOChannelSocket *ioc,
|
||||
Error **errp);
|
||||
|
||||
|
||||
#endif /* QIO_CHANNEL_SOCKET_H__ */
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* QEMU I/O channels TLS driver
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_CHANNEL_TLS_H__
|
||||
#define QIO_CHANNEL_TLS_H__
|
||||
|
||||
#include "io/channel.h"
|
||||
#include "io/task.h"
|
||||
#include "crypto/tlssession.h"
|
||||
|
||||
#define TYPE_QIO_CHANNEL_TLS "qio-channel-tls"
|
||||
#define QIO_CHANNEL_TLS(obj) \
|
||||
OBJECT_CHECK(QIOChannelTLS, (obj), TYPE_QIO_CHANNEL_TLS)
|
||||
|
||||
typedef struct QIOChannelTLS QIOChannelTLS;
|
||||
|
||||
/**
|
||||
* QIOChannelTLS
|
||||
*
|
||||
* The QIOChannelTLS class provides a channel wrapper which
|
||||
* can transparently run the TLS encryption protocol. It is
|
||||
* usually used over a TCP socket, but there is actually no
|
||||
* technical restriction on which type of master channel is
|
||||
* used as the transport.
|
||||
*
|
||||
* This channel object is capable of running as either a
|
||||
* TLS server or TLS client.
|
||||
*/
|
||||
|
||||
struct QIOChannelTLS {
|
||||
QIOChannel parent;
|
||||
QIOChannel *master;
|
||||
QCryptoTLSSession *session;
|
||||
};
|
||||
|
||||
/**
|
||||
* qio_channel_tls_new_server:
|
||||
* @master: the underlying channel object
|
||||
* @creds: the credentials to use for TLS handshake
|
||||
* @aclname: the access control list for validating clients
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Create a new TLS channel that runs the server side of
|
||||
* a TLS session. The TLS session handshake will use the
|
||||
* credentials provided in @creds. If the @aclname parameter
|
||||
* is non-NULL, then the client will have to provide
|
||||
* credentials (ie a x509 client certificate) which will
|
||||
* then be validated against the ACL.
|
||||
*
|
||||
* After creating the channel, it is mandatory to call
|
||||
* the qio_channel_tls_handshake() method before attempting
|
||||
* todo any I/O on the channel.
|
||||
*
|
||||
* Once the handshake has completed, all I/O should be done
|
||||
* via the new TLS channel object and not the original
|
||||
* master channel
|
||||
*
|
||||
* Returns: the new TLS channel object, or NULL
|
||||
*/
|
||||
QIOChannelTLS *
|
||||
qio_channel_tls_new_server(QIOChannel *master,
|
||||
QCryptoTLSCreds *creds,
|
||||
const char *aclname,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_tls_new_client:
|
||||
* @master: the underlying channel object
|
||||
* @creds: the credentials to use for TLS handshake
|
||||
* @hostname: the user specified server hostname
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Create a new TLS channel that runs the client side of
|
||||
* a TLS session. The TLS session handshake will use the
|
||||
* credentials provided in @creds. The @hostname parameter
|
||||
* should provide the user specified hostname of the server
|
||||
* and will be validated against the server's credentials
|
||||
* (ie CommonName of the x509 certificate)
|
||||
*
|
||||
* After creating the channel, it is mandatory to call
|
||||
* the qio_channel_tls_handshake() method before attempting
|
||||
* todo any I/O on the channel.
|
||||
*
|
||||
* Once the handshake has completed, all I/O should be done
|
||||
* via the new TLS channel object and not the original
|
||||
* master channel
|
||||
*
|
||||
* Returns: the new TLS channel object, or NULL
|
||||
*/
|
||||
QIOChannelTLS *
|
||||
qio_channel_tls_new_client(QIOChannel *master,
|
||||
QCryptoTLSCreds *creds,
|
||||
const char *hostname,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_tls_handshake:
|
||||
* @ioc: the TLS channel object
|
||||
* @func: the callback to invoke when completed
|
||||
* @opaque: opaque data to pass to @func
|
||||
* @destroy: optional callback to free @opaque
|
||||
*
|
||||
* Perform the TLS session handshake. This method
|
||||
* will return immediately and the handshake will
|
||||
* continue in the background, provided the main
|
||||
* loop is running. When the handshake is complete,
|
||||
* or fails, the @func callback will be invoked.
|
||||
*/
|
||||
void qio_channel_tls_handshake(QIOChannelTLS *ioc,
|
||||
QIOTaskFunc func,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
/**
|
||||
* qio_channel_tls_get_session:
|
||||
* @ioc: the TLS channel object
|
||||
*
|
||||
* Get the TLS session used by the channel.
|
||||
*
|
||||
* Returns: the TLS session
|
||||
*/
|
||||
QCryptoTLSSession *
|
||||
qio_channel_tls_get_session(QIOChannelTLS *ioc);
|
||||
|
||||
#endif /* QIO_CHANNEL_TLS_H__ */
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* QEMU I/O channels watch helper APIs
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_CHANNEL_WATCH_H__
|
||||
#define QIO_CHANNEL_WATCH_H__
|
||||
|
||||
#include "io/channel.h"
|
||||
|
||||
/*
|
||||
* This module provides helper functions that will be needed by
|
||||
* the various QIOChannel implementations, for creating watches
|
||||
* on file descriptors / sockets
|
||||
*/
|
||||
|
||||
/**
|
||||
* qio_channel_create_fd_watch:
|
||||
* @ioc: the channel object
|
||||
* @fd: the file descriptor
|
||||
* @condition: the I/O condition
|
||||
*
|
||||
* Create a new main loop source that is able to
|
||||
* monitor the file descriptor @fd for the
|
||||
* I/O conditions in @condition. This is able
|
||||
* monitor block devices, character devices,
|
||||
* sockets, pipes but not plain files.
|
||||
*
|
||||
* Returns: the new main loop source
|
||||
*/
|
||||
GSource *qio_channel_create_fd_watch(QIOChannel *ioc,
|
||||
int fd,
|
||||
GIOCondition condition);
|
||||
|
||||
/**
|
||||
* qio_channel_create_fd_pair_watch:
|
||||
* @ioc: the channel object
|
||||
* @fdread: the file descriptor for reading
|
||||
* @fdwrite: the file descriptor for writing
|
||||
* @condition: the I/O condition
|
||||
*
|
||||
* Create a new main loop source that is able to
|
||||
* monitor the pair of file descriptors @fdread
|
||||
* and @fdwrite for the I/O conditions in @condition.
|
||||
* This is intended for monitoring unidirectional
|
||||
* file descriptors such as pipes, where a pair
|
||||
* of descriptors is required for bidirectional
|
||||
* I/O
|
||||
*
|
||||
* Returns: the new main loop source
|
||||
*/
|
||||
GSource *qio_channel_create_fd_pair_watch(QIOChannel *ioc,
|
||||
int fdread,
|
||||
int fdwrite,
|
||||
GIOCondition condition);
|
||||
|
||||
#endif /* QIO_CHANNEL_WATCH_H__ */
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* QEMU I/O channels driver websockets
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_CHANNEL_WEBSOCK_H__
|
||||
#define QIO_CHANNEL_WEBSOCK_H__
|
||||
|
||||
#include "io/channel.h"
|
||||
#include "qemu/buffer.h"
|
||||
#include "io/task.h"
|
||||
|
||||
#define TYPE_QIO_CHANNEL_WEBSOCK "qio-channel-websock"
|
||||
#define QIO_CHANNEL_WEBSOCK(obj) \
|
||||
OBJECT_CHECK(QIOChannelWebsock, (obj), TYPE_QIO_CHANNEL_WEBSOCK)
|
||||
|
||||
typedef struct QIOChannelWebsock QIOChannelWebsock;
|
||||
typedef union QIOChannelWebsockMask QIOChannelWebsockMask;
|
||||
|
||||
union QIOChannelWebsockMask {
|
||||
char c[4];
|
||||
uint32_t u;
|
||||
};
|
||||
|
||||
/**
|
||||
* QIOChannelWebsock
|
||||
*
|
||||
* The QIOChannelWebsock class provides a channel wrapper which
|
||||
* can transparently run the HTTP websockets protocol. This is
|
||||
* usually used over a TCP socket, but there is actually no
|
||||
* technical restriction on which type of master channel is
|
||||
* used as the transport.
|
||||
*
|
||||
* This channel object is currently only capable of running as
|
||||
* a websocket server and is a pretty crude implementation
|
||||
* of it, not supporting the full websockets protocol feature
|
||||
* set. It is sufficient to use with a simple websockets
|
||||
* client for encapsulating VNC for noVNC in-browser client.
|
||||
*/
|
||||
|
||||
struct QIOChannelWebsock {
|
||||
QIOChannel parent;
|
||||
QIOChannel *master;
|
||||
Buffer encinput;
|
||||
Buffer encoutput;
|
||||
Buffer rawinput;
|
||||
Buffer rawoutput;
|
||||
size_t payload_remain;
|
||||
QIOChannelWebsockMask mask;
|
||||
guint io_tag;
|
||||
Error *io_err;
|
||||
gboolean io_eof;
|
||||
};
|
||||
|
||||
/**
|
||||
* qio_channel_websock_new_server:
|
||||
* @master: the underlying channel object
|
||||
*
|
||||
* Create a new websockets channel that runs the server
|
||||
* side of the protocol.
|
||||
*
|
||||
* After creating the channel, it is mandatory to call
|
||||
* the qio_channel_websock_handshake() method before attempting
|
||||
* todo any I/O on the channel.
|
||||
*
|
||||
* Once the handshake has completed, all I/O should be done
|
||||
* via the new websocket channel object and not the original
|
||||
* master channel
|
||||
*
|
||||
* Returns: the new websockets channel object
|
||||
*/
|
||||
QIOChannelWebsock *
|
||||
qio_channel_websock_new_server(QIOChannel *master);
|
||||
|
||||
/**
|
||||
* qio_channel_websock_handshake:
|
||||
* @ioc: the websocket channel object
|
||||
* @func: the callback to invoke when completed
|
||||
* @opaque: opaque data to pass to @func
|
||||
* @destroy: optional callback to free @opaque
|
||||
*
|
||||
* Perform the websocket handshake. This method
|
||||
* will return immediately and the handshake will
|
||||
* continue in the background, provided the main
|
||||
* loop is running. When the handshake is complete,
|
||||
* or fails, the @func callback will be invoked.
|
||||
*/
|
||||
void qio_channel_websock_handshake(QIOChannelWebsock *ioc,
|
||||
QIOTaskFunc func,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
#endif /* QIO_CHANNEL_WEBSOCK_H__ */
|
|
@ -0,0 +1,502 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_CHANNEL_H__
|
||||
#define QIO_CHANNEL_H__
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_QIO_CHANNEL "qio-channel"
|
||||
#define QIO_CHANNEL(obj) \
|
||||
OBJECT_CHECK(QIOChannel, (obj), TYPE_QIO_CHANNEL)
|
||||
#define QIO_CHANNEL_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(QIOChannelClass, klass, TYPE_QIO_CHANNEL)
|
||||
#define QIO_CHANNEL_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(QIOChannelClass, obj, TYPE_QIO_CHANNEL)
|
||||
|
||||
typedef struct QIOChannel QIOChannel;
|
||||
typedef struct QIOChannelClass QIOChannelClass;
|
||||
|
||||
#define QIO_CHANNEL_ERR_BLOCK -2
|
||||
|
||||
typedef enum QIOChannelFeature QIOChannelFeature;
|
||||
|
||||
enum QIOChannelFeature {
|
||||
QIO_CHANNEL_FEATURE_FD_PASS = (1 << 0),
|
||||
QIO_CHANNEL_FEATURE_SHUTDOWN = (1 << 1),
|
||||
};
|
||||
|
||||
|
||||
typedef enum QIOChannelShutdown QIOChannelShutdown;
|
||||
|
||||
enum QIOChannelShutdown {
|
||||
QIO_CHANNEL_SHUTDOWN_BOTH,
|
||||
QIO_CHANNEL_SHUTDOWN_READ,
|
||||
QIO_CHANNEL_SHUTDOWN_WRITE,
|
||||
};
|
||||
|
||||
typedef gboolean (*QIOChannelFunc)(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer data);
|
||||
|
||||
/**
|
||||
* QIOChannel:
|
||||
*
|
||||
* The QIOChannel defines the core API for a generic I/O channel
|
||||
* class hierarchy. It is inspired by GIOChannel, but has the
|
||||
* following differences
|
||||
*
|
||||
* - Use QOM to properly support arbitrary subclassing
|
||||
* - Support use of iovecs for efficient I/O with multiple blocks
|
||||
* - None of the character set translation, binary data exclusively
|
||||
* - Direct support for QEMU Error object reporting
|
||||
* - File descriptor passing
|
||||
*
|
||||
* This base class is abstract so cannot be instantiated. There
|
||||
* will be subclasses for dealing with sockets, files, and higher
|
||||
* level protocols such as TLS, WebSocket, etc.
|
||||
*/
|
||||
|
||||
struct QIOChannel {
|
||||
Object parent;
|
||||
unsigned int features; /* bitmask of QIOChannelFeatures */
|
||||
};
|
||||
|
||||
/**
|
||||
* QIOChannelClass:
|
||||
*
|
||||
* This class defines the contract that all subclasses
|
||||
* must follow to provide specific channel implementations.
|
||||
* The first five callbacks are mandatory to support, others
|
||||
* provide additional optional features.
|
||||
*
|
||||
* Consult the corresponding public API docs for a description
|
||||
* of the semantics of each callback
|
||||
*/
|
||||
struct QIOChannelClass {
|
||||
ObjectClass parent;
|
||||
|
||||
/* Mandatory callbacks */
|
||||
ssize_t (*io_writev)(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp);
|
||||
ssize_t (*io_readv)(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp);
|
||||
int (*io_close)(QIOChannel *ioc,
|
||||
Error **errp);
|
||||
GSource * (*io_create_watch)(QIOChannel *ioc,
|
||||
GIOCondition condition);
|
||||
int (*io_set_blocking)(QIOChannel *ioc,
|
||||
bool enabled,
|
||||
Error **errp);
|
||||
|
||||
/* Optional callbacks */
|
||||
int (*io_shutdown)(QIOChannel *ioc,
|
||||
QIOChannelShutdown how,
|
||||
Error **errp);
|
||||
void (*io_set_cork)(QIOChannel *ioc,
|
||||
bool enabled);
|
||||
void (*io_set_delay)(QIOChannel *ioc,
|
||||
bool enabled);
|
||||
off_t (*io_seek)(QIOChannel *ioc,
|
||||
off_t offset,
|
||||
int whence,
|
||||
Error **errp);
|
||||
};
|
||||
|
||||
/* General I/O handling functions */
|
||||
|
||||
/**
|
||||
* qio_channel_has_feature:
|
||||
* @ioc: the channel object
|
||||
* @feature: the feature to check support of
|
||||
*
|
||||
* Determine whether the channel implementation supports
|
||||
* the optional feature named in @feature.
|
||||
*
|
||||
* Returns: true if supported, false otherwise.
|
||||
*/
|
||||
bool qio_channel_has_feature(QIOChannel *ioc,
|
||||
QIOChannelFeature feature);
|
||||
|
||||
/**
|
||||
* qio_channel_readv_full:
|
||||
* @ioc: the channel object
|
||||
* @iov: the array of memory regions to read data into
|
||||
* @niov: the length of the @iov array
|
||||
* @fds: pointer to an array that will received file handles
|
||||
* @nfds: pointer filled with number of elements in @fds on return
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Read data from the IO channel, storing it in the
|
||||
* memory regions referenced by @iov. Each element
|
||||
* in the @iov will be fully populated with data
|
||||
* before the next one is used. The @niov parameter
|
||||
* specifies the total number of elements in @iov.
|
||||
*
|
||||
* It is not required for all @iov to be filled with
|
||||
* data. If the channel is in blocking mode, at least
|
||||
* one byte of data will be read, but no more is
|
||||
* guaranteed. If the channel is non-blocking and no
|
||||
* data is available, it will return QIO_CHANNEL_ERR_BLOCK
|
||||
*
|
||||
* If the channel has passed any file descriptors,
|
||||
* the @fds array pointer will be allocated and
|
||||
* the elements filled with the received file
|
||||
* descriptors. The @nfds pointer will be updated
|
||||
* to indicate the size of the @fds array that
|
||||
* was allocated. It is the callers responsibility
|
||||
* to call close() on each file descriptor and to
|
||||
* call g_free() on the array pointer in @fds.
|
||||
*
|
||||
* It is an error to pass a non-NULL @fds parameter
|
||||
* unless qio_channel_has_feature() returns a true
|
||||
* value for the QIO_CHANNEL_FEATURE_FD_PASS constant.
|
||||
*
|
||||
* Returns: the number of bytes read, or -1 on error,
|
||||
* or QIO_CHANNEL_ERR_BLOCK if no data is available
|
||||
* and the channel is non-blocking
|
||||
*/
|
||||
ssize_t qio_channel_readv_full(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_writev_full:
|
||||
* @ioc: the channel object
|
||||
* @iov: the array of memory regions to write data from
|
||||
* @niov: the length of the @iov array
|
||||
* @fds: an array of file handles to send
|
||||
* @nfds: number of file handles in @fds
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Write data to the IO channel, reading it from the
|
||||
* memory regions referenced by @iov. Each element
|
||||
* in the @iov will be fully sent, before the next
|
||||
* one is used. The @niov parameter specifies the
|
||||
* total number of elements in @iov.
|
||||
*
|
||||
* It is not required for all @iov data to be fully
|
||||
* sent. If the channel is in blocking mode, at least
|
||||
* one byte of data will be sent, but no more is
|
||||
* guaranteed. If the channel is non-blocking and no
|
||||
* data can be sent, it will return QIO_CHANNEL_ERR_BLOCK
|
||||
*
|
||||
* If there are file descriptors to send, the @fds
|
||||
* array should be non-NULL and provide the handles.
|
||||
* All file descriptors will be sent if at least one
|
||||
* byte of data was sent.
|
||||
*
|
||||
* It is an error to pass a non-NULL @fds parameter
|
||||
* unless qio_channel_has_feature() returns a true
|
||||
* value for the QIO_CHANNEL_FEATURE_FD_PASS constant.
|
||||
*
|
||||
* Returns: the number of bytes sent, or -1 on error,
|
||||
* or QIO_CHANNEL_ERR_BLOCK if no data is can be sent
|
||||
* and the channel is non-blocking
|
||||
*/
|
||||
ssize_t qio_channel_writev_full(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_readv:
|
||||
* @ioc: the channel object
|
||||
* @iov: the array of memory regions to read data into
|
||||
* @niov: the length of the @iov array
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Behaves as qio_channel_readv_full() but does not support
|
||||
* receiving of file handles.
|
||||
*/
|
||||
ssize_t qio_channel_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_writev:
|
||||
* @ioc: the channel object
|
||||
* @iov: the array of memory regions to write data from
|
||||
* @niov: the length of the @iov array
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Behaves as qio_channel_writev_full() but does not support
|
||||
* sending of file handles.
|
||||
*/
|
||||
ssize_t qio_channel_writev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_readv:
|
||||
* @ioc: the channel object
|
||||
* @buf: the memory region to read data into
|
||||
* @buflen: the length of @buf
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Behaves as qio_channel_readv_full() but does not support
|
||||
* receiving of file handles, and only supports reading into
|
||||
* a single memory region.
|
||||
*/
|
||||
ssize_t qio_channel_read(QIOChannel *ioc,
|
||||
char *buf,
|
||||
size_t buflen,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_writev:
|
||||
* @ioc: the channel object
|
||||
* @buf: the memory regions to send data from
|
||||
* @buflen: the length of @buf
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Behaves as qio_channel_writev_full() but does not support
|
||||
* sending of file handles, and only supports writing from a
|
||||
* single memory region.
|
||||
*/
|
||||
ssize_t qio_channel_write(QIOChannel *ioc,
|
||||
const char *buf,
|
||||
size_t buflen,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_set_blocking:
|
||||
* @ioc: the channel object
|
||||
* @enabled: the blocking flag state
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* If @enabled is true, then the channel is put into
|
||||
* blocking mode, otherwise it will be non-blocking.
|
||||
*
|
||||
* In non-blocking mode, read/write operations may
|
||||
* return QIO_CHANNEL_ERR_BLOCK if they would otherwise
|
||||
* block on I/O
|
||||
*/
|
||||
int qio_channel_set_blocking(QIOChannel *ioc,
|
||||
bool enabled,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_close:
|
||||
* @ioc: the channel object
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Close the channel, flushing any pending I/O
|
||||
*
|
||||
* Returns: 0 on success, -1 on error
|
||||
*/
|
||||
int qio_channel_close(QIOChannel *ioc,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_shutdown:
|
||||
* @ioc: the channel object
|
||||
* @how: the direction to shutdown
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Shutdowns transmission and/or receiving of data
|
||||
* without closing the underlying transport.
|
||||
*
|
||||
* Not all implementations will support this facility,
|
||||
* so may report an error. To avoid errors, the
|
||||
* caller may check for the feature flag
|
||||
* QIO_CHANNEL_FEATURE_SHUTDOWN prior to calling
|
||||
* this method.
|
||||
*
|
||||
* Returns: 0 on success, -1 on error
|
||||
*/
|
||||
int qio_channel_shutdown(QIOChannel *ioc,
|
||||
QIOChannelShutdown how,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_set_delay:
|
||||
* @ioc: the channel object
|
||||
* @enabled: the new flag state
|
||||
*
|
||||
* Controls whether the underlying transport is
|
||||
* permitted to delay writes in order to merge
|
||||
* small packets. If @enabled is true, then the
|
||||
* writes may be delayed in order to opportunistically
|
||||
* merge small packets into larger ones. If @enabled
|
||||
* is false, writes are dispatched immediately with
|
||||
* no delay.
|
||||
*
|
||||
* When @enabled is false, applications may wish to
|
||||
* use the qio_channel_set_cork() method to explicitly
|
||||
* control write merging.
|
||||
*
|
||||
* On channels which are backed by a socket, this
|
||||
* API corresponds to the inverse of TCP_NODELAY flag,
|
||||
* controlling whether the Nagle algorithm is active.
|
||||
*
|
||||
* This setting is merely a hint, so implementations are
|
||||
* free to ignore this without it being considered an
|
||||
* error.
|
||||
*/
|
||||
void qio_channel_set_delay(QIOChannel *ioc,
|
||||
bool enabled);
|
||||
|
||||
/**
|
||||
* qio_channel_set_cork:
|
||||
* @ioc: the channel object
|
||||
* @enabled: the new flag state
|
||||
*
|
||||
* Controls whether the underlying transport is
|
||||
* permitted to dispatch data that is written.
|
||||
* If @enabled is true, then any data written will
|
||||
* be queued in local buffers until @enabled is
|
||||
* set to false once again.
|
||||
*
|
||||
* This feature is typically used when the automatic
|
||||
* write coalescing facility is disabled via the
|
||||
* qio_channel_set_delay() method.
|
||||
*
|
||||
* On channels which are backed by a socket, this
|
||||
* API corresponds to the TCP_CORK flag.
|
||||
*
|
||||
* This setting is merely a hint, so implementations are
|
||||
* free to ignore this without it being considered an
|
||||
* error.
|
||||
*/
|
||||
void qio_channel_set_cork(QIOChannel *ioc,
|
||||
bool enabled);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_seek:
|
||||
* @ioc: the channel object
|
||||
* @offset: the position to seek to, relative to @whence
|
||||
* @whence: one of the (POSIX) SEEK_* constants listed below
|
||||
* @errp: pointer to an uninitialized error object
|
||||
*
|
||||
* Moves the current I/O position within the channel
|
||||
* @ioc, to be @offset. The value of @offset is
|
||||
* interpreted relative to @whence:
|
||||
*
|
||||
* SEEK_SET - the position is set to @offset bytes
|
||||
* SEEK_CUR - the position is moved by @offset bytes
|
||||
* SEEK_END - the position is set to end of the file plus @offset bytes
|
||||
*
|
||||
* Not all implementations will support this facility,
|
||||
* so may report an error.
|
||||
*
|
||||
* Returns: the new position on success, (off_t)-1 on failure
|
||||
*/
|
||||
off_t qio_channel_io_seek(QIOChannel *ioc,
|
||||
off_t offset,
|
||||
int whence,
|
||||
Error **errp);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_create_watch:
|
||||
* @ioc: the channel object
|
||||
* @condition: the I/O condition to monitor
|
||||
*
|
||||
* Create a new main loop source that is used to watch
|
||||
* for the I/O condition @condition. Typically the
|
||||
* qio_channel_add_watch() method would be used instead
|
||||
* of this, since it directly attaches a callback to
|
||||
* the source
|
||||
*
|
||||
* Returns: the new main loop source.
|
||||
*/
|
||||
GSource *qio_channel_create_watch(QIOChannel *ioc,
|
||||
GIOCondition condition);
|
||||
|
||||
/**
|
||||
* qio_channel_add_watch:
|
||||
* @ioc: the channel object
|
||||
* @condition: the I/O condition to monitor
|
||||
* @func: callback to invoke when the source becomes ready
|
||||
* @user_data: opaque data to pass to @func
|
||||
* @notify: callback to free @user_data
|
||||
*
|
||||
* Create a new main loop source that is used to watch
|
||||
* for the I/O condition @condition. The callback @func
|
||||
* will be registered against the source, to be invoked
|
||||
* when the source becomes ready. The optional @user_data
|
||||
* will be passed to @func when it is invoked. The @notify
|
||||
* callback will be used to free @user_data when the
|
||||
* watch is deleted
|
||||
*
|
||||
* The returned source ID can be used with g_source_remove()
|
||||
* to remove and free the source when no longer required.
|
||||
* Alternatively the @func callback can return a FALSE
|
||||
* value.
|
||||
*
|
||||
* Returns: the source ID
|
||||
*/
|
||||
guint qio_channel_add_watch(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
QIOChannelFunc func,
|
||||
gpointer user_data,
|
||||
GDestroyNotify notify);
|
||||
|
||||
|
||||
/**
|
||||
* qio_channel_yield:
|
||||
* @ioc: the channel object
|
||||
* @condition: the I/O condition to wait for
|
||||
*
|
||||
* Yields execution from the current coroutine until
|
||||
* the condition indicated by @condition becomes
|
||||
* available.
|
||||
*
|
||||
* This must only be called from coroutine context
|
||||
*/
|
||||
void qio_channel_yield(QIOChannel *ioc,
|
||||
GIOCondition condition);
|
||||
|
||||
/**
|
||||
* qio_channel_wait:
|
||||
* @ioc: the channel object
|
||||
* @condition: the I/O condition to wait for
|
||||
*
|
||||
* Block execution from the current thread until
|
||||
* the condition indicated by @condition becomes
|
||||
* available.
|
||||
*
|
||||
* This will enter a nested event loop to perform
|
||||
* the wait.
|
||||
*/
|
||||
void qio_channel_wait(QIOChannel *ioc,
|
||||
GIOCondition condition);
|
||||
|
||||
#endif /* QIO_CHANNEL_H__ */
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* QEMU I/O task
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QIO_TASK_H__
|
||||
#define QIO_TASK_H__
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
typedef struct QIOTask QIOTask;
|
||||
|
||||
typedef void (*QIOTaskFunc)(Object *source,
|
||||
Error *err,
|
||||
gpointer opaque);
|
||||
|
||||
typedef int (*QIOTaskWorker)(QIOTask *task,
|
||||
Error **errp,
|
||||
gpointer opaque);
|
||||
|
||||
/**
|
||||
* QIOTask:
|
||||
*
|
||||
* The QIOTask object provides a simple mechanism for reporting
|
||||
* success / failure of long running background operations.
|
||||
*
|
||||
* A object on which the operation is to be performed could have
|
||||
* a public API which accepts a task callback:
|
||||
*
|
||||
* <example>
|
||||
* <title>Task callback function signature</title>
|
||||
* <programlisting>
|
||||
* void myobject_operation(QMyObject *obj,
|
||||
* QIOTaskFunc *func,
|
||||
* gpointer opaque,
|
||||
* GDestroyNotify *notify);
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
* The 'func' parameter is the callback to be invoked, and 'opaque'
|
||||
* is data to pass to it. The optional 'notify' function is used
|
||||
* to free 'opaque' when no longer needed.
|
||||
*
|
||||
* Now, lets say the implementation of this method wants to set
|
||||
* a timer to run once a second checking for completion of some
|
||||
* activity. It would do something like
|
||||
*
|
||||
* <example>
|
||||
* <title>Task callback function implementation</title>
|
||||
* <programlisting>
|
||||
* void myobject_operation(QMyObject *obj,
|
||||
* QIOTaskFunc *func,
|
||||
* gpointer opaque,
|
||||
* GDestroyNotify *notify)
|
||||
* {
|
||||
* QIOTask *task;
|
||||
*
|
||||
* task = qio_task_new(OBJECT(obj), func, opaque, notify);
|
||||
*
|
||||
* g_timeout_add_full(G_PRIORITY_DEFAULT,
|
||||
* 1000,
|
||||
* myobject_operation_timer,
|
||||
* task,
|
||||
* NULL);
|
||||
* }
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
* It could equally have setup a watch on a file descriptor or
|
||||
* created a background thread, or something else entirely.
|
||||
* Notice that the source object is passed to the task, and
|
||||
* QIOTask will hold a reference on that. This ensure that
|
||||
* the QMyObject instance cannot be garbage collected while
|
||||
* the async task is still in progress.
|
||||
*
|
||||
* In this case, myobject_operation_timer will fire after
|
||||
* 3 secs and do
|
||||
*
|
||||
* <example>
|
||||
* <title>Task timer function</title>
|
||||
* <programlisting>
|
||||
* gboolean myobject_operation_timer(gpointer opaque)
|
||||
* {
|
||||
* QIOTask *task = QIO_TASK(opaque);
|
||||
* Error *err;*
|
||||
*
|
||||
* ...check something important...
|
||||
* if (err) {
|
||||
* qio_task_abort(task, err);
|
||||
* error_free(task);
|
||||
* return FALSE;
|
||||
* } else if (...work is completed ...) {
|
||||
* qio_task_complete(task);
|
||||
* return FALSE;
|
||||
* }
|
||||
* ...carry on polling ...
|
||||
* return TRUE;
|
||||
* }
|
||||
* </programlisting>
|
||||
* </example>
|
||||
*
|
||||
* Once this function returns false, object_unref will be called
|
||||
* automatically on the task causing it to be released and the
|
||||
* ref on QMyObject dropped too.
|
||||
*
|
||||
* The QIOTask module can also be used to perform operations
|
||||
* in a background thread context, while still reporting the
|
||||
* results in the main event thread. This allows code which
|
||||
* cannot easily be rewritten to be asychronous (such as DNS
|
||||
* lookups) to be easily run non-blocking. Reporting the
|
||||
* results in the main thread context means that the caller
|
||||
* typically does not need to be concerned about thread
|
||||
* safety wrt the QEMU global mutex.
|
||||
*
|
||||
* For example, the socket_listen() method will block the caller
|
||||
* while DNS lookups take place if given a name, instead of IP
|
||||
* address. The C library often do not provide a practical async
|
||||
* DNS API, so the to get non-blocking DNS lookups in a portable
|
||||
* manner requires use of a thread. So achieve a non-blocking
|
||||
* socket listen using QIOTask would require:
|
||||
*
|
||||
* <example>
|
||||
* static int myobject_listen_worker(QIOTask *task,
|
||||
* Error **errp,
|
||||
* gpointer opaque)
|
||||
* {
|
||||
* QMyObject obj = QMY_OBJECT(qio_task_get_source(task));
|
||||
* SocketAddress *addr = opaque;
|
||||
*
|
||||
* obj->fd = socket_listen(addr, errp);
|
||||
* if (obj->fd < 0) {
|
||||
* return -1;
|
||||
* }
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* void myobject_listen_async(QMyObject *obj,
|
||||
* SocketAddress *addr,
|
||||
* QIOTaskFunc *func,
|
||||
* gpointer opaque,
|
||||
* GDestroyNotify *notify)
|
||||
* {
|
||||
* QIOTask *task;
|
||||
* SocketAddress *addrCopy;
|
||||
*
|
||||
* qapi_copy_SocketAddress(&addrCopy, addr);
|
||||
* task = qio_task_new(OBJECT(obj), func, opaque, notify);
|
||||
*
|
||||
* qio_task_run_in_thread(task, myobject_listen_worker,
|
||||
* addrCopy,
|
||||
* qapi_free_SocketAddress);
|
||||
* }
|
||||
* </example>
|
||||
*
|
||||
* NB, The 'func' callback passed into myobject_listen_async
|
||||
* will be invoked from the main event thread, despite the
|
||||
* actual operation being performed in a different thread.
|
||||
*/
|
||||
|
||||
/**
|
||||
* qio_task_new:
|
||||
* @source: the object on which the operation is invoked
|
||||
* @func: the callback to invoke when the task completes
|
||||
* @opaque: opaque data to pass to @func when invoked
|
||||
* @destroy: optional callback to free @opaque
|
||||
*
|
||||
* Creates a new task struct to track completion of a
|
||||
* background operation running on the object @source.
|
||||
* When the operation completes or fails, the callback
|
||||
* @func will be invoked. The callback can access the
|
||||
* 'err' attribute in the task object to determine if
|
||||
* the operation was successful or not.
|
||||
*
|
||||
* The returned task will be released when one of
|
||||
* qio_task_abort() or qio_task_complete() are invoked.
|
||||
*
|
||||
* Returns: the task struct
|
||||
*/
|
||||
QIOTask *qio_task_new(Object *source,
|
||||
QIOTaskFunc func,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
/**
|
||||
* qio_task_run_in_thread:
|
||||
* @task: the task struct
|
||||
* @worker: the function to invoke in a thread
|
||||
* @opaque: opaque data to pass to @worker
|
||||
* @destroy: function to free @opaque
|
||||
*
|
||||
* Run a task in a background thread. If @worker
|
||||
* returns 0 it will call qio_task_complete() in
|
||||
* the main event thread context. If @worker
|
||||
* returns -1 it will call qio_task_abort() in
|
||||
* the main event thread context.
|
||||
*/
|
||||
void qio_task_run_in_thread(QIOTask *task,
|
||||
QIOTaskWorker worker,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy);
|
||||
|
||||
/**
|
||||
* qio_task_complete:
|
||||
* @task: the task struct
|
||||
*
|
||||
* Mark the operation as succesfully completed
|
||||
* and free the memory for @task.
|
||||
*/
|
||||
void qio_task_complete(QIOTask *task);
|
||||
|
||||
/**
|
||||
* qio_task_abort:
|
||||
* @task: the task struct
|
||||
* @err: the error to record for the operation
|
||||
*
|
||||
* Mark the operation as failed, with @err providing
|
||||
* details about the failure. The @err may be freed
|
||||
* afer the function returns, as the notification
|
||||
* callback is invoked synchronously. The @task will
|
||||
* be freed when this call completes.
|
||||
*/
|
||||
void qio_task_abort(QIOTask *task,
|
||||
Error *err);
|
||||
|
||||
|
||||
/**
|
||||
* qio_task_get_source:
|
||||
* @task: the task struct
|
||||
*
|
||||
* Get the source object associated with the background
|
||||
* task. This returns a new reference to the object,
|
||||
* which the caller must released with object_unref()
|
||||
* when no longer required.
|
||||
*
|
||||
* Returns: the source object
|
||||
*/
|
||||
Object *qio_task_get_source(QIOTask *task);
|
||||
|
||||
#endif /* QIO_TASK_H__ */
|
|
@ -88,6 +88,25 @@ int socket_dgram(SocketAddress *remote, SocketAddress *local, Error **errp);
|
|||
int parse_host_port(struct sockaddr_in *saddr, const char *str);
|
||||
int socket_init(void);
|
||||
|
||||
/**
|
||||
* socket_sockaddr_to_address:
|
||||
* @sa: socket address struct
|
||||
* @salen: size of @sa struct
|
||||
* @errp: pointer to uninitialized error object
|
||||
*
|
||||
* Get the string representation of the socket
|
||||
* address. A pointer to the allocated address information
|
||||
* struct will be returned, which the caller is required to
|
||||
* release with a call qapi_free_SocketAddress when no
|
||||
* longer required.
|
||||
*
|
||||
* Returns: the socket address struct, or NULL on error
|
||||
*/
|
||||
SocketAddress *
|
||||
socket_sockaddr_to_address(struct sockaddr_storage *sa,
|
||||
socklen_t salen,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* socket_local_address:
|
||||
* @fd: the socket file handle
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
io-obj-y = channel.o
|
||||
io-obj-y += channel-buffer.o
|
||||
io-obj-y += channel-command.o
|
||||
io-obj-y += channel-file.o
|
||||
io-obj-y += channel-socket.o
|
||||
io-obj-y += channel-tls.o
|
||||
io-obj-y += channel-watch.o
|
||||
io-obj-y += channel-websock.o
|
||||
io-obj-y += task.o
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* QEMU I/O channels memory buffer driver
|
||||
*
|
||||
* 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-buffer.h"
|
||||
#include "io/channel-watch.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "trace.h"
|
||||
|
||||
QIOChannelBuffer *
|
||||
qio_channel_buffer_new(size_t capacity)
|
||||
{
|
||||
QIOChannelBuffer *ioc;
|
||||
|
||||
ioc = QIO_CHANNEL_BUFFER(object_new(TYPE_QIO_CHANNEL_BUFFER));
|
||||
|
||||
if (capacity) {
|
||||
ioc->data = g_new0(char, capacity);
|
||||
ioc->capacity = capacity;
|
||||
}
|
||||
|
||||
return ioc;
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_buffer_finalize(Object *obj)
|
||||
{
|
||||
QIOChannelBuffer *ioc = QIO_CHANNEL_BUFFER(obj);
|
||||
g_free(ioc->data);
|
||||
ioc->capacity = ioc->usage = ioc->offset = 0;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_buffer_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelBuffer *bioc = QIO_CHANNEL_BUFFER(ioc);
|
||||
ssize_t ret = 0;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < niov; i++) {
|
||||
size_t want = iov[i].iov_len;
|
||||
if (bioc->offset >= bioc->usage) {
|
||||
break;
|
||||
}
|
||||
if ((bioc->offset + want) > bioc->usage) {
|
||||
want = bioc->usage - bioc->offset;
|
||||
}
|
||||
memcpy(iov[i].iov_base, bioc->data + bioc->offset, want);
|
||||
ret += want;
|
||||
bioc->offset += want;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t qio_channel_buffer_writev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelBuffer *bioc = QIO_CHANNEL_BUFFER(ioc);
|
||||
ssize_t ret = 0;
|
||||
size_t i;
|
||||
size_t towrite = 0;
|
||||
|
||||
for (i = 0; i < niov; i++) {
|
||||
towrite += iov[i].iov_len;
|
||||
}
|
||||
|
||||
if ((bioc->offset + towrite) > bioc->capacity) {
|
||||
bioc->capacity = bioc->offset + towrite;
|
||||
bioc->data = g_realloc(bioc->data, bioc->capacity);
|
||||
}
|
||||
|
||||
if (bioc->offset > bioc->usage) {
|
||||
memset(bioc->data, 0, bioc->offset - bioc->usage);
|
||||
bioc->usage = bioc->offset;
|
||||
}
|
||||
|
||||
for (i = 0; i < niov; i++) {
|
||||
memcpy(bioc->data + bioc->usage,
|
||||
iov[i].iov_base,
|
||||
iov[i].iov_len);
|
||||
bioc->usage += iov[i].iov_len;
|
||||
bioc->offset += iov[i].iov_len;
|
||||
ret += iov[i].iov_len;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qio_channel_buffer_set_blocking(QIOChannel *ioc G_GNUC_UNUSED,
|
||||
bool enabled G_GNUC_UNUSED,
|
||||
Error **errp G_GNUC_UNUSED)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static off_t qio_channel_buffer_seek(QIOChannel *ioc,
|
||||
off_t offset,
|
||||
int whence,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelBuffer *bioc = QIO_CHANNEL_BUFFER(ioc);
|
||||
|
||||
bioc->offset = offset;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
static int qio_channel_buffer_close(QIOChannel *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelBuffer *bioc = QIO_CHANNEL_BUFFER(ioc);
|
||||
|
||||
g_free(bioc->data);
|
||||
bioc->capacity = bioc->usage = bioc->offset = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
typedef struct QIOChannelBufferSource QIOChannelBufferSource;
|
||||
struct QIOChannelBufferSource {
|
||||
GSource parent;
|
||||
QIOChannelBuffer *bioc;
|
||||
GIOCondition condition;
|
||||
};
|
||||
|
||||
static gboolean
|
||||
qio_channel_buffer_source_prepare(GSource *source,
|
||||
gint *timeout)
|
||||
{
|
||||
QIOChannelBufferSource *bsource = (QIOChannelBufferSource *)source;
|
||||
|
||||
*timeout = -1;
|
||||
|
||||
return (G_IO_IN | G_IO_OUT) & bsource->condition;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
qio_channel_buffer_source_check(GSource *source)
|
||||
{
|
||||
QIOChannelBufferSource *bsource = (QIOChannelBufferSource *)source;
|
||||
|
||||
return (G_IO_IN | G_IO_OUT) & bsource->condition;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
qio_channel_buffer_source_dispatch(GSource *source,
|
||||
GSourceFunc callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
QIOChannelFunc func = (QIOChannelFunc)callback;
|
||||
QIOChannelBufferSource *bsource = (QIOChannelBufferSource *)source;
|
||||
|
||||
return (*func)(QIO_CHANNEL(bsource->bioc),
|
||||
((G_IO_IN | G_IO_OUT) & bsource->condition),
|
||||
user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
qio_channel_buffer_source_finalize(GSource *source)
|
||||
{
|
||||
QIOChannelBufferSource *ssource = (QIOChannelBufferSource *)source;
|
||||
|
||||
object_unref(OBJECT(ssource->bioc));
|
||||
}
|
||||
|
||||
GSourceFuncs qio_channel_buffer_source_funcs = {
|
||||
qio_channel_buffer_source_prepare,
|
||||
qio_channel_buffer_source_check,
|
||||
qio_channel_buffer_source_dispatch,
|
||||
qio_channel_buffer_source_finalize
|
||||
};
|
||||
|
||||
static GSource *qio_channel_buffer_create_watch(QIOChannel *ioc,
|
||||
GIOCondition condition)
|
||||
{
|
||||
QIOChannelBuffer *bioc = QIO_CHANNEL_BUFFER(ioc);
|
||||
QIOChannelBufferSource *ssource;
|
||||
GSource *source;
|
||||
|
||||
source = g_source_new(&qio_channel_buffer_source_funcs,
|
||||
sizeof(QIOChannelBufferSource));
|
||||
ssource = (QIOChannelBufferSource *)source;
|
||||
|
||||
ssource->bioc = bioc;
|
||||
object_ref(OBJECT(bioc));
|
||||
|
||||
ssource->condition = condition;
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_buffer_class_init(ObjectClass *klass,
|
||||
void *class_data G_GNUC_UNUSED)
|
||||
{
|
||||
QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
|
||||
|
||||
ioc_klass->io_writev = qio_channel_buffer_writev;
|
||||
ioc_klass->io_readv = qio_channel_buffer_readv;
|
||||
ioc_klass->io_set_blocking = qio_channel_buffer_set_blocking;
|
||||
ioc_klass->io_seek = qio_channel_buffer_seek;
|
||||
ioc_klass->io_close = qio_channel_buffer_close;
|
||||
ioc_klass->io_create_watch = qio_channel_buffer_create_watch;
|
||||
}
|
||||
|
||||
static const TypeInfo qio_channel_buffer_info = {
|
||||
.parent = TYPE_QIO_CHANNEL,
|
||||
.name = TYPE_QIO_CHANNEL_BUFFER,
|
||||
.instance_size = sizeof(QIOChannelBuffer),
|
||||
.instance_finalize = qio_channel_buffer_finalize,
|
||||
.class_init = qio_channel_buffer_class_init,
|
||||
};
|
||||
|
||||
static void qio_channel_buffer_register_types(void)
|
||||
{
|
||||
type_register_static(&qio_channel_buffer_info);
|
||||
}
|
||||
|
||||
type_init(qio_channel_buffer_register_types);
|
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* QEMU I/O channels external command driver
|
||||
*
|
||||
* 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-command.h"
|
||||
#include "io/channel-watch.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
QIOChannelCommand *
|
||||
qio_channel_command_new_pid(int writefd,
|
||||
int readfd,
|
||||
pid_t pid)
|
||||
{
|
||||
QIOChannelCommand *ioc;
|
||||
|
||||
ioc = QIO_CHANNEL_COMMAND(object_new(TYPE_QIO_CHANNEL_COMMAND));
|
||||
|
||||
ioc->readfd = readfd;
|
||||
ioc->writefd = writefd;
|
||||
ioc->pid = pid;
|
||||
|
||||
trace_qio_channel_command_new_pid(ioc, writefd, readfd, pid);
|
||||
return ioc;
|
||||
}
|
||||
|
||||
|
||||
#ifndef WIN32
|
||||
QIOChannelCommand *
|
||||
qio_channel_command_new_spawn(const char *const argv[],
|
||||
int flags,
|
||||
Error **errp)
|
||||
{
|
||||
pid_t pid = -1;
|
||||
int stdinfd[2] = { -1, -1 };
|
||||
int stdoutfd[2] = { -1, -1 };
|
||||
int devnull = -1;
|
||||
bool stdinnull = false, stdoutnull = false;
|
||||
QIOChannelCommand *ioc;
|
||||
|
||||
flags = flags & O_ACCMODE;
|
||||
|
||||
if (flags == O_RDONLY) {
|
||||
stdinnull = true;
|
||||
}
|
||||
if (flags == O_WRONLY) {
|
||||
stdoutnull = true;
|
||||
}
|
||||
|
||||
if (stdinnull || stdoutnull) {
|
||||
devnull = open("/dev/null", O_RDWR);
|
||||
if (!devnull) {
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to open /dev/null");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!stdinnull && pipe(stdinfd) < 0) ||
|
||||
(!stdoutnull && pipe(stdoutfd) < 0)) {
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to open pipe");
|
||||
goto error;
|
||||
}
|
||||
|
||||
pid = qemu_fork(errp);
|
||||
if (pid < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pid == 0) { /* child */
|
||||
dup2(stdinnull ? devnull : stdinfd[0], STDIN_FILENO);
|
||||
dup2(stdoutnull ? devnull : stdoutfd[1], STDOUT_FILENO);
|
||||
/* Leave stderr connected to qemu's stderr */
|
||||
|
||||
if (!stdinnull) {
|
||||
close(stdinfd[0]);
|
||||
close(stdinfd[1]);
|
||||
}
|
||||
if (!stdoutnull) {
|
||||
close(stdoutfd[0]);
|
||||
close(stdoutfd[1]);
|
||||
}
|
||||
|
||||
execv(argv[0], (char * const *)argv);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
if (!stdinnull) {
|
||||
close(stdinfd[0]);
|
||||
}
|
||||
if (!stdoutnull) {
|
||||
close(stdoutfd[1]);
|
||||
}
|
||||
|
||||
ioc = qio_channel_command_new_pid(stdinnull ? devnull : stdinfd[1],
|
||||
stdoutnull ? devnull : stdoutfd[0],
|
||||
pid);
|
||||
trace_qio_channel_command_new_spawn(ioc, argv[0], flags);
|
||||
return ioc;
|
||||
|
||||
error:
|
||||
if (stdinfd[0] != -1) {
|
||||
close(stdinfd[0]);
|
||||
}
|
||||
if (stdinfd[1] != -1) {
|
||||
close(stdinfd[1]);
|
||||
}
|
||||
if (stdoutfd[0] != -1) {
|
||||
close(stdoutfd[0]);
|
||||
}
|
||||
if (stdoutfd[1] != -1) {
|
||||
close(stdoutfd[1]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#else /* WIN32 */
|
||||
QIOChannelCommand *
|
||||
qio_channel_command_new_spawn(const char *const argv[],
|
||||
int flags,
|
||||
Error **errp)
|
||||
{
|
||||
error_setg_errno(errp, ENOSYS,
|
||||
"Command spawn not supported on this platform");
|
||||
return NULL;
|
||||
}
|
||||
#endif /* WIN32 */
|
||||
|
||||
#ifndef WIN32
|
||||
static int qio_channel_command_abort(QIOChannelCommand *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
pid_t ret;
|
||||
int status;
|
||||
int step = 0;
|
||||
|
||||
/* See if intermediate process has exited; if not, try a nice
|
||||
* SIGTERM followed by a more severe SIGKILL.
|
||||
*/
|
||||
rewait:
|
||||
trace_qio_channel_command_abort(ioc, ioc->pid);
|
||||
ret = waitpid(ioc->pid, &status, WNOHANG);
|
||||
trace_qio_channel_command_wait(ioc, ioc->pid, ret, status);
|
||||
if (ret == (pid_t)-1) {
|
||||
if (errno == EINTR) {
|
||||
goto rewait;
|
||||
} else {
|
||||
error_setg_errno(errp, errno,
|
||||
"Cannot wait on pid %llu",
|
||||
(unsigned long long)ioc->pid);
|
||||
return -1;
|
||||
}
|
||||
} else if (ret == 0) {
|
||||
if (step == 0) {
|
||||
kill(ioc->pid, SIGTERM);
|
||||
} else if (step == 1) {
|
||||
kill(ioc->pid, SIGKILL);
|
||||
} else {
|
||||
error_setg(errp,
|
||||
"Process %llu refused to die",
|
||||
(unsigned long long)ioc->pid);
|
||||
return -1;
|
||||
}
|
||||
usleep(10 * 1000);
|
||||
goto rewait;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* ! WIN32 */
|
||||
|
||||
|
||||
static void qio_channel_command_init(Object *obj)
|
||||
{
|
||||
QIOChannelCommand *ioc = QIO_CHANNEL_COMMAND(obj);
|
||||
ioc->readfd = -1;
|
||||
ioc->writefd = -1;
|
||||
ioc->pid = -1;
|
||||
}
|
||||
|
||||
static void qio_channel_command_finalize(Object *obj)
|
||||
{
|
||||
QIOChannelCommand *ioc = QIO_CHANNEL_COMMAND(obj);
|
||||
if (ioc->readfd != -1) {
|
||||
close(ioc->readfd);
|
||||
ioc->readfd = -1;
|
||||
}
|
||||
if (ioc->writefd != -1) {
|
||||
close(ioc->writefd);
|
||||
ioc->writefd = -1;
|
||||
}
|
||||
if (ioc->pid > 0) {
|
||||
#ifndef WIN32
|
||||
qio_channel_command_abort(ioc, NULL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_command_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelCommand *cioc = QIO_CHANNEL_COMMAND(ioc);
|
||||
ssize_t ret;
|
||||
|
||||
retry:
|
||||
ret = readv(cioc->readfd, iov, niov);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN ||
|
||||
errno == EWOULDBLOCK) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to read from command");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t qio_channel_command_writev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelCommand *cioc = QIO_CHANNEL_COMMAND(ioc);
|
||||
ssize_t ret;
|
||||
|
||||
retry:
|
||||
ret = writev(cioc->writefd, iov, niov);
|
||||
if (ret <= 0) {
|
||||
if (errno == EAGAIN ||
|
||||
errno == EWOULDBLOCK) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
error_setg_errno(errp, errno, "%s",
|
||||
"Unable to write to command");
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qio_channel_command_set_blocking(QIOChannel *ioc,
|
||||
bool enabled,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelCommand *cioc = QIO_CHANNEL_COMMAND(ioc);
|
||||
|
||||
if (enabled) {
|
||||
qemu_set_block(cioc->writefd);
|
||||
qemu_set_block(cioc->readfd);
|
||||
} else {
|
||||
qemu_set_nonblock(cioc->writefd);
|
||||
qemu_set_nonblock(cioc->readfd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int qio_channel_command_close(QIOChannel *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelCommand *cioc = QIO_CHANNEL_COMMAND(ioc);
|
||||
int rv = 0;
|
||||
|
||||
/* We close FDs before killing, because that
|
||||
* gives a better chance of clean shutdown
|
||||
*/
|
||||
if (close(cioc->writefd) < 0) {
|
||||
rv = -1;
|
||||
}
|
||||
if (close(cioc->readfd) < 0) {
|
||||
rv = -1;
|
||||
}
|
||||
#ifndef WIN32
|
||||
if (qio_channel_command_abort(cioc, errp) < 0) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
if (rv < 0) {
|
||||
error_setg_errno(errp, errno, "%s",
|
||||
"Unable to close command");
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
static GSource *qio_channel_command_create_watch(QIOChannel *ioc,
|
||||
GIOCondition condition)
|
||||
{
|
||||
QIOChannelCommand *cioc = QIO_CHANNEL_COMMAND(ioc);
|
||||
return qio_channel_create_fd_pair_watch(ioc,
|
||||
cioc->readfd,
|
||||
cioc->writefd,
|
||||
condition);
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_command_class_init(ObjectClass *klass,
|
||||
void *class_data G_GNUC_UNUSED)
|
||||
{
|
||||
QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
|
||||
|
||||
ioc_klass->io_writev = qio_channel_command_writev;
|
||||
ioc_klass->io_readv = qio_channel_command_readv;
|
||||
ioc_klass->io_set_blocking = qio_channel_command_set_blocking;
|
||||
ioc_klass->io_close = qio_channel_command_close;
|
||||
ioc_klass->io_create_watch = qio_channel_command_create_watch;
|
||||
}
|
||||
|
||||
static const TypeInfo qio_channel_command_info = {
|
||||
.parent = TYPE_QIO_CHANNEL,
|
||||
.name = TYPE_QIO_CHANNEL_COMMAND,
|
||||
.instance_size = sizeof(QIOChannelCommand),
|
||||
.instance_init = qio_channel_command_init,
|
||||
.instance_finalize = qio_channel_command_finalize,
|
||||
.class_init = qio_channel_command_class_init,
|
||||
};
|
||||
|
||||
static void qio_channel_command_register_types(void)
|
||||
{
|
||||
type_register_static(&qio_channel_command_info);
|
||||
}
|
||||
|
||||
type_init(qio_channel_command_register_types);
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* QEMU I/O channels files driver
|
||||
*
|
||||
* 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-file.h"
|
||||
#include "io/channel-watch.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "trace.h"
|
||||
|
||||
QIOChannelFile *
|
||||
qio_channel_file_new_fd(int fd)
|
||||
{
|
||||
QIOChannelFile *ioc;
|
||||
|
||||
ioc = QIO_CHANNEL_FILE(object_new(TYPE_QIO_CHANNEL_FILE));
|
||||
|
||||
ioc->fd = fd;
|
||||
|
||||
trace_qio_channel_file_new_fd(ioc, fd);
|
||||
|
||||
return ioc;
|
||||
}
|
||||
|
||||
|
||||
QIOChannelFile *
|
||||
qio_channel_file_new_path(const char *path,
|
||||
int flags,
|
||||
mode_t mode,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelFile *ioc;
|
||||
|
||||
ioc = QIO_CHANNEL_FILE(object_new(TYPE_QIO_CHANNEL_FILE));
|
||||
|
||||
if (flags & O_WRONLY) {
|
||||
ioc->fd = open(path, flags, mode);
|
||||
} else {
|
||||
ioc->fd = open(path, flags);
|
||||
}
|
||||
if (ioc->fd < 0) {
|
||||
object_unref(OBJECT(ioc));
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to open %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
trace_qio_channel_file_new_path(ioc, path, flags, mode, ioc->fd);
|
||||
|
||||
return ioc;
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_file_init(Object *obj)
|
||||
{
|
||||
QIOChannelFile *ioc = QIO_CHANNEL_FILE(obj);
|
||||
ioc->fd = -1;
|
||||
}
|
||||
|
||||
static void qio_channel_file_finalize(Object *obj)
|
||||
{
|
||||
QIOChannelFile *ioc = QIO_CHANNEL_FILE(obj);
|
||||
if (ioc->fd != -1) {
|
||||
close(ioc->fd);
|
||||
ioc->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_file_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
ssize_t ret;
|
||||
|
||||
retry:
|
||||
ret = readv(fioc->fd, iov, niov);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN ||
|
||||
errno == EWOULDBLOCK) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to read from file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t qio_channel_file_writev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
ssize_t ret;
|
||||
|
||||
retry:
|
||||
ret = writev(fioc->fd, iov, niov);
|
||||
if (ret <= 0) {
|
||||
if (errno == EAGAIN ||
|
||||
errno == EWOULDBLOCK) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to write to file");
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qio_channel_file_set_blocking(QIOChannel *ioc,
|
||||
bool enabled,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
|
||||
if (enabled) {
|
||||
qemu_set_block(fioc->fd);
|
||||
} else {
|
||||
qemu_set_nonblock(fioc->fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static off_t qio_channel_file_seek(QIOChannel *ioc,
|
||||
off_t offset,
|
||||
int whence,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
off_t ret;
|
||||
|
||||
ret = lseek(fioc->fd, offset, whence);
|
||||
if (ret == (off_t)-1) {
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to seek to offset %lld whence %d in file",
|
||||
(long long int)offset, whence);
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int qio_channel_file_close(QIOChannel *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
|
||||
if (close(fioc->fd) < 0) {
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to close file");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static GSource *qio_channel_file_create_watch(QIOChannel *ioc,
|
||||
GIOCondition condition)
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
return qio_channel_create_fd_watch(ioc,
|
||||
fioc->fd,
|
||||
condition);
|
||||
}
|
||||
|
||||
static void qio_channel_file_class_init(ObjectClass *klass,
|
||||
void *class_data G_GNUC_UNUSED)
|
||||
{
|
||||
QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
|
||||
|
||||
ioc_klass->io_writev = qio_channel_file_writev;
|
||||
ioc_klass->io_readv = qio_channel_file_readv;
|
||||
ioc_klass->io_set_blocking = qio_channel_file_set_blocking;
|
||||
ioc_klass->io_seek = qio_channel_file_seek;
|
||||
ioc_klass->io_close = qio_channel_file_close;
|
||||
ioc_klass->io_create_watch = qio_channel_file_create_watch;
|
||||
}
|
||||
|
||||
static const TypeInfo qio_channel_file_info = {
|
||||
.parent = TYPE_QIO_CHANNEL,
|
||||
.name = TYPE_QIO_CHANNEL_FILE,
|
||||
.instance_size = sizeof(QIOChannelFile),
|
||||
.instance_init = qio_channel_file_init,
|
||||
.instance_finalize = qio_channel_file_finalize,
|
||||
.class_init = qio_channel_file_class_init,
|
||||
};
|
||||
|
||||
static void qio_channel_file_register_types(void)
|
||||
{
|
||||
type_register_static(&qio_channel_file_info);
|
||||
}
|
||||
|
||||
type_init(qio_channel_file_register_types);
|
|
@ -0,0 +1,741 @@
|
|||
/*
|
||||
* QEMU I/O channels sockets driver
|
||||
*
|
||||
* 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-socket.h"
|
||||
#include "io/channel-watch.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define SOCKET_MAX_FDS 16
|
||||
|
||||
SocketAddress *
|
||||
qio_channel_socket_get_local_address(QIOChannelSocket *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
return socket_sockaddr_to_address(&ioc->localAddr,
|
||||
ioc->localAddrLen,
|
||||
errp);
|
||||
}
|
||||
|
||||
SocketAddress *
|
||||
qio_channel_socket_get_remote_address(QIOChannelSocket *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
return socket_sockaddr_to_address(&ioc->remoteAddr,
|
||||
ioc->remoteAddrLen,
|
||||
errp);
|
||||
}
|
||||
|
||||
QIOChannelSocket *
|
||||
qio_channel_socket_new(void)
|
||||
{
|
||||
QIOChannelSocket *sioc;
|
||||
QIOChannel *ioc;
|
||||
|
||||
sioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
|
||||
sioc->fd = -1;
|
||||
|
||||
ioc = QIO_CHANNEL(sioc);
|
||||
ioc->features |= (1 << QIO_CHANNEL_FEATURE_SHUTDOWN);
|
||||
|
||||
trace_qio_channel_socket_new(sioc);
|
||||
|
||||
return sioc;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
qio_channel_socket_set_fd(QIOChannelSocket *sioc,
|
||||
int fd,
|
||||
Error **errp)
|
||||
{
|
||||
if (sioc->fd != -1) {
|
||||
error_setg(errp, "Socket is already open");
|
||||
return -1;
|
||||
}
|
||||
|
||||
sioc->fd = fd;
|
||||
sioc->remoteAddrLen = sizeof(sioc->remoteAddr);
|
||||
sioc->localAddrLen = sizeof(sioc->localAddr);
|
||||
|
||||
|
||||
if (getpeername(fd, (struct sockaddr *)&sioc->remoteAddr,
|
||||
&sioc->remoteAddrLen) < 0) {
|
||||
if (socket_error() == ENOTCONN) {
|
||||
memset(&sioc->remoteAddr, 0, sizeof(sioc->remoteAddr));
|
||||
sioc->remoteAddrLen = sizeof(sioc->remoteAddr);
|
||||
} else {
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to query remote socket address");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (getsockname(fd, (struct sockaddr *)&sioc->localAddr,
|
||||
&sioc->localAddrLen) < 0) {
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to query local socket address");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
if (sioc->localAddr.ss_family == AF_UNIX) {
|
||||
QIOChannel *ioc = QIO_CHANNEL(sioc);
|
||||
ioc->features |= (1 << QIO_CHANNEL_FEATURE_FD_PASS);
|
||||
}
|
||||
#endif /* WIN32 */
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
sioc->fd = -1; /* Let the caller close FD on failure */
|
||||
return -1;
|
||||
}
|
||||
|
||||
QIOChannelSocket *
|
||||
qio_channel_socket_new_fd(int fd,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *ioc;
|
||||
|
||||
ioc = qio_channel_socket_new();
|
||||
if (qio_channel_socket_set_fd(ioc, fd, errp) < 0) {
|
||||
object_unref(OBJECT(ioc));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
trace_qio_channel_socket_new_fd(ioc, fd);
|
||||
|
||||
return ioc;
|
||||
}
|
||||
|
||||
|
||||
int qio_channel_socket_connect_sync(QIOChannelSocket *ioc,
|
||||
SocketAddress *addr,
|
||||
Error **errp)
|
||||
{
|
||||
int fd;
|
||||
|
||||
trace_qio_channel_socket_connect_sync(ioc, addr);
|
||||
fd = socket_connect(addr, errp, NULL, NULL);
|
||||
if (fd < 0) {
|
||||
trace_qio_channel_socket_connect_fail(ioc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
trace_qio_channel_socket_connect_complete(ioc, fd);
|
||||
if (qio_channel_socket_set_fd(ioc, fd, errp) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int qio_channel_socket_connect_worker(QIOTask *task,
|
||||
Error **errp,
|
||||
gpointer opaque)
|
||||
{
|
||||
QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task));
|
||||
SocketAddress *addr = opaque;
|
||||
int ret;
|
||||
|
||||
ret = qio_channel_socket_connect_sync(ioc,
|
||||
addr,
|
||||
errp);
|
||||
|
||||
object_unref(OBJECT(ioc));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void qio_channel_socket_connect_async(QIOChannelSocket *ioc,
|
||||
SocketAddress *addr,
|
||||
QIOTaskFunc callback,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
QIOTask *task = qio_task_new(
|
||||
OBJECT(ioc), callback, opaque, destroy);
|
||||
SocketAddress *addrCopy;
|
||||
|
||||
qapi_copy_SocketAddress(&addrCopy, addr);
|
||||
|
||||
/* socket_connect() does a non-blocking connect(), but it
|
||||
* still blocks in DNS lookups, so we must use a thread */
|
||||
trace_qio_channel_socket_connect_async(ioc, addr);
|
||||
qio_task_run_in_thread(task,
|
||||
qio_channel_socket_connect_worker,
|
||||
addrCopy,
|
||||
(GDestroyNotify)qapi_free_SocketAddress);
|
||||
}
|
||||
|
||||
|
||||
int qio_channel_socket_listen_sync(QIOChannelSocket *ioc,
|
||||
SocketAddress *addr,
|
||||
Error **errp)
|
||||
{
|
||||
int fd;
|
||||
|
||||
trace_qio_channel_socket_listen_sync(ioc, addr);
|
||||
fd = socket_listen(addr, errp);
|
||||
if (fd < 0) {
|
||||
trace_qio_channel_socket_listen_fail(ioc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
trace_qio_channel_socket_listen_complete(ioc, fd);
|
||||
if (qio_channel_socket_set_fd(ioc, fd, errp) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int qio_channel_socket_listen_worker(QIOTask *task,
|
||||
Error **errp,
|
||||
gpointer opaque)
|
||||
{
|
||||
QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task));
|
||||
SocketAddress *addr = opaque;
|
||||
int ret;
|
||||
|
||||
ret = qio_channel_socket_listen_sync(ioc,
|
||||
addr,
|
||||
errp);
|
||||
|
||||
object_unref(OBJECT(ioc));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void qio_channel_socket_listen_async(QIOChannelSocket *ioc,
|
||||
SocketAddress *addr,
|
||||
QIOTaskFunc callback,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
QIOTask *task = qio_task_new(
|
||||
OBJECT(ioc), callback, opaque, destroy);
|
||||
SocketAddress *addrCopy;
|
||||
|
||||
qapi_copy_SocketAddress(&addrCopy, addr);
|
||||
|
||||
/* socket_listen() blocks in DNS lookups, so we must use a thread */
|
||||
trace_qio_channel_socket_listen_async(ioc, addr);
|
||||
qio_task_run_in_thread(task,
|
||||
qio_channel_socket_listen_worker,
|
||||
addrCopy,
|
||||
(GDestroyNotify)qapi_free_SocketAddress);
|
||||
}
|
||||
|
||||
|
||||
int qio_channel_socket_dgram_sync(QIOChannelSocket *ioc,
|
||||
SocketAddress *localAddr,
|
||||
SocketAddress *remoteAddr,
|
||||
Error **errp)
|
||||
{
|
||||
int fd;
|
||||
|
||||
trace_qio_channel_socket_dgram_sync(ioc, localAddr, remoteAddr);
|
||||
fd = socket_dgram(localAddr, remoteAddr, errp);
|
||||
if (fd < 0) {
|
||||
trace_qio_channel_socket_dgram_fail(ioc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
trace_qio_channel_socket_dgram_complete(ioc, fd);
|
||||
if (qio_channel_socket_set_fd(ioc, fd, errp) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct QIOChannelSocketDGramWorkerData {
|
||||
SocketAddress *localAddr;
|
||||
SocketAddress *remoteAddr;
|
||||
};
|
||||
|
||||
|
||||
static void qio_channel_socket_dgram_worker_free(gpointer opaque)
|
||||
{
|
||||
struct QIOChannelSocketDGramWorkerData *data = opaque;
|
||||
qapi_free_SocketAddress(data->localAddr);
|
||||
qapi_free_SocketAddress(data->remoteAddr);
|
||||
g_free(data);
|
||||
}
|
||||
|
||||
static int qio_channel_socket_dgram_worker(QIOTask *task,
|
||||
Error **errp,
|
||||
gpointer opaque)
|
||||
{
|
||||
QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task));
|
||||
struct QIOChannelSocketDGramWorkerData *data = opaque;
|
||||
int ret;
|
||||
|
||||
/* socket_dgram() blocks in DNS lookups, so we must use a thread */
|
||||
ret = qio_channel_socket_dgram_sync(ioc,
|
||||
data->localAddr,
|
||||
data->remoteAddr,
|
||||
errp);
|
||||
|
||||
object_unref(OBJECT(ioc));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void qio_channel_socket_dgram_async(QIOChannelSocket *ioc,
|
||||
SocketAddress *localAddr,
|
||||
SocketAddress *remoteAddr,
|
||||
QIOTaskFunc callback,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
QIOTask *task = qio_task_new(
|
||||
OBJECT(ioc), callback, opaque, destroy);
|
||||
struct QIOChannelSocketDGramWorkerData *data = g_new0(
|
||||
struct QIOChannelSocketDGramWorkerData, 1);
|
||||
|
||||
qapi_copy_SocketAddress(&data->localAddr, localAddr);
|
||||
qapi_copy_SocketAddress(&data->remoteAddr, remoteAddr);
|
||||
|
||||
trace_qio_channel_socket_dgram_async(ioc, localAddr, remoteAddr);
|
||||
qio_task_run_in_thread(task,
|
||||
qio_channel_socket_dgram_worker,
|
||||
data,
|
||||
qio_channel_socket_dgram_worker_free);
|
||||
}
|
||||
|
||||
|
||||
QIOChannelSocket *
|
||||
qio_channel_socket_accept(QIOChannelSocket *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *cioc;
|
||||
|
||||
cioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
|
||||
cioc->fd = -1;
|
||||
cioc->remoteAddrLen = sizeof(ioc->remoteAddr);
|
||||
cioc->localAddrLen = sizeof(ioc->localAddr);
|
||||
|
||||
retry:
|
||||
trace_qio_channel_socket_accept(ioc);
|
||||
cioc->fd = accept(ioc->fd, (struct sockaddr *)&cioc->remoteAddr,
|
||||
&cioc->remoteAddrLen);
|
||||
if (cioc->fd < 0) {
|
||||
trace_qio_channel_socket_accept_fail(ioc);
|
||||
if (socket_error() == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (getsockname(cioc->fd, (struct sockaddr *)&ioc->localAddr,
|
||||
&ioc->localAddrLen) < 0) {
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to query local socket address");
|
||||
goto error;
|
||||
}
|
||||
|
||||
trace_qio_channel_socket_accept_complete(ioc, cioc, cioc->fd);
|
||||
return cioc;
|
||||
|
||||
error:
|
||||
object_unref(OBJECT(cioc));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void qio_channel_socket_init(Object *obj)
|
||||
{
|
||||
QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(obj);
|
||||
ioc->fd = -1;
|
||||
}
|
||||
|
||||
static void qio_channel_socket_finalize(Object *obj)
|
||||
{
|
||||
QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(obj);
|
||||
if (ioc->fd != -1) {
|
||||
close(ioc->fd);
|
||||
ioc->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifndef WIN32
|
||||
static void qio_channel_socket_copy_fds(struct msghdr *msg,
|
||||
int **fds, size_t *nfds)
|
||||
{
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
*nfds = 0;
|
||||
*fds = NULL;
|
||||
|
||||
for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
|
||||
int fd_size, i;
|
||||
int gotfds;
|
||||
|
||||
if (cmsg->cmsg_len < CMSG_LEN(sizeof(int)) ||
|
||||
cmsg->cmsg_level != SOL_SOCKET ||
|
||||
cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fd_size = cmsg->cmsg_len - CMSG_LEN(0);
|
||||
|
||||
if (!fd_size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
gotfds = fd_size / sizeof(int);
|
||||
*fds = g_renew(int, *fds, *nfds + gotfds);
|
||||
memcpy(*fds + *nfds, CMSG_DATA(cmsg), fd_size);
|
||||
|
||||
for (i = 0; i < gotfds; i++) {
|
||||
int fd = (*fds)[*nfds + i];
|
||||
if (fd < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
|
||||
qemu_set_block(fd);
|
||||
|
||||
#ifndef MSG_CMSG_CLOEXEC
|
||||
qemu_set_cloexec(fd);
|
||||
#endif
|
||||
}
|
||||
*nfds += gotfds;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_socket_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
ssize_t ret;
|
||||
struct msghdr msg = { NULL, };
|
||||
char control[CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS)];
|
||||
int sflags = 0;
|
||||
|
||||
#ifdef MSG_CMSG_CLOEXEC
|
||||
sflags |= MSG_CMSG_CLOEXEC;
|
||||
#endif
|
||||
|
||||
msg.msg_iov = (struct iovec *)iov;
|
||||
msg.msg_iovlen = niov;
|
||||
if (fds && nfds) {
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
}
|
||||
|
||||
retry:
|
||||
ret = recvmsg(sioc->fd, &msg, sflags);
|
||||
if (ret < 0) {
|
||||
if (socket_error() == EAGAIN ||
|
||||
socket_error() == EWOULDBLOCK) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
if (socket_error() == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to read from socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fds && nfds) {
|
||||
qio_channel_socket_copy_fds(&msg, fds, nfds);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
ssize_t ret;
|
||||
struct msghdr msg = { NULL, };
|
||||
|
||||
msg.msg_iov = (struct iovec *)iov;
|
||||
msg.msg_iovlen = niov;
|
||||
|
||||
if (nfds) {
|
||||
char control[CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS)];
|
||||
size_t fdsize = sizeof(int) * nfds;
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
if (nfds > SOCKET_MAX_FDS) {
|
||||
error_setg_errno(errp, -EINVAL,
|
||||
"Only %d FDs can be sent, got %zu",
|
||||
SOCKET_MAX_FDS, nfds);
|
||||
return -1;
|
||||
}
|
||||
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = CMSG_SPACE(sizeof(int) * nfds);
|
||||
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_len = CMSG_LEN(fdsize);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
memcpy(CMSG_DATA(cmsg), fds, fdsize);
|
||||
}
|
||||
|
||||
retry:
|
||||
ret = sendmsg(sioc->fd, &msg, 0);
|
||||
if (ret <= 0) {
|
||||
if (socket_error() == EAGAIN ||
|
||||
socket_error() == EWOULDBLOCK) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
if (socket_error() == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to write to socket");
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#else /* WIN32 */
|
||||
static ssize_t qio_channel_socket_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
ssize_t done = 0;
|
||||
ssize_t i;
|
||||
|
||||
for (i = 0; i < niov; i++) {
|
||||
ssize_t ret;
|
||||
retry:
|
||||
ret = recv(sioc->fd,
|
||||
iov[i].iov_base,
|
||||
iov[i].iov_len,
|
||||
0);
|
||||
if (ret < 0) {
|
||||
if (socket_error() == EAGAIN) {
|
||||
if (done) {
|
||||
return done;
|
||||
} else {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
} else if (socket_error() == EINTR) {
|
||||
goto retry;
|
||||
} else {
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to write to socket");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
done += ret;
|
||||
if (ret < iov[i].iov_len) {
|
||||
return done;
|
||||
}
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
ssize_t done = 0;
|
||||
ssize_t i;
|
||||
|
||||
for (i = 0; i < niov; i++) {
|
||||
ssize_t ret;
|
||||
retry:
|
||||
ret = send(sioc->fd,
|
||||
iov[i].iov_base,
|
||||
iov[i].iov_len,
|
||||
0);
|
||||
if (ret < 0) {
|
||||
if (socket_error() == EAGAIN) {
|
||||
if (done) {
|
||||
return done;
|
||||
} else {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
} else if (socket_error() == EINTR) {
|
||||
goto retry;
|
||||
} else {
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to write to socket");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
done += ret;
|
||||
if (ret < iov[i].iov_len) {
|
||||
return done;
|
||||
}
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
#endif /* WIN32 */
|
||||
|
||||
static int
|
||||
qio_channel_socket_set_blocking(QIOChannel *ioc,
|
||||
bool enabled,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
|
||||
if (enabled) {
|
||||
qemu_set_block(sioc->fd);
|
||||
} else {
|
||||
qemu_set_nonblock(sioc->fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
qio_channel_socket_set_delay(QIOChannel *ioc,
|
||||
bool enabled)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
int v = enabled ? 0 : 1;
|
||||
|
||||
qemu_setsockopt(sioc->fd,
|
||||
IPPROTO_TCP, TCP_NODELAY,
|
||||
&v, sizeof(v));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
qio_channel_socket_set_cork(QIOChannel *ioc,
|
||||
bool enabled)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
int v = enabled ? 1 : 0;
|
||||
|
||||
socket_set_cork(sioc->fd, v);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
qio_channel_socket_close(QIOChannel *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
|
||||
if (closesocket(sioc->fd) < 0) {
|
||||
sioc->fd = -1;
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to close socket");
|
||||
return -1;
|
||||
}
|
||||
sioc->fd = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
qio_channel_socket_shutdown(QIOChannel *ioc,
|
||||
QIOChannelShutdown how,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
int sockhow;
|
||||
|
||||
switch (how) {
|
||||
case QIO_CHANNEL_SHUTDOWN_READ:
|
||||
sockhow = SHUT_RD;
|
||||
break;
|
||||
case QIO_CHANNEL_SHUTDOWN_WRITE:
|
||||
sockhow = SHUT_WR;
|
||||
break;
|
||||
case QIO_CHANNEL_SHUTDOWN_BOTH:
|
||||
default:
|
||||
sockhow = SHUT_RDWR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (shutdown(sioc->fd, sockhow) < 0) {
|
||||
error_setg_errno(errp, socket_error(),
|
||||
"Unable to shutdown socket");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static GSource *qio_channel_socket_create_watch(QIOChannel *ioc,
|
||||
GIOCondition condition)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||
return qio_channel_create_fd_watch(ioc,
|
||||
sioc->fd,
|
||||
condition);
|
||||
}
|
||||
|
||||
static void qio_channel_socket_class_init(ObjectClass *klass,
|
||||
void *class_data G_GNUC_UNUSED)
|
||||
{
|
||||
QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
|
||||
|
||||
ioc_klass->io_writev = qio_channel_socket_writev;
|
||||
ioc_klass->io_readv = qio_channel_socket_readv;
|
||||
ioc_klass->io_set_blocking = qio_channel_socket_set_blocking;
|
||||
ioc_klass->io_close = qio_channel_socket_close;
|
||||
ioc_klass->io_shutdown = qio_channel_socket_shutdown;
|
||||
ioc_klass->io_set_cork = qio_channel_socket_set_cork;
|
||||
ioc_klass->io_set_delay = qio_channel_socket_set_delay;
|
||||
ioc_klass->io_create_watch = qio_channel_socket_create_watch;
|
||||
}
|
||||
|
||||
static const TypeInfo qio_channel_socket_info = {
|
||||
.parent = TYPE_QIO_CHANNEL,
|
||||
.name = TYPE_QIO_CHANNEL_SOCKET,
|
||||
.instance_size = sizeof(QIOChannelSocket),
|
||||
.instance_init = qio_channel_socket_init,
|
||||
.instance_finalize = qio_channel_socket_finalize,
|
||||
.class_init = qio_channel_socket_class_init,
|
||||
};
|
||||
|
||||
static void qio_channel_socket_register_types(void)
|
||||
{
|
||||
type_register_static(&qio_channel_socket_info);
|
||||
}
|
||||
|
||||
type_init(qio_channel_socket_register_types);
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* QEMU I/O channels TLS driver
|
||||
*
|
||||
* 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-tls.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
static ssize_t qio_channel_tls_write_handler(const char *buf,
|
||||
size_t len,
|
||||
void *opaque)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque);
|
||||
ssize_t ret;
|
||||
|
||||
ret = qio_channel_write(tioc->master, buf, len, NULL);
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
} else if (ret < 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t qio_channel_tls_read_handler(char *buf,
|
||||
size_t len,
|
||||
void *opaque)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque);
|
||||
ssize_t ret;
|
||||
|
||||
ret = qio_channel_read(tioc->master, buf, len, NULL);
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
} else if (ret < 0) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
QIOChannelTLS *
|
||||
qio_channel_tls_new_server(QIOChannel *master,
|
||||
QCryptoTLSCreds *creds,
|
||||
const char *aclname,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *ioc;
|
||||
|
||||
ioc = QIO_CHANNEL_TLS(object_new(TYPE_QIO_CHANNEL_TLS));
|
||||
|
||||
ioc->master = master;
|
||||
object_ref(OBJECT(master));
|
||||
|
||||
ioc->session = qcrypto_tls_session_new(
|
||||
creds,
|
||||
NULL,
|
||||
aclname,
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
|
||||
errp);
|
||||
if (!ioc->session) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
qcrypto_tls_session_set_callbacks(
|
||||
ioc->session,
|
||||
qio_channel_tls_write_handler,
|
||||
qio_channel_tls_read_handler,
|
||||
ioc);
|
||||
|
||||
trace_qio_channel_tls_new_server(ioc, master, creds, aclname);
|
||||
return ioc;
|
||||
|
||||
error:
|
||||
object_unref(OBJECT(ioc));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QIOChannelTLS *
|
||||
qio_channel_tls_new_client(QIOChannel *master,
|
||||
QCryptoTLSCreds *creds,
|
||||
const char *hostname,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *tioc;
|
||||
QIOChannel *ioc;
|
||||
|
||||
tioc = QIO_CHANNEL_TLS(object_new(TYPE_QIO_CHANNEL_TLS));
|
||||
ioc = QIO_CHANNEL(tioc);
|
||||
|
||||
tioc->master = master;
|
||||
if (master->features & (1 << QIO_CHANNEL_FEATURE_SHUTDOWN)) {
|
||||
ioc->features |= (1 << QIO_CHANNEL_FEATURE_SHUTDOWN);
|
||||
}
|
||||
object_ref(OBJECT(master));
|
||||
|
||||
tioc->session = qcrypto_tls_session_new(
|
||||
creds,
|
||||
hostname,
|
||||
NULL,
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
|
||||
errp);
|
||||
if (!tioc->session) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
qcrypto_tls_session_set_callbacks(
|
||||
tioc->session,
|
||||
qio_channel_tls_write_handler,
|
||||
qio_channel_tls_read_handler,
|
||||
tioc);
|
||||
|
||||
trace_qio_channel_tls_new_client(tioc, master, creds, hostname);
|
||||
return tioc;
|
||||
|
||||
error:
|
||||
object_unref(OBJECT(tioc));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer user_data);
|
||||
|
||||
static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc,
|
||||
QIOTask *task)
|
||||
{
|
||||
Error *err = NULL;
|
||||
QCryptoTLSSessionHandshakeStatus status;
|
||||
|
||||
if (qcrypto_tls_session_handshake(ioc->session, &err) < 0) {
|
||||
trace_qio_channel_tls_handshake_fail(ioc);
|
||||
qio_task_abort(task, err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
status = qcrypto_tls_session_get_handshake_status(ioc->session);
|
||||
if (status == QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
|
||||
trace_qio_channel_tls_handshake_complete(ioc);
|
||||
if (qcrypto_tls_session_check_credentials(ioc->session,
|
||||
&err) < 0) {
|
||||
trace_qio_channel_tls_credentials_deny(ioc);
|
||||
qio_task_abort(task, err);
|
||||
goto cleanup;
|
||||
}
|
||||
trace_qio_channel_tls_credentials_allow(ioc);
|
||||
qio_task_complete(task);
|
||||
} else {
|
||||
GIOCondition condition;
|
||||
if (status == QCRYPTO_TLS_HANDSHAKE_SENDING) {
|
||||
condition = G_IO_OUT;
|
||||
} else {
|
||||
condition = G_IO_IN;
|
||||
}
|
||||
|
||||
trace_qio_channel_tls_handshake_pending(ioc, status);
|
||||
qio_channel_add_watch(ioc->master,
|
||||
condition,
|
||||
qio_channel_tls_handshake_io,
|
||||
task,
|
||||
NULL);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
error_free(err);
|
||||
}
|
||||
|
||||
|
||||
static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer user_data)
|
||||
{
|
||||
QIOTask *task = user_data;
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(
|
||||
qio_task_get_source(task));
|
||||
|
||||
qio_channel_tls_handshake_task(
|
||||
tioc, task);
|
||||
|
||||
object_unref(OBJECT(tioc));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void qio_channel_tls_handshake(QIOChannelTLS *ioc,
|
||||
QIOTaskFunc func,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
QIOTask *task;
|
||||
|
||||
task = qio_task_new(OBJECT(ioc),
|
||||
func, opaque, destroy);
|
||||
|
||||
trace_qio_channel_tls_handshake_start(ioc);
|
||||
qio_channel_tls_handshake_task(ioc, task);
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_tls_init(Object *obj G_GNUC_UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_tls_finalize(Object *obj)
|
||||
{
|
||||
QIOChannelTLS *ioc = QIO_CHANNEL_TLS(obj);
|
||||
|
||||
object_unref(OBJECT(ioc->master));
|
||||
qcrypto_tls_session_free(ioc->session);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
|
||||
size_t i;
|
||||
ssize_t got = 0;
|
||||
|
||||
for (i = 0 ; i < niov ; i++) {
|
||||
ssize_t ret = qcrypto_tls_session_read(tioc->session,
|
||||
iov[i].iov_base,
|
||||
iov[i].iov_len);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
if (got) {
|
||||
return got;
|
||||
} else {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
error_setg_errno(errp, errno,
|
||||
"Cannot read from TLS channel");
|
||||
return -1;
|
||||
}
|
||||
got += ret;
|
||||
if (ret < iov[i].iov_len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return got;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_tls_writev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
|
||||
size_t i;
|
||||
ssize_t done = 0;
|
||||
|
||||
for (i = 0 ; i < niov ; i++) {
|
||||
ssize_t ret = qcrypto_tls_session_write(tioc->session,
|
||||
iov[i].iov_base,
|
||||
iov[i].iov_len);
|
||||
if (ret <= 0) {
|
||||
if (errno == EAGAIN) {
|
||||
if (done) {
|
||||
return done;
|
||||
} else {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
error_setg_errno(errp, errno,
|
||||
"Cannot write to TLS channel");
|
||||
return -1;
|
||||
}
|
||||
done += ret;
|
||||
if (ret < iov[i].iov_len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
static int qio_channel_tls_set_blocking(QIOChannel *ioc,
|
||||
bool enabled,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
|
||||
|
||||
return qio_channel_set_blocking(tioc->master, enabled, errp);
|
||||
}
|
||||
|
||||
static void qio_channel_tls_set_delay(QIOChannel *ioc,
|
||||
bool enabled)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
|
||||
|
||||
qio_channel_set_delay(tioc->master, enabled);
|
||||
}
|
||||
|
||||
static void qio_channel_tls_set_cork(QIOChannel *ioc,
|
||||
bool enabled)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
|
||||
|
||||
qio_channel_set_cork(tioc->master, enabled);
|
||||
}
|
||||
|
||||
static int qio_channel_tls_shutdown(QIOChannel *ioc,
|
||||
QIOChannelShutdown how,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
|
||||
|
||||
return qio_channel_shutdown(tioc->master, how, errp);
|
||||
}
|
||||
|
||||
static int qio_channel_tls_close(QIOChannel *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
|
||||
|
||||
return qio_channel_close(tioc->master, errp);
|
||||
}
|
||||
|
||||
static GSource *qio_channel_tls_create_watch(QIOChannel *ioc,
|
||||
GIOCondition condition)
|
||||
{
|
||||
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
|
||||
|
||||
return qio_channel_create_watch(tioc->master, condition);
|
||||
}
|
||||
|
||||
QCryptoTLSSession *
|
||||
qio_channel_tls_get_session(QIOChannelTLS *ioc)
|
||||
{
|
||||
return ioc->session;
|
||||
}
|
||||
|
||||
static void qio_channel_tls_class_init(ObjectClass *klass,
|
||||
void *class_data G_GNUC_UNUSED)
|
||||
{
|
||||
QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
|
||||
|
||||
ioc_klass->io_writev = qio_channel_tls_writev;
|
||||
ioc_klass->io_readv = qio_channel_tls_readv;
|
||||
ioc_klass->io_set_blocking = qio_channel_tls_set_blocking;
|
||||
ioc_klass->io_set_delay = qio_channel_tls_set_delay;
|
||||
ioc_klass->io_set_cork = qio_channel_tls_set_cork;
|
||||
ioc_klass->io_close = qio_channel_tls_close;
|
||||
ioc_klass->io_shutdown = qio_channel_tls_shutdown;
|
||||
ioc_klass->io_create_watch = qio_channel_tls_create_watch;
|
||||
}
|
||||
|
||||
static const TypeInfo qio_channel_tls_info = {
|
||||
.parent = TYPE_QIO_CHANNEL,
|
||||
.name = TYPE_QIO_CHANNEL_TLS,
|
||||
.instance_size = sizeof(QIOChannelTLS),
|
||||
.instance_init = qio_channel_tls_init,
|
||||
.instance_finalize = qio_channel_tls_finalize,
|
||||
.class_init = qio_channel_tls_class_init,
|
||||
};
|
||||
|
||||
static void qio_channel_tls_register_types(void)
|
||||
{
|
||||
type_register_static(&qio_channel_tls_info);
|
||||
}
|
||||
|
||||
type_init(qio_channel_tls_register_types);
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* QEMU I/O channels watch helper APIs
|
||||
*
|
||||
* 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-watch.h"
|
||||
|
||||
typedef struct QIOChannelFDSource QIOChannelFDSource;
|
||||
struct QIOChannelFDSource {
|
||||
GSource parent;
|
||||
GPollFD fd;
|
||||
QIOChannel *ioc;
|
||||
GIOCondition condition;
|
||||
};
|
||||
|
||||
|
||||
typedef struct QIOChannelFDPairSource QIOChannelFDPairSource;
|
||||
struct QIOChannelFDPairSource {
|
||||
GSource parent;
|
||||
GPollFD fdread;
|
||||
GPollFD fdwrite;
|
||||
QIOChannel *ioc;
|
||||
GIOCondition condition;
|
||||
};
|
||||
|
||||
|
||||
static gboolean
|
||||
qio_channel_fd_source_prepare(GSource *source G_GNUC_UNUSED,
|
||||
gint *timeout)
|
||||
{
|
||||
*timeout = -1;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
qio_channel_fd_source_check(GSource *source)
|
||||
{
|
||||
QIOChannelFDSource *ssource = (QIOChannelFDSource *)source;
|
||||
|
||||
return ssource->fd.revents & ssource->condition;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
qio_channel_fd_source_dispatch(GSource *source,
|
||||
GSourceFunc callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
QIOChannelFunc func = (QIOChannelFunc)callback;
|
||||
QIOChannelFDSource *ssource = (QIOChannelFDSource *)source;
|
||||
|
||||
return (*func)(ssource->ioc,
|
||||
ssource->fd.revents & ssource->condition,
|
||||
user_data);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
qio_channel_fd_source_finalize(GSource *source)
|
||||
{
|
||||
QIOChannelFDSource *ssource = (QIOChannelFDSource *)source;
|
||||
|
||||
object_unref(OBJECT(ssource->ioc));
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
qio_channel_fd_pair_source_prepare(GSource *source G_GNUC_UNUSED,
|
||||
gint *timeout)
|
||||
{
|
||||
*timeout = -1;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
qio_channel_fd_pair_source_check(GSource *source)
|
||||
{
|
||||
QIOChannelFDPairSource *ssource = (QIOChannelFDPairSource *)source;
|
||||
GIOCondition poll_condition = ssource->fdread.revents |
|
||||
ssource->fdwrite.revents;
|
||||
|
||||
return poll_condition & ssource->condition;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
qio_channel_fd_pair_source_dispatch(GSource *source,
|
||||
GSourceFunc callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
QIOChannelFunc func = (QIOChannelFunc)callback;
|
||||
QIOChannelFDPairSource *ssource = (QIOChannelFDPairSource *)source;
|
||||
GIOCondition poll_condition = ssource->fdread.revents |
|
||||
ssource->fdwrite.revents;
|
||||
|
||||
return (*func)(ssource->ioc,
|
||||
poll_condition & ssource->condition,
|
||||
user_data);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
qio_channel_fd_pair_source_finalize(GSource *source)
|
||||
{
|
||||
QIOChannelFDPairSource *ssource = (QIOChannelFDPairSource *)source;
|
||||
|
||||
object_unref(OBJECT(ssource->ioc));
|
||||
}
|
||||
|
||||
|
||||
GSourceFuncs qio_channel_fd_source_funcs = {
|
||||
qio_channel_fd_source_prepare,
|
||||
qio_channel_fd_source_check,
|
||||
qio_channel_fd_source_dispatch,
|
||||
qio_channel_fd_source_finalize
|
||||
};
|
||||
|
||||
|
||||
GSourceFuncs qio_channel_fd_pair_source_funcs = {
|
||||
qio_channel_fd_pair_source_prepare,
|
||||
qio_channel_fd_pair_source_check,
|
||||
qio_channel_fd_pair_source_dispatch,
|
||||
qio_channel_fd_pair_source_finalize
|
||||
};
|
||||
|
||||
|
||||
GSource *qio_channel_create_fd_watch(QIOChannel *ioc,
|
||||
int fd,
|
||||
GIOCondition condition)
|
||||
{
|
||||
GSource *source;
|
||||
QIOChannelFDSource *ssource;
|
||||
|
||||
source = g_source_new(&qio_channel_fd_source_funcs,
|
||||
sizeof(QIOChannelFDSource));
|
||||
ssource = (QIOChannelFDSource *)source;
|
||||
|
||||
ssource->ioc = ioc;
|
||||
object_ref(OBJECT(ioc));
|
||||
|
||||
ssource->condition = condition;
|
||||
|
||||
ssource->fd.fd = fd;
|
||||
ssource->fd.events = condition;
|
||||
|
||||
g_source_add_poll(source, &ssource->fd);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
GSource *qio_channel_create_fd_pair_watch(QIOChannel *ioc,
|
||||
int fdread,
|
||||
int fdwrite,
|
||||
GIOCondition condition)
|
||||
{
|
||||
GSource *source;
|
||||
QIOChannelFDPairSource *ssource;
|
||||
|
||||
source = g_source_new(&qio_channel_fd_pair_source_funcs,
|
||||
sizeof(QIOChannelFDPairSource));
|
||||
ssource = (QIOChannelFDPairSource *)source;
|
||||
|
||||
ssource->ioc = ioc;
|
||||
object_ref(OBJECT(ioc));
|
||||
|
||||
ssource->condition = condition;
|
||||
|
||||
ssource->fdread.fd = fdread;
|
||||
ssource->fdread.events = condition & G_IO_IN;
|
||||
|
||||
ssource->fdwrite.fd = fdwrite;
|
||||
ssource->fdwrite.events = condition & G_IO_OUT;
|
||||
|
||||
g_source_add_poll(source, &ssource->fdread);
|
||||
g_source_add_poll(source, &ssource->fdwrite);
|
||||
|
||||
return source;
|
||||
}
|
|
@ -0,0 +1,962 @@
|
|||
/*
|
||||
* QEMU I/O channels driver websockets
|
||||
*
|
||||
* 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-websock.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
/* Max amount to allow in rawinput/rawoutput buffers */
|
||||
#define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
|
||||
|
||||
#define QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN 24
|
||||
#define QIO_CHANNEL_WEBSOCK_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
#define QIO_CHANNEL_WEBSOCK_GUID_LEN strlen(QIO_CHANNEL_WEBSOCK_GUID)
|
||||
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL "Sec-WebSocket-Protocol"
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_VERSION "Sec-WebSocket-Version"
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_KEY "Sec-WebSocket-Key"
|
||||
|
||||
#define QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY "binary"
|
||||
|
||||
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE \
|
||||
"HTTP/1.1 101 Switching Protocols\r\n" \
|
||||
"Upgrade: websocket\r\n" \
|
||||
"Connection: Upgrade\r\n" \
|
||||
"Sec-WebSocket-Accept: %s\r\n" \
|
||||
"Sec-WebSocket-Protocol: binary\r\n" \
|
||||
"\r\n"
|
||||
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM "\r\n"
|
||||
#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_END "\r\n\r\n"
|
||||
#define QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION "13"
|
||||
|
||||
/* The websockets packet header is variable length
|
||||
* depending on the size of the payload... */
|
||||
|
||||
/* ...length when using 7-bit payload length */
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT 6
|
||||
/* ...length when using 16-bit payload length */
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT 8
|
||||
/* ...length when using 64-bit payload length */
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT 14
|
||||
|
||||
/* Length of the optional data mask field in header */
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK 4
|
||||
|
||||
/* Maximum length that can fit in 7-bit payload size */
|
||||
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT 126
|
||||
/* Maximum length that can fit in 16-bit payload size */
|
||||
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT 65536
|
||||
|
||||
/* Magic 7-bit length to indicate use of 16-bit payload length */
|
||||
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT 126
|
||||
/* Magic 7-bit length to indicate use of 64-bit payload length */
|
||||
#define QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT 127
|
||||
|
||||
/* Bitmasks & shifts for accessing header fields */
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN 0x80
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE 0x0f
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK 0x80
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN 0x7f
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN 7
|
||||
#define QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_HAS_MASK 7
|
||||
|
||||
typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader;
|
||||
|
||||
struct QEMU_PACKED QIOChannelWebsockHeader {
|
||||
unsigned char b0;
|
||||
unsigned char b1;
|
||||
union {
|
||||
struct QEMU_PACKED {
|
||||
uint16_t l16;
|
||||
QIOChannelWebsockMask m16;
|
||||
} s16;
|
||||
struct QEMU_PACKED {
|
||||
uint64_t l64;
|
||||
QIOChannelWebsockMask m64;
|
||||
} s64;
|
||||
QIOChannelWebsockMask m;
|
||||
} u;
|
||||
};
|
||||
|
||||
enum {
|
||||
QIO_CHANNEL_WEBSOCK_OPCODE_CONTINUATION = 0x0,
|
||||
QIO_CHANNEL_WEBSOCK_OPCODE_TEXT_FRAME = 0x1,
|
||||
QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME = 0x2,
|
||||
QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE = 0x8,
|
||||
QIO_CHANNEL_WEBSOCK_OPCODE_PING = 0x9,
|
||||
QIO_CHANNEL_WEBSOCK_OPCODE_PONG = 0xA
|
||||
};
|
||||
|
||||
static char *qio_channel_websock_handshake_entry(const char *handshake,
|
||||
size_t handshake_len,
|
||||
const char *name)
|
||||
{
|
||||
char *begin, *end, *ret = NULL;
|
||||
char *line = g_strdup_printf("%s%s: ",
|
||||
QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM,
|
||||
name);
|
||||
begin = g_strstr_len(handshake, handshake_len, line);
|
||||
if (begin != NULL) {
|
||||
begin += strlen(line);
|
||||
end = g_strstr_len(begin, handshake_len - (begin - handshake),
|
||||
QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
|
||||
if (end != NULL) {
|
||||
ret = g_strndup(begin, end - begin);
|
||||
}
|
||||
}
|
||||
g_free(line);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc,
|
||||
const char *key,
|
||||
Error **errp)
|
||||
{
|
||||
char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
|
||||
QIO_CHANNEL_WEBSOCK_GUID_LEN + 1];
|
||||
char *accept = NULL, *response = NULL;
|
||||
size_t responselen;
|
||||
|
||||
g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1);
|
||||
g_strlcat(combined_key, QIO_CHANNEL_WEBSOCK_GUID,
|
||||
QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
|
||||
QIO_CHANNEL_WEBSOCK_GUID_LEN + 1);
|
||||
|
||||
/* hash and encode it */
|
||||
if (qcrypto_hash_base64(QCRYPTO_HASH_ALG_SHA1,
|
||||
combined_key,
|
||||
QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
|
||||
QIO_CHANNEL_WEBSOCK_GUID_LEN,
|
||||
&accept,
|
||||
errp) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
response = g_strdup_printf(QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE, accept);
|
||||
responselen = strlen(response);
|
||||
buffer_reserve(&ioc->encoutput, responselen);
|
||||
buffer_append(&ioc->encoutput, response, responselen);
|
||||
|
||||
g_free(accept);
|
||||
g_free(response);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
|
||||
const char *line,
|
||||
size_t size,
|
||||
Error **errp)
|
||||
{
|
||||
int ret = -1;
|
||||
char *protocols = qio_channel_websock_handshake_entry(
|
||||
line, size, QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL);
|
||||
char *version = qio_channel_websock_handshake_entry(
|
||||
line, size, QIO_CHANNEL_WEBSOCK_HEADER_VERSION);
|
||||
char *key = qio_channel_websock_handshake_entry(
|
||||
line, size, QIO_CHANNEL_WEBSOCK_HEADER_KEY);
|
||||
|
||||
if (!protocols) {
|
||||
error_setg(errp, "Missing websocket protocol header data");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
error_setg(errp, "Missing websocket version header data");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
error_setg(errp, "Missing websocket key header data");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!g_strrstr(protocols, QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY)) {
|
||||
error_setg(errp, "No '%s' protocol is supported by client '%s'",
|
||||
QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY, protocols);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!g_str_equal(version, QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION)) {
|
||||
error_setg(errp, "Version '%s' is not supported by client '%s'",
|
||||
QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION, version);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (strlen(key) != QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN) {
|
||||
error_setg(errp, "Key length '%zu' was not as expected '%d'",
|
||||
strlen(key), QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = qio_channel_websock_handshake_send_response(ioc, key, errp);
|
||||
|
||||
cleanup:
|
||||
g_free(protocols);
|
||||
g_free(version);
|
||||
g_free(key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
char *handshake_end;
|
||||
ssize_t ret;
|
||||
/* Typical HTTP headers from novnc are 512 bytes, so limiting
|
||||
* total header size to 4096 is easily enough. */
|
||||
size_t want = 4096 - ioc->encinput.offset;
|
||||
buffer_reserve(&ioc->encinput, want);
|
||||
ret = qio_channel_read(ioc->master,
|
||||
(char *)buffer_end(&ioc->encinput), want, errp);
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
ioc->encinput.offset += ret;
|
||||
|
||||
handshake_end = g_strstr_len((char *)ioc->encinput.buffer,
|
||||
ioc->encinput.offset,
|
||||
QIO_CHANNEL_WEBSOCK_HANDSHAKE_END);
|
||||
if (!handshake_end) {
|
||||
if (ioc->encinput.offset >= 4096) {
|
||||
error_setg(errp,
|
||||
"End of headers not found in first 4096 bytes");
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (qio_channel_websock_handshake_process(ioc,
|
||||
(char *)ioc->encinput.buffer,
|
||||
ioc->encinput.offset,
|
||||
errp) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buffer_advance(&ioc->encinput,
|
||||
handshake_end - (char *)ioc->encinput.buffer +
|
||||
strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_END));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer user_data)
|
||||
{
|
||||
QIOTask *task = user_data;
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(
|
||||
qio_task_get_source(task));
|
||||
Error *err = NULL;
|
||||
ssize_t ret;
|
||||
|
||||
ret = qio_channel_write(wioc->master,
|
||||
(char *)wioc->encoutput.buffer,
|
||||
wioc->encoutput.offset,
|
||||
&err);
|
||||
|
||||
if (ret < 0) {
|
||||
trace_qio_channel_websock_handshake_fail(ioc);
|
||||
qio_task_abort(task, err);
|
||||
error_free(err);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
buffer_advance(&wioc->encoutput, ret);
|
||||
if (wioc->encoutput.offset == 0) {
|
||||
trace_qio_channel_websock_handshake_complete(ioc);
|
||||
qio_task_complete(task);
|
||||
return FALSE;
|
||||
}
|
||||
trace_qio_channel_websock_handshake_pending(ioc, G_IO_OUT);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer user_data)
|
||||
{
|
||||
QIOTask *task = user_data;
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(
|
||||
qio_task_get_source(task));
|
||||
Error *err = NULL;
|
||||
int ret;
|
||||
|
||||
ret = qio_channel_websock_handshake_read(wioc, &err);
|
||||
if (ret < 0) {
|
||||
trace_qio_channel_websock_handshake_fail(ioc);
|
||||
qio_task_abort(task, err);
|
||||
error_free(err);
|
||||
return FALSE;
|
||||
}
|
||||
if (ret == 0) {
|
||||
trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN);
|
||||
/* need more data still */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
object_ref(OBJECT(task));
|
||||
trace_qio_channel_websock_handshake_reply(ioc);
|
||||
qio_channel_add_watch(
|
||||
wioc->master,
|
||||
G_IO_OUT,
|
||||
qio_channel_websock_handshake_send,
|
||||
task,
|
||||
(GDestroyNotify)object_unref);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
|
||||
{
|
||||
size_t header_size;
|
||||
union {
|
||||
char buf[QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT];
|
||||
QIOChannelWebsockHeader ws;
|
||||
} header;
|
||||
|
||||
if (!ioc->rawoutput.offset) {
|
||||
return;
|
||||
}
|
||||
|
||||
header.ws.b0 = (1 << QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN) |
|
||||
(QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME &
|
||||
QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE);
|
||||
if (ioc->rawoutput.offset <
|
||||
QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_7_BIT) {
|
||||
header.ws.b1 = (uint8_t)ioc->rawoutput.offset;
|
||||
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
|
||||
} else if (ioc->rawoutput.offset <
|
||||
QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_THRESHOLD_16_BIT) {
|
||||
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT;
|
||||
header.ws.u.s16.l16 = cpu_to_be16((uint16_t)ioc->rawoutput.offset);
|
||||
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT;
|
||||
} else {
|
||||
header.ws.b1 = QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT;
|
||||
header.ws.u.s64.l64 = cpu_to_be64(ioc->rawoutput.offset);
|
||||
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT;
|
||||
}
|
||||
header_size -= QIO_CHANNEL_WEBSOCK_HEADER_LEN_MASK;
|
||||
|
||||
buffer_reserve(&ioc->encoutput, header_size + ioc->rawoutput.offset);
|
||||
buffer_append(&ioc->encoutput, header.buf, header_size);
|
||||
buffer_append(&ioc->encoutput, ioc->rawoutput.buffer,
|
||||
ioc->rawoutput.offset);
|
||||
buffer_reset(&ioc->rawoutput);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
unsigned char opcode, fin, has_mask;
|
||||
size_t header_size;
|
||||
size_t payload_len;
|
||||
QIOChannelWebsockHeader *header =
|
||||
(QIOChannelWebsockHeader *)ioc->encinput.buffer;
|
||||
|
||||
if (ioc->payload_remain) {
|
||||
error_setg(errp,
|
||||
"Decoding header but %zu bytes of payload remain",
|
||||
ioc->payload_remain);
|
||||
return -1;
|
||||
}
|
||||
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT) {
|
||||
/* header not complete */
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
|
||||
fin = (header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_FIN) >>
|
||||
QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_FIN;
|
||||
opcode = header->b0 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_OPCODE;
|
||||
has_mask = (header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_HAS_MASK) >>
|
||||
QIO_CHANNEL_WEBSOCK_HEADER_SHIFT_HAS_MASK;
|
||||
payload_len = header->b1 & QIO_CHANNEL_WEBSOCK_HEADER_FIELD_PAYLOAD_LEN;
|
||||
|
||||
if (opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
|
||||
/* disconnect */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Websocket frame sanity check:
|
||||
* * Websocket fragmentation is not supported.
|
||||
* * All websockets frames sent by a client have to be masked.
|
||||
* * Only binary encoding is supported.
|
||||
*/
|
||||
if (!fin) {
|
||||
error_setg(errp, "websocket fragmentation is not supported");
|
||||
return -1;
|
||||
}
|
||||
if (!has_mask) {
|
||||
error_setg(errp, "websocket frames must be masked");
|
||||
return -1;
|
||||
}
|
||||
if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
|
||||
error_setg(errp, "only binary websocket frames are supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (payload_len < QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT) {
|
||||
ioc->payload_remain = payload_len;
|
||||
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_7_BIT;
|
||||
ioc->mask = header->u.m;
|
||||
} else if (payload_len == QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_16_BIT &&
|
||||
ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT) {
|
||||
ioc->payload_remain = be16_to_cpu(header->u.s16.l16);
|
||||
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_16_BIT;
|
||||
ioc->mask = header->u.s16.m16;
|
||||
} else if (payload_len == QIO_CHANNEL_WEBSOCK_PAYLOAD_LEN_MAGIC_64_BIT &&
|
||||
ioc->encinput.offset >= QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT) {
|
||||
ioc->payload_remain = be64_to_cpu(header->u.s64.l64);
|
||||
header_size = QIO_CHANNEL_WEBSOCK_HEADER_LEN_64_BIT;
|
||||
ioc->mask = header->u.s64.m64;
|
||||
} else {
|
||||
/* header not complete */
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
|
||||
buffer_advance(&ioc->encinput, header_size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_websock_decode_payload(QIOChannelWebsock *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
size_t i;
|
||||
size_t payload_len;
|
||||
uint32_t *payload32;
|
||||
|
||||
if (!ioc->payload_remain) {
|
||||
error_setg(errp,
|
||||
"Decoding payload but no bytes of payload remain");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If we aren't at the end of the payload, then drop
|
||||
* off the last bytes, so we're always multiple of 4
|
||||
* for purpose of unmasking, except at end of payload
|
||||
*/
|
||||
if (ioc->encinput.offset < ioc->payload_remain) {
|
||||
payload_len = ioc->encinput.offset - (ioc->encinput.offset % 4);
|
||||
} else {
|
||||
payload_len = ioc->payload_remain;
|
||||
}
|
||||
if (payload_len == 0) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
|
||||
ioc->payload_remain -= payload_len;
|
||||
|
||||
/* unmask frame */
|
||||
/* process 1 frame (32 bit op) */
|
||||
payload32 = (uint32_t *)ioc->encinput.buffer;
|
||||
for (i = 0; i < payload_len / 4; i++) {
|
||||
payload32[i] ^= ioc->mask.u;
|
||||
}
|
||||
/* process the remaining bytes (if any) */
|
||||
for (i *= 4; i < payload_len; i++) {
|
||||
ioc->encinput.buffer[i] ^= ioc->mask.c[i % 4];
|
||||
}
|
||||
|
||||
buffer_reserve(&ioc->rawinput, payload_len);
|
||||
buffer_append(&ioc->rawinput, ioc->encinput.buffer, payload_len);
|
||||
buffer_advance(&ioc->encinput, payload_len);
|
||||
return payload_len;
|
||||
}
|
||||
|
||||
|
||||
QIOChannelWebsock *
|
||||
qio_channel_websock_new_server(QIOChannel *master)
|
||||
{
|
||||
QIOChannelWebsock *wioc;
|
||||
QIOChannel *ioc;
|
||||
|
||||
wioc = QIO_CHANNEL_WEBSOCK(object_new(TYPE_QIO_CHANNEL_WEBSOCK));
|
||||
ioc = QIO_CHANNEL(wioc);
|
||||
|
||||
wioc->master = master;
|
||||
if (master->features & (1 << QIO_CHANNEL_FEATURE_SHUTDOWN)) {
|
||||
ioc->features |= (1 << QIO_CHANNEL_FEATURE_SHUTDOWN);
|
||||
}
|
||||
object_ref(OBJECT(master));
|
||||
|
||||
trace_qio_channel_websock_new_server(wioc, master);
|
||||
return wioc;
|
||||
}
|
||||
|
||||
void qio_channel_websock_handshake(QIOChannelWebsock *ioc,
|
||||
QIOTaskFunc func,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
QIOTask *task;
|
||||
|
||||
task = qio_task_new(OBJECT(ioc),
|
||||
func,
|
||||
opaque,
|
||||
destroy);
|
||||
|
||||
trace_qio_channel_websock_handshake_start(ioc);
|
||||
trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN);
|
||||
qio_channel_add_watch(ioc->master,
|
||||
G_IO_IN,
|
||||
qio_channel_websock_handshake_io,
|
||||
task,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_websock_finalize(Object *obj)
|
||||
{
|
||||
QIOChannelWebsock *ioc = QIO_CHANNEL_WEBSOCK(obj);
|
||||
|
||||
buffer_free(&ioc->encinput);
|
||||
buffer_free(&ioc->encoutput);
|
||||
buffer_free(&ioc->rawinput);
|
||||
buffer_free(&ioc->rawoutput);
|
||||
object_unref(OBJECT(ioc->master));
|
||||
if (ioc->io_tag) {
|
||||
g_source_remove(ioc->io_tag);
|
||||
}
|
||||
if (ioc->io_err) {
|
||||
error_free(ioc->io_err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
if (ioc->encinput.offset < 4096) {
|
||||
size_t want = 4096 - ioc->encinput.offset;
|
||||
|
||||
buffer_reserve(&ioc->encinput, want);
|
||||
ret = qio_channel_read(ioc->master,
|
||||
(char *)ioc->encinput.buffer +
|
||||
ioc->encinput.offset,
|
||||
want,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
if (ret == 0 &&
|
||||
ioc->encinput.offset == 0) {
|
||||
return 0;
|
||||
}
|
||||
ioc->encinput.offset += ret;
|
||||
}
|
||||
|
||||
if (ioc->payload_remain == 0) {
|
||||
ret = qio_channel_websock_decode_header(ioc, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ret = qio_channel_websock_decode_payload(ioc, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
ssize_t ret;
|
||||
ssize_t done = 0;
|
||||
qio_channel_websock_encode(ioc);
|
||||
|
||||
while (ioc->encoutput.offset > 0) {
|
||||
ret = qio_channel_write(ioc->master,
|
||||
(char *)ioc->encoutput.buffer,
|
||||
ioc->encoutput.offset,
|
||||
errp);
|
||||
if (ret < 0) {
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK &&
|
||||
done > 0) {
|
||||
return done;
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
buffer_advance(&ioc->encoutput, ret);
|
||||
done += ret;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_websock_flush_free(gpointer user_data)
|
||||
{
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(user_data);
|
||||
object_unref(OBJECT(wioc));
|
||||
}
|
||||
|
||||
static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc);
|
||||
|
||||
static gboolean qio_channel_websock_flush(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer user_data)
|
||||
{
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(user_data);
|
||||
ssize_t ret;
|
||||
|
||||
if (condition & G_IO_OUT) {
|
||||
ret = qio_channel_websock_write_wire(wioc, &wioc->io_err);
|
||||
if (ret < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (condition & G_IO_IN) {
|
||||
ret = qio_channel_websock_read_wire(wioc, &wioc->io_err);
|
||||
if (ret < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (ret == 0) {
|
||||
wioc->io_eof = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
qio_channel_websock_set_watch(wioc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void qio_channel_websock_unset_watch(QIOChannelWebsock *ioc)
|
||||
{
|
||||
if (ioc->io_tag) {
|
||||
g_source_remove(ioc->io_tag);
|
||||
ioc->io_tag = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc)
|
||||
{
|
||||
GIOCondition cond = 0;
|
||||
|
||||
qio_channel_websock_unset_watch(ioc);
|
||||
|
||||
if (ioc->io_err) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ioc->encoutput.offset) {
|
||||
cond |= G_IO_OUT;
|
||||
}
|
||||
if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER &&
|
||||
!ioc->io_eof) {
|
||||
cond |= G_IO_IN;
|
||||
}
|
||||
|
||||
if (cond) {
|
||||
object_ref(OBJECT(ioc));
|
||||
ioc->io_tag =
|
||||
qio_channel_add_watch(ioc->master,
|
||||
cond,
|
||||
qio_channel_websock_flush,
|
||||
ioc,
|
||||
qio_channel_websock_flush_free);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_websock_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int **fds,
|
||||
size_t *nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
|
||||
size_t i;
|
||||
ssize_t got = 0;
|
||||
ssize_t ret;
|
||||
|
||||
if (wioc->io_err) {
|
||||
*errp = error_copy(wioc->io_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!wioc->rawinput.offset) {
|
||||
ret = qio_channel_websock_read_wire(QIO_CHANNEL_WEBSOCK(ioc), errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0 ; i < niov ; i++) {
|
||||
size_t want = iov[i].iov_len;
|
||||
if (want > (wioc->rawinput.offset - got)) {
|
||||
want = (wioc->rawinput.offset - got);
|
||||
}
|
||||
|
||||
memcpy(iov[i].iov_base,
|
||||
wioc->rawinput.buffer + got,
|
||||
want);
|
||||
got += want;
|
||||
|
||||
if (want < iov[i].iov_len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buffer_advance(&wioc->rawinput, got);
|
||||
qio_channel_websock_set_watch(wioc);
|
||||
return got;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t qio_channel_websock_writev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
int *fds,
|
||||
size_t nfds,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
|
||||
size_t i;
|
||||
ssize_t done = 0;
|
||||
ssize_t ret;
|
||||
|
||||
if (wioc->io_err) {
|
||||
*errp = error_copy(wioc->io_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (wioc->io_eof) {
|
||||
error_setg(errp, "%s", "Broken pipe");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < niov; i++) {
|
||||
size_t want = iov[i].iov_len;
|
||||
if ((want + wioc->rawoutput.offset) > QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
|
||||
want = (QIO_CHANNEL_WEBSOCK_MAX_BUFFER - wioc->rawoutput.offset);
|
||||
}
|
||||
if (want == 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
buffer_reserve(&wioc->rawoutput, want);
|
||||
buffer_append(&wioc->rawoutput, iov[i].iov_base, want);
|
||||
done += want;
|
||||
if (want < iov[i].iov_len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
ret = qio_channel_websock_write_wire(wioc, errp);
|
||||
if (ret < 0 &&
|
||||
ret != QIO_CHANNEL_ERR_BLOCK) {
|
||||
qio_channel_websock_unset_watch(wioc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
qio_channel_websock_set_watch(wioc);
|
||||
|
||||
if (done == 0) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
static int qio_channel_websock_set_blocking(QIOChannel *ioc,
|
||||
bool enabled,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
|
||||
|
||||
qio_channel_set_blocking(wioc->master, enabled, errp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qio_channel_websock_set_delay(QIOChannel *ioc,
|
||||
bool enabled)
|
||||
{
|
||||
QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc);
|
||||
|
||||
qio_channel_set_delay(tioc->master, enabled);
|
||||
}
|
||||
|
||||
static void qio_channel_websock_set_cork(QIOChannel *ioc,
|
||||
bool enabled)
|
||||
{
|
||||
QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc);
|
||||
|
||||
qio_channel_set_cork(tioc->master, enabled);
|
||||
}
|
||||
|
||||
static int qio_channel_websock_shutdown(QIOChannel *ioc,
|
||||
QIOChannelShutdown how,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelWebsock *tioc = QIO_CHANNEL_WEBSOCK(ioc);
|
||||
|
||||
return qio_channel_shutdown(tioc->master, how, errp);
|
||||
}
|
||||
|
||||
static int qio_channel_websock_close(QIOChannel *ioc,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
|
||||
|
||||
return qio_channel_close(wioc->master, errp);
|
||||
}
|
||||
|
||||
typedef struct QIOChannelWebsockSource QIOChannelWebsockSource;
|
||||
struct QIOChannelWebsockSource {
|
||||
GSource parent;
|
||||
QIOChannelWebsock *wioc;
|
||||
GIOCondition condition;
|
||||
};
|
||||
|
||||
static gboolean
|
||||
qio_channel_websock_source_prepare(GSource *source,
|
||||
gint *timeout)
|
||||
{
|
||||
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
|
||||
GIOCondition cond = 0;
|
||||
*timeout = -1;
|
||||
|
||||
if (wsource->wioc->rawinput.offset) {
|
||||
cond |= G_IO_IN;
|
||||
}
|
||||
if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
|
||||
cond |= G_IO_OUT;
|
||||
}
|
||||
|
||||
return cond & wsource->condition;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
qio_channel_websock_source_check(GSource *source)
|
||||
{
|
||||
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
|
||||
GIOCondition cond = 0;
|
||||
|
||||
if (wsource->wioc->rawinput.offset) {
|
||||
cond |= G_IO_IN;
|
||||
}
|
||||
if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
|
||||
cond |= G_IO_OUT;
|
||||
}
|
||||
|
||||
return cond & wsource->condition;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
qio_channel_websock_source_dispatch(GSource *source,
|
||||
GSourceFunc callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
QIOChannelFunc func = (QIOChannelFunc)callback;
|
||||
QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
|
||||
GIOCondition cond = 0;
|
||||
|
||||
if (wsource->wioc->rawinput.offset) {
|
||||
cond |= G_IO_IN;
|
||||
}
|
||||
if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
|
||||
cond |= G_IO_OUT;
|
||||
}
|
||||
|
||||
return (*func)(QIO_CHANNEL(wsource->wioc),
|
||||
(cond & wsource->condition),
|
||||
user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
qio_channel_websock_source_finalize(GSource *source)
|
||||
{
|
||||
QIOChannelWebsockSource *ssource = (QIOChannelWebsockSource *)source;
|
||||
|
||||
object_unref(OBJECT(ssource->wioc));
|
||||
}
|
||||
|
||||
GSourceFuncs qio_channel_websock_source_funcs = {
|
||||
qio_channel_websock_source_prepare,
|
||||
qio_channel_websock_source_check,
|
||||
qio_channel_websock_source_dispatch,
|
||||
qio_channel_websock_source_finalize
|
||||
};
|
||||
|
||||
static GSource *qio_channel_websock_create_watch(QIOChannel *ioc,
|
||||
GIOCondition condition)
|
||||
{
|
||||
QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
|
||||
QIOChannelWebsockSource *ssource;
|
||||
GSource *source;
|
||||
|
||||
source = g_source_new(&qio_channel_websock_source_funcs,
|
||||
sizeof(QIOChannelWebsockSource));
|
||||
ssource = (QIOChannelWebsockSource *)source;
|
||||
|
||||
ssource->wioc = wioc;
|
||||
object_ref(OBJECT(wioc));
|
||||
|
||||
ssource->condition = condition;
|
||||
|
||||
qio_channel_websock_set_watch(wioc);
|
||||
return source;
|
||||
}
|
||||
|
||||
static void qio_channel_websock_class_init(ObjectClass *klass,
|
||||
void *class_data G_GNUC_UNUSED)
|
||||
{
|
||||
QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
|
||||
|
||||
ioc_klass->io_writev = qio_channel_websock_writev;
|
||||
ioc_klass->io_readv = qio_channel_websock_readv;
|
||||
ioc_klass->io_set_blocking = qio_channel_websock_set_blocking;
|
||||
ioc_klass->io_set_cork = qio_channel_websock_set_cork;
|
||||
ioc_klass->io_set_delay = qio_channel_websock_set_delay;
|
||||
ioc_klass->io_close = qio_channel_websock_close;
|
||||
ioc_klass->io_shutdown = qio_channel_websock_shutdown;
|
||||
ioc_klass->io_create_watch = qio_channel_websock_create_watch;
|
||||
}
|
||||
|
||||
static const TypeInfo qio_channel_websock_info = {
|
||||
.parent = TYPE_QIO_CHANNEL,
|
||||
.name = TYPE_QIO_CHANNEL_WEBSOCK,
|
||||
.instance_size = sizeof(QIOChannelWebsock),
|
||||
.instance_finalize = qio_channel_websock_finalize,
|
||||
.class_init = qio_channel_websock_class_init,
|
||||
};
|
||||
|
||||
static void qio_channel_websock_register_types(void)
|
||||
{
|
||||
type_register_static(&qio_channel_websock_info);
|
||||
}
|
||||
|
||||
type_init(qio_channel_websock_register_types);
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* 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);
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* QEMU I/O task
|
||||
*
|
||||
* 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/task.h"
|
||||
#include "qemu/thread.h"
|
||||
#include "trace.h"
|
||||
|
||||
struct QIOTask {
|
||||
Object *source;
|
||||
QIOTaskFunc func;
|
||||
gpointer opaque;
|
||||
GDestroyNotify destroy;
|
||||
};
|
||||
|
||||
|
||||
QIOTask *qio_task_new(Object *source,
|
||||
QIOTaskFunc func,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
QIOTask *task;
|
||||
|
||||
task = g_new0(QIOTask, 1);
|
||||
|
||||
task->source = source;
|
||||
object_ref(source);
|
||||
task->func = func;
|
||||
task->opaque = opaque;
|
||||
task->destroy = destroy;
|
||||
|
||||
trace_qio_task_new(task, source, func, opaque);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
static void qio_task_free(QIOTask *task)
|
||||
{
|
||||
if (task->destroy) {
|
||||
task->destroy(task->opaque);
|
||||
}
|
||||
object_unref(task->source);
|
||||
|
||||
g_free(task);
|
||||
}
|
||||
|
||||
|
||||
struct QIOTaskThreadData {
|
||||
QIOTask *task;
|
||||
QIOTaskWorker worker;
|
||||
gpointer opaque;
|
||||
GDestroyNotify destroy;
|
||||
Error *err;
|
||||
int ret;
|
||||
};
|
||||
|
||||
|
||||
static gboolean gio_task_thread_result(gpointer opaque)
|
||||
{
|
||||
struct QIOTaskThreadData *data = opaque;
|
||||
|
||||
trace_qio_task_thread_result(data->task);
|
||||
if (data->ret == 0) {
|
||||
qio_task_complete(data->task);
|
||||
} else {
|
||||
qio_task_abort(data->task, data->err);
|
||||
}
|
||||
|
||||
error_free(data->err);
|
||||
if (data->destroy) {
|
||||
data->destroy(data->opaque);
|
||||
}
|
||||
|
||||
g_free(data);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gpointer qio_task_thread_worker(gpointer opaque)
|
||||
{
|
||||
struct QIOTaskThreadData *data = opaque;
|
||||
|
||||
trace_qio_task_thread_run(data->task);
|
||||
data->ret = data->worker(data->task, &data->err, data->opaque);
|
||||
if (data->ret < 0 && data->err == NULL) {
|
||||
error_setg(&data->err, "Task worker failed but did not set an error");
|
||||
}
|
||||
|
||||
/* We're running in the background thread, and must only
|
||||
* ever report the task results in the main event loop
|
||||
* thread. So we schedule an idle callback to report
|
||||
* the worker results
|
||||
*/
|
||||
trace_qio_task_thread_exit(data->task);
|
||||
g_idle_add(gio_task_thread_result, data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void qio_task_run_in_thread(QIOTask *task,
|
||||
QIOTaskWorker worker,
|
||||
gpointer opaque,
|
||||
GDestroyNotify destroy)
|
||||
{
|
||||
struct QIOTaskThreadData *data = g_new0(struct QIOTaskThreadData, 1);
|
||||
QemuThread thread;
|
||||
|
||||
data->task = task;
|
||||
data->worker = worker;
|
||||
data->opaque = opaque;
|
||||
data->destroy = destroy;
|
||||
|
||||
trace_qio_task_thread_start(task, worker, opaque);
|
||||
qemu_thread_create(&thread,
|
||||
"io-task-worker",
|
||||
qio_task_thread_worker,
|
||||
data,
|
||||
QEMU_THREAD_DETACHED);
|
||||
}
|
||||
|
||||
|
||||
void qio_task_complete(QIOTask *task)
|
||||
{
|
||||
task->func(task->source, NULL, task->opaque);
|
||||
trace_qio_task_complete(task);
|
||||
qio_task_free(task);
|
||||
}
|
||||
|
||||
void qio_task_abort(QIOTask *task,
|
||||
Error *err)
|
||||
{
|
||||
task->func(task->source, err, task->opaque);
|
||||
trace_qio_task_abort(task);
|
||||
qio_task_free(task);
|
||||
}
|
||||
|
||||
|
||||
Object *qio_task_get_source(QIOTask *task)
|
||||
{
|
||||
object_ref(task->source);
|
||||
return task->source;
|
||||
}
|
|
@ -61,6 +61,15 @@ case $line in
|
|||
value=${line#*=}
|
||||
echo "#define $name $value"
|
||||
;;
|
||||
HAVE_*=y) # configuration
|
||||
name=${line%=*}
|
||||
echo "#define $name 1"
|
||||
;;
|
||||
HAVE_*=*) # configuration
|
||||
name=${line%=*}
|
||||
value=${line#*=}
|
||||
echo "#define $name $value"
|
||||
;;
|
||||
ARCH=*) # configuration
|
||||
arch=${line#*=}
|
||||
arch_name=`echo $arch | LC_ALL=C tr '[a-z]' '[A-Z]'`
|
||||
|
|
|
@ -24,6 +24,14 @@ test-cutils
|
|||
test-hbitmap
|
||||
test-int128
|
||||
test-iov
|
||||
test-io-channel-buffer
|
||||
test-io-channel-command
|
||||
test-io-channel-command.fifo
|
||||
test-io-channel-file
|
||||
test-io-channel-file.txt
|
||||
test-io-channel-socket
|
||||
test-io-channel-tls
|
||||
test-io-task
|
||||
test-mul64
|
||||
test-opts-visitor
|
||||
test-qapi-event.[ch]
|
||||
|
|
|
@ -84,6 +84,12 @@ check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF)
|
|||
check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
|
||||
check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
|
||||
check-unit-y += tests/test-timed-average$(EXESUF)
|
||||
check-unit-y += tests/test-io-task$(EXESUF)
|
||||
check-unit-y += tests/test-io-channel-socket$(EXESUF)
|
||||
check-unit-y += tests/test-io-channel-file$(EXESUF)
|
||||
check-unit-$(CONFIG_GNUTLS) += tests/test-io-channel-tls$(EXESUF)
|
||||
check-unit-y += tests/test-io-channel-command$(EXESUF)
|
||||
check-unit-y += tests/test-io-channel-buffer$(EXESUF)
|
||||
|
||||
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
|
||||
|
||||
|
@ -381,6 +387,7 @@ test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
|
|||
$(test-qom-obj-y)
|
||||
test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
|
||||
test-block-obj-y = $(block-obj-y) $(test-crypto-obj-y)
|
||||
test-io-obj-y = $(io-obj-y) $(test-crypto-obj-y)
|
||||
|
||||
tests/check-qint$(EXESUF): tests/check-qint.o $(test-util-obj-y)
|
||||
tests/check-qstring$(EXESUF): tests/check-qstring.o $(test-util-obj-y)
|
||||
|
@ -469,6 +476,18 @@ tests/test-crypto-tlscredsx509$(EXESUF): tests/test-crypto-tlscredsx509.o \
|
|||
tests/test-crypto-tlssession.o-cflags := $(TASN1_CFLAGS)
|
||||
tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
|
||||
tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-obj-y)
|
||||
tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
|
||||
tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
|
||||
tests/io-channel-helpers.o $(test-io-obj-y)
|
||||
tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \
|
||||
tests/io-channel-helpers.o $(test-io-obj-y)
|
||||
tests/test-io-channel-tls$(EXESUF): tests/test-io-channel-tls.o \
|
||||
tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o \
|
||||
tests/io-channel-helpers.o $(test-io-obj-y)
|
||||
tests/test-io-channel-command$(EXESUF): tests/test-io-channel-command.o \
|
||||
tests/io-channel-helpers.o $(test-io-obj-y)
|
||||
tests/test-io-channel-buffer$(EXESUF): tests/test-io-channel-buffer.o \
|
||||
tests/io-channel-helpers.o $(test-io-obj-y)
|
||||
|
||||
libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
|
||||
libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* QEMU I/O channel test helpers
|
||||
*
|
||||
* 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-helpers.h"
|
||||
|
||||
struct QIOChannelTest {
|
||||
QIOChannel *src;
|
||||
QIOChannel *dst;
|
||||
bool blocking;
|
||||
size_t len;
|
||||
size_t niov;
|
||||
char *input;
|
||||
struct iovec *inputv;
|
||||
char *output;
|
||||
struct iovec *outputv;
|
||||
Error *writeerr;
|
||||
Error *readerr;
|
||||
};
|
||||
|
||||
|
||||
static void test_skip_iovec(struct iovec **iov,
|
||||
size_t *niov,
|
||||
size_t skip,
|
||||
struct iovec *old)
|
||||
{
|
||||
size_t offset = 0;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < *niov; i++) {
|
||||
if (skip < (*iov)[i].iov_len) {
|
||||
old->iov_len = (*iov)[i].iov_len;
|
||||
old->iov_base = (*iov)[i].iov_base;
|
||||
|
||||
(*iov)[i].iov_len -= skip;
|
||||
(*iov)[i].iov_base += skip;
|
||||
break;
|
||||
} else {
|
||||
skip -= (*iov)[i].iov_len;
|
||||
|
||||
if (i == 0 && old->iov_base) {
|
||||
(*iov)[i].iov_len = old->iov_len;
|
||||
(*iov)[i].iov_base = old->iov_base;
|
||||
old->iov_len = 0;
|
||||
old->iov_base = NULL;
|
||||
}
|
||||
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
*iov = *iov + offset;
|
||||
*niov -= offset;
|
||||
}
|
||||
|
||||
|
||||
/* This thread sends all data using iovecs */
|
||||
static gpointer test_io_thread_writer(gpointer opaque)
|
||||
{
|
||||
QIOChannelTest *data = opaque;
|
||||
struct iovec *iov = data->inputv;
|
||||
size_t niov = data->niov;
|
||||
struct iovec old = { 0 };
|
||||
|
||||
qio_channel_set_blocking(data->src, data->blocking, NULL);
|
||||
|
||||
while (niov) {
|
||||
ssize_t ret;
|
||||
ret = qio_channel_writev(data->src,
|
||||
iov,
|
||||
niov,
|
||||
&data->writeerr);
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
if (data->blocking) {
|
||||
error_setg(&data->writeerr,
|
||||
"Unexpected I/O blocking");
|
||||
break;
|
||||
} else {
|
||||
qio_channel_wait(data->src,
|
||||
G_IO_OUT);
|
||||
continue;
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
break;
|
||||
} else if (ret == 0) {
|
||||
error_setg(&data->writeerr,
|
||||
"Unexpected zero length write");
|
||||
break;
|
||||
}
|
||||
|
||||
test_skip_iovec(&iov, &niov, ret, &old);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* This thread receives all data using iovecs */
|
||||
static gpointer test_io_thread_reader(gpointer opaque)
|
||||
{
|
||||
QIOChannelTest *data = opaque;
|
||||
struct iovec *iov = data->outputv;
|
||||
size_t niov = data->niov;
|
||||
struct iovec old = { 0 };
|
||||
|
||||
qio_channel_set_blocking(data->dst, data->blocking, NULL);
|
||||
|
||||
while (niov) {
|
||||
ssize_t ret;
|
||||
|
||||
ret = qio_channel_readv(data->dst,
|
||||
iov,
|
||||
niov,
|
||||
&data->readerr);
|
||||
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
if (data->blocking) {
|
||||
error_setg(&data->writeerr,
|
||||
"Unexpected I/O blocking");
|
||||
break;
|
||||
} else {
|
||||
qio_channel_wait(data->dst,
|
||||
G_IO_IN);
|
||||
continue;
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
break;
|
||||
} else if (ret == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
test_skip_iovec(&iov, &niov, ret, &old);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
QIOChannelTest *qio_channel_test_new(void)
|
||||
{
|
||||
QIOChannelTest *data = g_new0(QIOChannelTest, 1);
|
||||
size_t i;
|
||||
size_t offset;
|
||||
|
||||
|
||||
/* We'll send 1 MB of data */
|
||||
#define CHUNK_COUNT 250
|
||||
#define CHUNK_LEN 4194
|
||||
|
||||
data->len = CHUNK_COUNT * CHUNK_LEN;
|
||||
data->input = g_new0(char, data->len);
|
||||
data->output = g_new0(gchar, data->len);
|
||||
|
||||
/* Fill input with a pattern */
|
||||
for (i = 0; i < data->len; i += CHUNK_LEN) {
|
||||
memset(data->input + i, (i / CHUNK_LEN), CHUNK_LEN);
|
||||
}
|
||||
|
||||
/* We'll split the data across a bunch of IO vecs */
|
||||
data->niov = CHUNK_COUNT;
|
||||
data->inputv = g_new0(struct iovec, data->niov);
|
||||
data->outputv = g_new0(struct iovec, data->niov);
|
||||
|
||||
for (i = 0, offset = 0; i < data->niov; i++, offset += CHUNK_LEN) {
|
||||
data->inputv[i].iov_base = data->input + offset;
|
||||
data->outputv[i].iov_base = data->output + offset;
|
||||
data->inputv[i].iov_len = CHUNK_LEN;
|
||||
data->outputv[i].iov_len = CHUNK_LEN;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void qio_channel_test_run_threads(QIOChannelTest *test,
|
||||
bool blocking,
|
||||
QIOChannel *src,
|
||||
QIOChannel *dst)
|
||||
{
|
||||
GThread *reader, *writer;
|
||||
|
||||
test->src = src;
|
||||
test->dst = dst;
|
||||
test->blocking = blocking;
|
||||
|
||||
reader = g_thread_new("reader",
|
||||
test_io_thread_reader,
|
||||
test);
|
||||
writer = g_thread_new("writer",
|
||||
test_io_thread_writer,
|
||||
test);
|
||||
|
||||
g_thread_join(reader);
|
||||
g_thread_join(writer);
|
||||
|
||||
test->dst = test->src = NULL;
|
||||
}
|
||||
|
||||
|
||||
void qio_channel_test_run_writer(QIOChannelTest *test,
|
||||
QIOChannel *src)
|
||||
{
|
||||
test->src = src;
|
||||
test_io_thread_writer(test);
|
||||
test->src = NULL;
|
||||
}
|
||||
|
||||
|
||||
void qio_channel_test_run_reader(QIOChannelTest *test,
|
||||
QIOChannel *dst)
|
||||
{
|
||||
test->dst = dst;
|
||||
test_io_thread_reader(test);
|
||||
test->dst = NULL;
|
||||
}
|
||||
|
||||
|
||||
void qio_channel_test_validate(QIOChannelTest *test)
|
||||
{
|
||||
g_assert_cmpint(memcmp(test->input,
|
||||
test->output,
|
||||
test->len), ==, 0);
|
||||
g_assert(test->readerr == NULL);
|
||||
g_assert(test->writeerr == NULL);
|
||||
|
||||
g_free(test->inputv);
|
||||
g_free(test->outputv);
|
||||
g_free(test->input);
|
||||
g_free(test->output);
|
||||
g_free(test);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* QEMU I/O channel test helpers
|
||||
*
|
||||
* 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"
|
||||
|
||||
#ifndef TEST_IO_CHANNEL_HELPERS
|
||||
#define TEST_IO_CHANNEL_HELPERS
|
||||
|
||||
typedef struct QIOChannelTest QIOChannelTest;
|
||||
|
||||
QIOChannelTest *qio_channel_test_new(void);
|
||||
|
||||
void qio_channel_test_run_threads(QIOChannelTest *test,
|
||||
bool blocking,
|
||||
QIOChannel *src,
|
||||
QIOChannel *dst);
|
||||
|
||||
void qio_channel_test_run_writer(QIOChannelTest *test,
|
||||
QIOChannel *src);
|
||||
void qio_channel_test_run_reader(QIOChannelTest *test,
|
||||
QIOChannel *dst);
|
||||
|
||||
void qio_channel_test_validate(QIOChannelTest *test);
|
||||
|
||||
#endif /* TEST_IO_CHANNEL_HELPERS */
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* QEMU I/O channel buffer test
|
||||
*
|
||||
* 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-buffer.h"
|
||||
#include "io-channel-helpers.h"
|
||||
|
||||
|
||||
static void test_io_channel_buf(void)
|
||||
{
|
||||
QIOChannelBuffer *buf;
|
||||
QIOChannelTest *test;
|
||||
|
||||
buf = qio_channel_buffer_new(0);
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_writer(test, QIO_CHANNEL(buf));
|
||||
buf->offset = 0;
|
||||
qio_channel_test_run_reader(test, QIO_CHANNEL(buf));
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
object_unref(OBJECT(buf));
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func("/io/channel/buf", test_io_channel_buf);
|
||||
return g_test_run();
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* QEMU I/O channel command test
|
||||
*
|
||||
* 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-command.h"
|
||||
#include "io-channel-helpers.h"
|
||||
|
||||
#ifndef WIN32
|
||||
static void test_io_channel_command_fifo(bool async)
|
||||
{
|
||||
#define TEST_FIFO "tests/test-io-channel-command.fifo"
|
||||
QIOChannel *src, *dst;
|
||||
QIOChannelTest *test;
|
||||
char *srcfifo = g_strdup_printf("PIPE:%s,wronly", TEST_FIFO);
|
||||
char *dstfifo = g_strdup_printf("PIPE:%s,rdonly", TEST_FIFO);
|
||||
const char *srcargv[] = {
|
||||
"/bin/socat", "-", srcfifo, NULL,
|
||||
};
|
||||
const char *dstargv[] = {
|
||||
"/bin/socat", dstfifo, "-", NULL,
|
||||
};
|
||||
|
||||
unlink(TEST_FIFO);
|
||||
if (access("/bin/socat", X_OK) < 0) {
|
||||
return; /* Pretend success if socat is not present */
|
||||
}
|
||||
if (mkfifo(TEST_FIFO, 0600) < 0) {
|
||||
abort();
|
||||
}
|
||||
src = QIO_CHANNEL(qio_channel_command_new_spawn(srcargv,
|
||||
O_WRONLY,
|
||||
&error_abort));
|
||||
dst = QIO_CHANNEL(qio_channel_command_new_spawn(dstargv,
|
||||
O_RDONLY,
|
||||
&error_abort));
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, async, src, dst);
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
object_unref(OBJECT(src));
|
||||
object_unref(OBJECT(dst));
|
||||
|
||||
g_free(srcfifo);
|
||||
g_free(dstfifo);
|
||||
unlink(TEST_FIFO);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_command_fifo_async(void)
|
||||
{
|
||||
test_io_channel_command_fifo(true);
|
||||
}
|
||||
|
||||
static void test_io_channel_command_fifo_sync(void)
|
||||
{
|
||||
test_io_channel_command_fifo(false);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_command_echo(bool async)
|
||||
{
|
||||
QIOChannel *ioc;
|
||||
QIOChannelTest *test;
|
||||
const char *socatargv[] = {
|
||||
"/bin/socat", "-", "-", NULL,
|
||||
};
|
||||
|
||||
if (access("/bin/socat", X_OK) < 0) {
|
||||
return; /* Pretend success if socat is not present */
|
||||
}
|
||||
|
||||
ioc = QIO_CHANNEL(qio_channel_command_new_spawn(socatargv,
|
||||
O_RDWR,
|
||||
&error_abort));
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, async, ioc, ioc);
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
object_unref(OBJECT(ioc));
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_command_echo_async(void)
|
||||
{
|
||||
test_io_channel_command_echo(true);
|
||||
}
|
||||
|
||||
static void test_io_channel_command_echo_sync(void)
|
||||
{
|
||||
test_io_channel_command_echo(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
#ifndef WIN32
|
||||
g_test_add_func("/io/channel/command/fifo/sync",
|
||||
test_io_channel_command_fifo_sync);
|
||||
g_test_add_func("/io/channel/command/fifo/async",
|
||||
test_io_channel_command_fifo_async);
|
||||
g_test_add_func("/io/channel/command/echo/sync",
|
||||
test_io_channel_command_echo_sync);
|
||||
g_test_add_func("/io/channel/command/echo/async",
|
||||
test_io_channel_command_echo_async);
|
||||
#endif
|
||||
|
||||
return g_test_run();
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* QEMU I/O channel file test
|
||||
*
|
||||
* 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-file.h"
|
||||
#include "io-channel-helpers.h"
|
||||
|
||||
|
||||
static void test_io_channel_file(void)
|
||||
{
|
||||
QIOChannel *src, *dst;
|
||||
QIOChannelTest *test;
|
||||
|
||||
#define TEST_FILE "tests/test-io-channel-file.txt"
|
||||
unlink(TEST_FILE);
|
||||
src = QIO_CHANNEL(qio_channel_file_new_path(
|
||||
TEST_FILE,
|
||||
O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600,
|
||||
&error_abort));
|
||||
dst = QIO_CHANNEL(qio_channel_file_new_path(
|
||||
TEST_FILE,
|
||||
O_RDONLY | O_BINARY, 0,
|
||||
&error_abort));
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_writer(test, src);
|
||||
qio_channel_test_run_reader(test, dst);
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
unlink(TEST_FILE);
|
||||
object_unref(OBJECT(src));
|
||||
object_unref(OBJECT(dst));
|
||||
}
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
static void test_io_channel_pipe(bool async)
|
||||
{
|
||||
QIOChannel *src, *dst;
|
||||
QIOChannelTest *test;
|
||||
int fd[2];
|
||||
|
||||
if (pipe(fd) < 0) {
|
||||
perror("pipe");
|
||||
abort();
|
||||
}
|
||||
|
||||
src = QIO_CHANNEL(qio_channel_file_new_fd(fd[1]));
|
||||
dst = QIO_CHANNEL(qio_channel_file_new_fd(fd[0]));
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, async, src, dst);
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
object_unref(OBJECT(src));
|
||||
object_unref(OBJECT(dst));
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_pipe_async(void)
|
||||
{
|
||||
test_io_channel_pipe(true);
|
||||
}
|
||||
|
||||
static void test_io_channel_pipe_sync(void)
|
||||
{
|
||||
test_io_channel_pipe(false);
|
||||
}
|
||||
#endif /* ! _WIN32 */
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func("/io/channel/file", test_io_channel_file);
|
||||
#ifndef _WIN32
|
||||
g_test_add_func("/io/channel/pipe/sync", test_io_channel_pipe_sync);
|
||||
g_test_add_func("/io/channel/pipe/async", test_io_channel_pipe_async);
|
||||
#endif
|
||||
return g_test_run();
|
||||
}
|
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
* QEMU I/O channel sockets test
|
||||
*
|
||||
* 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-socket.h"
|
||||
#include "io-channel-helpers.h"
|
||||
#ifdef HAVE_IFADDRS_H
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
static int check_protocol_support(bool *has_ipv4, bool *has_ipv6)
|
||||
{
|
||||
#ifdef HAVE_IFADDRS_H
|
||||
struct ifaddrs *ifaddr = NULL, *ifa;
|
||||
struct addrinfo hints = { 0 };
|
||||
struct addrinfo *ai = NULL;
|
||||
int gaierr;
|
||||
|
||||
*has_ipv4 = *has_ipv6 = false;
|
||||
|
||||
if (getifaddrs(&ifaddr) < 0) {
|
||||
g_printerr("Failed to lookup interface addresses: %s\n",
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (!ifa->ifa_addr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ifa->ifa_addr->sa_family == AF_INET) {
|
||||
*has_ipv4 = true;
|
||||
}
|
||||
if (ifa->ifa_addr->sa_family == AF_INET6) {
|
||||
*has_ipv6 = true;
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
|
||||
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
|
||||
hints.ai_family = AF_INET6;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
gaierr = getaddrinfo("::1", NULL, &hints, &ai);
|
||||
if (gaierr != 0) {
|
||||
if (gaierr == EAI_ADDRFAMILY ||
|
||||
gaierr == EAI_FAMILY ||
|
||||
gaierr == EAI_NONAME) {
|
||||
*has_ipv6 = false;
|
||||
} else {
|
||||
g_printerr("Failed to resolve ::1 address: %s\n",
|
||||
gai_strerror(gaierr));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(ai);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
*has_ipv4 = *has_ipv6 = false;
|
||||
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_set_socket_bufs(QIOChannel *src,
|
||||
QIOChannel *dst)
|
||||
{
|
||||
int buflen = 64 * 1024;
|
||||
|
||||
/*
|
||||
* Make the socket buffers small so that we see
|
||||
* the effects of partial reads/writes
|
||||
*/
|
||||
setsockopt(((QIOChannelSocket *)src)->fd,
|
||||
SOL_SOCKET, SO_SNDBUF,
|
||||
(char *)&buflen,
|
||||
sizeof(buflen));
|
||||
|
||||
setsockopt(((QIOChannelSocket *)dst)->fd,
|
||||
SOL_SOCKET, SO_SNDBUF,
|
||||
(char *)&buflen,
|
||||
sizeof(buflen));
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_setup_sync(SocketAddress *listen_addr,
|
||||
SocketAddress *connect_addr,
|
||||
QIOChannel **src,
|
||||
QIOChannel **dst)
|
||||
{
|
||||
QIOChannelSocket *lioc;
|
||||
|
||||
lioc = qio_channel_socket_new();
|
||||
qio_channel_socket_listen_sync(lioc, listen_addr, &error_abort);
|
||||
|
||||
if (listen_addr->type == SOCKET_ADDRESS_KIND_INET) {
|
||||
SocketAddress *laddr = qio_channel_socket_get_local_address(
|
||||
lioc, &error_abort);
|
||||
|
||||
g_free(connect_addr->u.inet->port);
|
||||
connect_addr->u.inet->port = g_strdup(laddr->u.inet->port);
|
||||
|
||||
qapi_free_SocketAddress(laddr);
|
||||
}
|
||||
|
||||
*src = QIO_CHANNEL(qio_channel_socket_new());
|
||||
qio_channel_socket_connect_sync(
|
||||
QIO_CHANNEL_SOCKET(*src), connect_addr, &error_abort);
|
||||
qio_channel_set_delay(*src, false);
|
||||
|
||||
*dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
|
||||
g_assert(*dst);
|
||||
|
||||
test_io_channel_set_socket_bufs(*src, *dst);
|
||||
|
||||
object_unref(OBJECT(lioc));
|
||||
}
|
||||
|
||||
|
||||
struct TestIOChannelData {
|
||||
bool err;
|
||||
GMainLoop *loop;
|
||||
};
|
||||
|
||||
|
||||
static void test_io_channel_complete(Object *src,
|
||||
Error *err,
|
||||
gpointer opaque)
|
||||
{
|
||||
struct TestIOChannelData *data = opaque;
|
||||
data->err = err != NULL;
|
||||
g_main_loop_quit(data->loop);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_setup_async(SocketAddress *listen_addr,
|
||||
SocketAddress *connect_addr,
|
||||
QIOChannel **src,
|
||||
QIOChannel **dst)
|
||||
{
|
||||
QIOChannelSocket *lioc;
|
||||
struct TestIOChannelData data;
|
||||
|
||||
data.loop = g_main_loop_new(g_main_context_default(),
|
||||
TRUE);
|
||||
|
||||
lioc = qio_channel_socket_new();
|
||||
qio_channel_socket_listen_async(
|
||||
lioc, listen_addr,
|
||||
test_io_channel_complete, &data, NULL);
|
||||
|
||||
g_main_loop_run(data.loop);
|
||||
g_main_context_iteration(g_main_context_default(), FALSE);
|
||||
|
||||
g_assert(!data.err);
|
||||
|
||||
if (listen_addr->type == SOCKET_ADDRESS_KIND_INET) {
|
||||
SocketAddress *laddr = qio_channel_socket_get_local_address(
|
||||
lioc, &error_abort);
|
||||
|
||||
g_free(connect_addr->u.inet->port);
|
||||
connect_addr->u.inet->port = g_strdup(laddr->u.inet->port);
|
||||
|
||||
qapi_free_SocketAddress(laddr);
|
||||
}
|
||||
|
||||
*src = QIO_CHANNEL(qio_channel_socket_new());
|
||||
|
||||
qio_channel_socket_connect_async(
|
||||
QIO_CHANNEL_SOCKET(*src), connect_addr,
|
||||
test_io_channel_complete, &data, NULL);
|
||||
|
||||
g_main_loop_run(data.loop);
|
||||
g_main_context_iteration(g_main_context_default(), FALSE);
|
||||
|
||||
g_assert(!data.err);
|
||||
|
||||
*dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
|
||||
g_assert(*dst);
|
||||
|
||||
qio_channel_set_delay(*src, false);
|
||||
test_io_channel_set_socket_bufs(*src, *dst);
|
||||
|
||||
object_unref(OBJECT(lioc));
|
||||
|
||||
g_main_loop_unref(data.loop);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel(bool async,
|
||||
SocketAddress *listen_addr,
|
||||
SocketAddress *connect_addr)
|
||||
{
|
||||
QIOChannel *src, *dst;
|
||||
QIOChannelTest *test;
|
||||
if (async) {
|
||||
test_io_channel_setup_async(listen_addr, connect_addr, &src, &dst);
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, true, src, dst);
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
object_unref(OBJECT(src));
|
||||
object_unref(OBJECT(dst));
|
||||
|
||||
test_io_channel_setup_async(listen_addr, connect_addr, &src, &dst);
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, false, src, dst);
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
object_unref(OBJECT(src));
|
||||
object_unref(OBJECT(dst));
|
||||
} else {
|
||||
test_io_channel_setup_sync(listen_addr, connect_addr, &src, &dst);
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, true, src, dst);
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
object_unref(OBJECT(src));
|
||||
object_unref(OBJECT(dst));
|
||||
|
||||
test_io_channel_setup_sync(listen_addr, connect_addr, &src, &dst);
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, false, src, dst);
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
object_unref(OBJECT(src));
|
||||
object_unref(OBJECT(dst));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_ipv4(bool async)
|
||||
{
|
||||
SocketAddress *listen_addr = g_new0(SocketAddress, 1);
|
||||
SocketAddress *connect_addr = g_new0(SocketAddress, 1);
|
||||
|
||||
listen_addr->type = SOCKET_ADDRESS_KIND_INET;
|
||||
listen_addr->u.inet = g_new0(InetSocketAddress, 1);
|
||||
listen_addr->u.inet->host = g_strdup("0.0.0.0");
|
||||
listen_addr->u.inet->port = NULL; /* Auto-select */
|
||||
|
||||
connect_addr->type = SOCKET_ADDRESS_KIND_INET;
|
||||
connect_addr->u.inet = g_new0(InetSocketAddress, 1);
|
||||
connect_addr->u.inet->host = g_strdup("127.0.0.1");
|
||||
connect_addr->u.inet->port = NULL; /* Filled in later */
|
||||
|
||||
test_io_channel(async, listen_addr, connect_addr);
|
||||
|
||||
qapi_free_SocketAddress(listen_addr);
|
||||
qapi_free_SocketAddress(connect_addr);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_ipv4_sync(void)
|
||||
{
|
||||
return test_io_channel_ipv4(false);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_ipv4_async(void)
|
||||
{
|
||||
return test_io_channel_ipv4(true);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_ipv6(bool async)
|
||||
{
|
||||
SocketAddress *listen_addr = g_new0(SocketAddress, 1);
|
||||
SocketAddress *connect_addr = g_new0(SocketAddress, 1);
|
||||
|
||||
listen_addr->type = SOCKET_ADDRESS_KIND_INET;
|
||||
listen_addr->u.inet = g_new0(InetSocketAddress, 1);
|
||||
listen_addr->u.inet->host = g_strdup("::");
|
||||
listen_addr->u.inet->port = NULL; /* Auto-select */
|
||||
|
||||
connect_addr->type = SOCKET_ADDRESS_KIND_INET;
|
||||
connect_addr->u.inet = g_new0(InetSocketAddress, 1);
|
||||
connect_addr->u.inet->host = g_strdup("::1");
|
||||
connect_addr->u.inet->port = NULL; /* Filled in later */
|
||||
|
||||
test_io_channel(async, listen_addr, connect_addr);
|
||||
|
||||
qapi_free_SocketAddress(listen_addr);
|
||||
qapi_free_SocketAddress(connect_addr);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_ipv6_sync(void)
|
||||
{
|
||||
return test_io_channel_ipv6(false);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_ipv6_async(void)
|
||||
{
|
||||
return test_io_channel_ipv6(true);
|
||||
}
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
static void test_io_channel_unix(bool async)
|
||||
{
|
||||
SocketAddress *listen_addr = g_new0(SocketAddress, 1);
|
||||
SocketAddress *connect_addr = g_new0(SocketAddress, 1);
|
||||
|
||||
#define TEST_SOCKET "test-io-channel-socket.sock"
|
||||
listen_addr->type = SOCKET_ADDRESS_KIND_UNIX;
|
||||
listen_addr->u.q_unix = g_new0(UnixSocketAddress, 1);
|
||||
listen_addr->u.q_unix->path = g_strdup(TEST_SOCKET);
|
||||
|
||||
connect_addr->type = SOCKET_ADDRESS_KIND_UNIX;
|
||||
connect_addr->u.q_unix = g_new0(UnixSocketAddress, 1);
|
||||
connect_addr->u.q_unix->path = g_strdup(TEST_SOCKET);
|
||||
|
||||
test_io_channel(async, listen_addr, connect_addr);
|
||||
|
||||
qapi_free_SocketAddress(listen_addr);
|
||||
qapi_free_SocketAddress(connect_addr);
|
||||
unlink(TEST_SOCKET);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_unix_sync(void)
|
||||
{
|
||||
return test_io_channel_unix(false);
|
||||
}
|
||||
|
||||
|
||||
static void test_io_channel_unix_async(void)
|
||||
{
|
||||
return test_io_channel_unix(true);
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool has_ipv4, has_ipv6;
|
||||
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
/* We're creating actual IPv4/6 sockets, so we should
|
||||
* check if the host running tests actually supports
|
||||
* each protocol to avoid breaking tests on machines
|
||||
* with either IPv4 or IPv6 disabled.
|
||||
*/
|
||||
if (check_protocol_support(&has_ipv4, &has_ipv6) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (has_ipv4) {
|
||||
g_test_add_func("/io/channel/socket/ipv4-sync",
|
||||
test_io_channel_ipv4_sync);
|
||||
g_test_add_func("/io/channel/socket/ipv4-async",
|
||||
test_io_channel_ipv4_async);
|
||||
}
|
||||
if (has_ipv6) {
|
||||
g_test_add_func("/io/channel/socket/ipv6-sync",
|
||||
test_io_channel_ipv6_sync);
|
||||
g_test_add_func("/io/channel/socket/ipv6-async",
|
||||
test_io_channel_ipv6_async);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
g_test_add_func("/io/channel/socket/unix-sync",
|
||||
test_io_channel_unix_sync);
|
||||
g_test_add_func("/io/channel/socket/unix-async",
|
||||
test_io_channel_unix_async);
|
||||
#endif /* _WIN32 */
|
||||
|
||||
return g_test_run();
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
* QEMU I/O channel TLS test
|
||||
*
|
||||
* 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.1 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/>.
|
||||
*
|
||||
* Author: Daniel P. Berrange <berrange@redhat.com>
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "config-host.h"
|
||||
#include "crypto-tls-x509-helpers.h"
|
||||
#include "io/channel-tls.h"
|
||||
#include "io/channel-socket.h"
|
||||
#include "io-channel-helpers.h"
|
||||
#include "crypto/tlscredsx509.h"
|
||||
#include "qemu/acl.h"
|
||||
#include "qom/object_interfaces.h"
|
||||
|
||||
#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
|
||||
|
||||
#define WORKDIR "tests/test-io-channel-tls-work/"
|
||||
#define KEYFILE WORKDIR "key-ctx.pem"
|
||||
|
||||
struct QIOChannelTLSTestData {
|
||||
const char *servercacrt;
|
||||
const char *clientcacrt;
|
||||
const char *servercrt;
|
||||
const char *clientcrt;
|
||||
bool expectServerFail;
|
||||
bool expectClientFail;
|
||||
const char *hostname;
|
||||
const char *const *wildcards;
|
||||
};
|
||||
|
||||
struct QIOChannelTLSHandshakeData {
|
||||
bool finished;
|
||||
bool failed;
|
||||
};
|
||||
|
||||
static void test_tls_handshake_done(Object *source,
|
||||
Error *err,
|
||||
gpointer opaque)
|
||||
{
|
||||
struct QIOChannelTLSHandshakeData *data = opaque;
|
||||
|
||||
data->finished = true;
|
||||
data->failed = err != NULL;
|
||||
}
|
||||
|
||||
|
||||
static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
|
||||
const char *certdir,
|
||||
Error **errp)
|
||||
{
|
||||
Object *parent = object_get_objects_root();
|
||||
Object *creds = object_new_with_props(
|
||||
TYPE_QCRYPTO_TLS_CREDS_X509,
|
||||
parent,
|
||||
(endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
|
||||
"testtlscredsserver" : "testtlscredsclient"),
|
||||
errp,
|
||||
"endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
|
||||
"server" : "client"),
|
||||
"dir", certdir,
|
||||
"verify-peer", "yes",
|
||||
/* We skip initial sanity checks here because we
|
||||
* want to make sure that problems are being
|
||||
* detected at the TLS session validation stage,
|
||||
* and the test-crypto-tlscreds test already
|
||||
* validate the sanity check code.
|
||||
*/
|
||||
"sanity-check", "no",
|
||||
NULL
|
||||
);
|
||||
|
||||
if (*errp) {
|
||||
return NULL;
|
||||
}
|
||||
return QCRYPTO_TLS_CREDS(creds);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This tests validation checking of peer certificates
|
||||
*
|
||||
* This is replicating the checks that are done for an
|
||||
* active TLS session after handshake completes. To
|
||||
* simulate that we create our TLS contexts, skipping
|
||||
* sanity checks. When then get a socketpair, and
|
||||
* initiate a TLS session across them. Finally do
|
||||
* do actual cert validation tests
|
||||
*/
|
||||
static void test_io_channel_tls(const void *opaque)
|
||||
{
|
||||
struct QIOChannelTLSTestData *data =
|
||||
(struct QIOChannelTLSTestData *)opaque;
|
||||
QCryptoTLSCreds *clientCreds;
|
||||
QCryptoTLSCreds *serverCreds;
|
||||
QIOChannelTLS *clientChanTLS;
|
||||
QIOChannelTLS *serverChanTLS;
|
||||
QIOChannelSocket *clientChanSock;
|
||||
QIOChannelSocket *serverChanSock;
|
||||
qemu_acl *acl;
|
||||
const char * const *wildcards;
|
||||
int channel[2];
|
||||
struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
|
||||
struct QIOChannelTLSHandshakeData serverHandshake = { false, false };
|
||||
Error *err = NULL;
|
||||
QIOChannelTest *test;
|
||||
GMainContext *mainloop;
|
||||
|
||||
/* We'll use this for our fake client-server connection */
|
||||
g_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == 0);
|
||||
|
||||
#define CLIENT_CERT_DIR "tests/test-crypto-tlssession-client/"
|
||||
#define SERVER_CERT_DIR "tests/test-crypto-tlssession-server/"
|
||||
mkdir(CLIENT_CERT_DIR, 0700);
|
||||
mkdir(SERVER_CERT_DIR, 0700);
|
||||
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
|
||||
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
|
||||
|
||||
g_assert(link(data->servercacrt,
|
||||
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
|
||||
g_assert(link(data->servercrt,
|
||||
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
|
||||
g_assert(link(KEYFILE,
|
||||
SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
|
||||
|
||||
g_assert(link(data->clientcacrt,
|
||||
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
|
||||
g_assert(link(data->clientcrt,
|
||||
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
|
||||
g_assert(link(KEYFILE,
|
||||
CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
|
||||
|
||||
clientCreds = test_tls_creds_create(
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
|
||||
CLIENT_CERT_DIR,
|
||||
&err);
|
||||
g_assert(clientCreds != NULL);
|
||||
|
||||
serverCreds = test_tls_creds_create(
|
||||
QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
|
||||
SERVER_CERT_DIR,
|
||||
&err);
|
||||
g_assert(serverCreds != NULL);
|
||||
|
||||
acl = qemu_acl_init("channeltlsacl");
|
||||
qemu_acl_reset(acl);
|
||||
wildcards = data->wildcards;
|
||||
while (wildcards && *wildcards) {
|
||||
qemu_acl_append(acl, 0, *wildcards);
|
||||
wildcards++;
|
||||
}
|
||||
|
||||
clientChanSock = qio_channel_socket_new_fd(
|
||||
channel[0], &err);
|
||||
g_assert(clientChanSock != NULL);
|
||||
serverChanSock = qio_channel_socket_new_fd(
|
||||
channel[1], &err);
|
||||
g_assert(serverChanSock != NULL);
|
||||
|
||||
/*
|
||||
* We have an evil loop to do the handshake in a single
|
||||
* thread, so we need these non-blocking to avoid deadlock
|
||||
* of ourselves
|
||||
*/
|
||||
qio_channel_set_blocking(QIO_CHANNEL(clientChanSock), false, NULL);
|
||||
qio_channel_set_blocking(QIO_CHANNEL(serverChanSock), false, NULL);
|
||||
|
||||
/* Now the real part of the test, setup the sessions */
|
||||
clientChanTLS = qio_channel_tls_new_client(
|
||||
QIO_CHANNEL(clientChanSock), clientCreds,
|
||||
data->hostname, &err);
|
||||
g_assert(clientChanTLS != NULL);
|
||||
|
||||
serverChanTLS = qio_channel_tls_new_server(
|
||||
QIO_CHANNEL(serverChanSock), serverCreds,
|
||||
"channeltlsacl", &err);
|
||||
g_assert(serverChanTLS != NULL);
|
||||
|
||||
qio_channel_tls_handshake(clientChanTLS,
|
||||
test_tls_handshake_done,
|
||||
&clientHandshake,
|
||||
NULL);
|
||||
qio_channel_tls_handshake(serverChanTLS,
|
||||
test_tls_handshake_done,
|
||||
&serverHandshake,
|
||||
NULL);
|
||||
|
||||
/*
|
||||
* Finally we loop around & around doing handshake on each
|
||||
* session until we get an error, or the handshake completes.
|
||||
* This relies on the socketpair being nonblocking to avoid
|
||||
* deadlocking ourselves upon handshake
|
||||
*/
|
||||
mainloop = g_main_context_default();
|
||||
do {
|
||||
g_main_context_iteration(mainloop, TRUE);
|
||||
} while (!clientHandshake.finished &&
|
||||
!serverHandshake.finished);
|
||||
|
||||
g_assert(clientHandshake.failed == data->expectClientFail);
|
||||
g_assert(serverHandshake.failed == data->expectServerFail);
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, false,
|
||||
QIO_CHANNEL(clientChanTLS),
|
||||
QIO_CHANNEL(serverChanTLS));
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
test = qio_channel_test_new();
|
||||
qio_channel_test_run_threads(test, true,
|
||||
QIO_CHANNEL(clientChanTLS),
|
||||
QIO_CHANNEL(serverChanTLS));
|
||||
qio_channel_test_validate(test);
|
||||
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
|
||||
unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
|
||||
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
|
||||
unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
|
||||
|
||||
rmdir(CLIENT_CERT_DIR);
|
||||
rmdir(SERVER_CERT_DIR);
|
||||
|
||||
object_unparent(OBJECT(serverCreds));
|
||||
object_unparent(OBJECT(clientCreds));
|
||||
|
||||
object_unref(OBJECT(serverChanTLS));
|
||||
object_unref(OBJECT(clientChanTLS));
|
||||
|
||||
object_unref(OBJECT(serverChanSock));
|
||||
object_unref(OBJECT(clientChanSock));
|
||||
|
||||
close(channel[0]);
|
||||
close(channel[1]);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
|
||||
|
||||
mkdir(WORKDIR, 0700);
|
||||
|
||||
test_tls_init(KEYFILE);
|
||||
|
||||
# define TEST_CHANNEL(name, caCrt, \
|
||||
serverCrt, clientCrt, \
|
||||
expectServerFail, expectClientFail, \
|
||||
hostname, wildcards) \
|
||||
struct QIOChannelTLSTestData name = { \
|
||||
caCrt, caCrt, serverCrt, clientCrt, \
|
||||
expectServerFail, expectClientFail, \
|
||||
hostname, wildcards \
|
||||
}; \
|
||||
g_test_add_data_func("/qio/channel/tls/" # name, \
|
||||
&name, test_io_channel_tls);
|
||||
|
||||
/* A perfect CA, perfect client & perfect server */
|
||||
|
||||
/* Basic:CA:critical */
|
||||
TLS_ROOT_REQ(cacertreq,
|
||||
"UK", "qemu CA", NULL, NULL, NULL, NULL,
|
||||
true, true, true,
|
||||
true, true, GNUTLS_KEY_KEY_CERT_SIGN,
|
||||
false, false, NULL, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(servercertreq, cacertreq,
|
||||
"UK", "qemu.org", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, true,
|
||||
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
|
||||
true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
|
||||
0, 0);
|
||||
TLS_CERT_REQ(clientcertreq, cacertreq,
|
||||
"UK", "qemu", NULL, NULL, NULL, NULL,
|
||||
true, true, false,
|
||||
true, true,
|
||||
GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
|
||||
true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
|
||||
0, 0);
|
||||
|
||||
const char *const wildcards[] = {
|
||||
"C=UK,CN=qemu*",
|
||||
NULL,
|
||||
};
|
||||
TEST_CHANNEL(basic, cacertreq.filename, servercertreq.filename,
|
||||
clientcertreq.filename, false, false,
|
||||
"qemu.org", wildcards);
|
||||
|
||||
ret = g_test_run();
|
||||
|
||||
test_tls_discard_cert(&clientcertreq);
|
||||
test_tls_discard_cert(&servercertreq);
|
||||
test_tls_discard_cert(&cacertreq);
|
||||
|
||||
test_tls_cleanup(KEYFILE);
|
||||
rmdir(WORKDIR);
|
||||
|
||||
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* QEMU I/O task tests
|
||||
*
|
||||
* 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 <glib.h>
|
||||
|
||||
#include "io/task.h"
|
||||
|
||||
#define TYPE_DUMMY "qemu:dummy"
|
||||
|
||||
typedef struct DummyObject DummyObject;
|
||||
typedef struct DummyObjectClass DummyObjectClass;
|
||||
|
||||
struct DummyObject {
|
||||
Object parent;
|
||||
};
|
||||
|
||||
struct DummyObjectClass {
|
||||
ObjectClass parent;
|
||||
};
|
||||
|
||||
static const TypeInfo dummy_info = {
|
||||
.parent = TYPE_OBJECT,
|
||||
.name = TYPE_DUMMY,
|
||||
.instance_size = sizeof(DummyObject),
|
||||
.class_size = sizeof(DummyObjectClass),
|
||||
};
|
||||
|
||||
struct TestTaskData {
|
||||
Object *source;
|
||||
Error *err;
|
||||
bool freed;
|
||||
};
|
||||
|
||||
|
||||
static void task_callback(Object *source,
|
||||
Error *err,
|
||||
gpointer opaque)
|
||||
{
|
||||
struct TestTaskData *data = opaque;
|
||||
|
||||
data->source = source;
|
||||
data->err = err;
|
||||
}
|
||||
|
||||
|
||||
static void test_task_complete(void)
|
||||
{
|
||||
QIOTask *task;
|
||||
Object *obj = object_new(TYPE_DUMMY);
|
||||
Object *src;
|
||||
struct TestTaskData data = { NULL, NULL, false };
|
||||
|
||||
task = qio_task_new(obj, task_callback, &data, NULL);
|
||||
src = qio_task_get_source(task);
|
||||
|
||||
qio_task_complete(task);
|
||||
|
||||
g_assert(obj == src);
|
||||
|
||||
object_unref(obj);
|
||||
object_unref(src);
|
||||
|
||||
g_assert(data.source == obj);
|
||||
g_assert(data.err == NULL);
|
||||
g_assert(data.freed == false);
|
||||
}
|
||||
|
||||
|
||||
static void task_data_free(gpointer opaque)
|
||||
{
|
||||
struct TestTaskData *data = opaque;
|
||||
|
||||
data->freed = true;
|
||||
}
|
||||
|
||||
|
||||
static void test_task_data_free(void)
|
||||
{
|
||||
QIOTask *task;
|
||||
Object *obj = object_new(TYPE_DUMMY);
|
||||
struct TestTaskData data = { NULL, NULL, false };
|
||||
|
||||
task = qio_task_new(obj, task_callback, &data, task_data_free);
|
||||
|
||||
qio_task_complete(task);
|
||||
|
||||
object_unref(obj);
|
||||
|
||||
g_assert(data.source == obj);
|
||||
g_assert(data.err == NULL);
|
||||
g_assert(data.freed == true);
|
||||
}
|
||||
|
||||
|
||||
static void test_task_error(void)
|
||||
{
|
||||
QIOTask *task;
|
||||
Object *obj = object_new(TYPE_DUMMY);
|
||||
struct TestTaskData data = { NULL, NULL, false };
|
||||
Error *err = NULL;
|
||||
|
||||
task = qio_task_new(obj, task_callback, &data, NULL);
|
||||
|
||||
error_setg(&err, "Some error");
|
||||
|
||||
qio_task_abort(task, err);
|
||||
|
||||
error_free(err);
|
||||
object_unref(obj);
|
||||
|
||||
g_assert(data.source == obj);
|
||||
g_assert(data.err == err);
|
||||
g_assert(data.freed == false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct TestThreadWorkerData {
|
||||
Object *source;
|
||||
Error *err;
|
||||
bool fail;
|
||||
GThread *worker;
|
||||
GThread *complete;
|
||||
GMainLoop *loop;
|
||||
};
|
||||
|
||||
static int test_task_thread_worker(QIOTask *task,
|
||||
Error **errp,
|
||||
gpointer opaque)
|
||||
{
|
||||
struct TestThreadWorkerData *data = opaque;
|
||||
|
||||
data->worker = g_thread_self();
|
||||
|
||||
if (data->fail) {
|
||||
error_setg(errp, "Testing fail");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void test_task_thread_callback(Object *source,
|
||||
Error *err,
|
||||
gpointer opaque)
|
||||
{
|
||||
struct TestThreadWorkerData *data = opaque;
|
||||
|
||||
data->source = source;
|
||||
data->err = err;
|
||||
|
||||
data->complete = g_thread_self();
|
||||
|
||||
g_main_loop_quit(data->loop);
|
||||
}
|
||||
|
||||
|
||||
static void test_task_thread_complete(void)
|
||||
{
|
||||
QIOTask *task;
|
||||
Object *obj = object_new(TYPE_DUMMY);
|
||||
struct TestThreadWorkerData data = { 0 };
|
||||
GThread *self;
|
||||
|
||||
data.loop = g_main_loop_new(g_main_context_default(),
|
||||
TRUE);
|
||||
|
||||
task = qio_task_new(obj,
|
||||
test_task_thread_callback,
|
||||
&data,
|
||||
NULL);
|
||||
|
||||
qio_task_run_in_thread(task,
|
||||
test_task_thread_worker,
|
||||
&data,
|
||||
NULL);
|
||||
|
||||
g_main_loop_run(data.loop);
|
||||
|
||||
g_main_loop_unref(data.loop);
|
||||
object_unref(obj);
|
||||
|
||||
g_assert(data.source == obj);
|
||||
g_assert(data.err == NULL);
|
||||
|
||||
self = g_thread_self();
|
||||
|
||||
/* Make sure the test_task_thread_worker actually got
|
||||
* run in a different thread */
|
||||
g_assert(data.worker != self);
|
||||
|
||||
/* And that the test_task_thread_callback got rnu in
|
||||
* the main loop thread (ie this one) */
|
||||
g_assert(data.complete == self);
|
||||
}
|
||||
|
||||
|
||||
static void test_task_thread_error(void)
|
||||
{
|
||||
QIOTask *task;
|
||||
Object *obj = object_new(TYPE_DUMMY);
|
||||
struct TestThreadWorkerData data = { 0 };
|
||||
GThread *self;
|
||||
|
||||
data.loop = g_main_loop_new(g_main_context_default(),
|
||||
TRUE);
|
||||
data.fail = true;
|
||||
|
||||
task = qio_task_new(obj,
|
||||
test_task_thread_callback,
|
||||
&data,
|
||||
NULL);
|
||||
|
||||
qio_task_run_in_thread(task,
|
||||
test_task_thread_worker,
|
||||
&data,
|
||||
NULL);
|
||||
|
||||
g_main_loop_run(data.loop);
|
||||
|
||||
g_main_loop_unref(data.loop);
|
||||
object_unref(obj);
|
||||
|
||||
g_assert(data.source == obj);
|
||||
g_assert(data.err != NULL);
|
||||
|
||||
self = g_thread_self();
|
||||
|
||||
/* Make sure the test_task_thread_worker actually got
|
||||
* run in a different thread */
|
||||
g_assert(data.worker != self);
|
||||
|
||||
/* And that the test_task_thread_callback got rnu in
|
||||
* the main loop thread (ie this one) */
|
||||
g_assert(data.complete == self);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
module_call_init(MODULE_INIT_QOM);
|
||||
type_register_static(&dummy_info);
|
||||
g_test_add_func("/crypto/task/complete", test_task_complete);
|
||||
g_test_add_func("/crypto/task/datafree", test_task_data_free);
|
||||
g_test_add_func("/crypto/task/error", test_task_error);
|
||||
g_test_add_func("/crypto/task/thread_complete", test_task_thread_complete);
|
||||
g_test_add_func("/crypto/task/thread_error", test_task_thread_error);
|
||||
return g_test_run();
|
||||
}
|
56
trace-events
56
trace-events
|
@ -1808,3 +1808,59 @@ user_handle_signal(void *env, int target_sig) "env=%p signal %d"
|
|||
user_host_signal(void *env, int host_sig, int target_sig) "env=%p signal %d (target %d("
|
||||
user_queue_signal(void *env, int target_sig) "env=%p signal %d"
|
||||
user_s390x_restore_sigregs(void *env, uint64_t sc_psw_addr, uint64_t env_psw_addr) "env=%p frame psw.addr "PRIx64 " current psw.addr "PRIx64""
|
||||
|
||||
# io/task.c
|
||||
qio_task_new(void *task, void *source, void *func, void *opaque) "Task new task=%p source=%p func=%p opaque=%p"
|
||||
qio_task_complete(void *task) "Task complete task=%p"
|
||||
qio_task_abort(void *task) "Task abort task=%p"
|
||||
qio_task_thread_start(void *task, void *worker, void *opaque) "Task thread start task=%p worker=%p opaque=%p"
|
||||
qio_task_thread_run(void *task) "Task thread run task=%p"
|
||||
qio_task_thread_exit(void *task) "Task thread exit task=%p"
|
||||
qio_task_thread_result(void *task) "Task thread result task=%p"
|
||||
|
||||
# io/channel-socket.c
|
||||
qio_channel_socket_new(void *ioc) "Socket new ioc=%p"
|
||||
qio_channel_socket_new_fd(void *ioc, int fd) "Socket new ioc=%p fd=%d"
|
||||
qio_channel_socket_connect_sync(void *ioc, void *addr) "Socket connect sync ioc=%p addr=%p"
|
||||
qio_channel_socket_connect_async(void *ioc, void *addr) "Socket connect async ioc=%p addr=%p"
|
||||
qio_channel_socket_connect_fail(void *ioc) "Socket connect fail ioc=%p"
|
||||
qio_channel_socket_connect_complete(void *ioc, int fd) "Socket connect complete ioc=%p fd=%d"
|
||||
qio_channel_socket_listen_sync(void *ioc, void *addr) "Socket listen sync ioc=%p addr=%p"
|
||||
qio_channel_socket_listen_async(void *ioc, void *addr) "Socket listen async ioc=%p addr=%p"
|
||||
qio_channel_socket_listen_fail(void *ioc) "Socket listen fail ioc=%p"
|
||||
qio_channel_socket_listen_complete(void *ioc, int fd) "Socket listen complete ioc=%p fd=%d"
|
||||
qio_channel_socket_dgram_sync(void *ioc, void *localAddr, void *remoteAddr) "Socket dgram sync ioc=%p localAddr=%p remoteAddr=%p"
|
||||
qio_channel_socket_dgram_async(void *ioc, void *localAddr, void *remoteAddr) "Socket dgram async ioc=%p localAddr=%p remoteAddr=%p"
|
||||
qio_channel_socket_dgram_fail(void *ioc) "Socket dgram fail ioc=%p"
|
||||
qio_channel_socket_dgram_complete(void *ioc, int fd) "Socket dgram complete ioc=%p fd=%d"
|
||||
qio_channel_socket_accept(void *ioc) "Socket accept start ioc=%p"
|
||||
qio_channel_socket_accept_fail(void *ioc) "Socket accept fail ioc=%p"
|
||||
qio_channel_socket_accept_complete(void *ioc, void *cioc, int fd) "Socket accept complete ioc=%p cioc=%p fd=%d"
|
||||
|
||||
# io/channel-file.c
|
||||
qio_channel_file_new_fd(void *ioc, int fd) "File new fd ioc=%p fd=%d"
|
||||
qio_channel_file_new_path(void *ioc, const char *path, int flags, int mode, int fd) "File new fd ioc=%p path=%s flags=%d mode=%d fd=%d"
|
||||
|
||||
# io/channel-tls.c
|
||||
qio_channel_tls_new_client(void *ioc, void *master, void *creds, const char *hostname) "TLS new client ioc=%p master=%p creds=%p hostname=%s"
|
||||
qio_channel_tls_new_server(void *ioc, void *master, void *creds, const char *aclname) "TLS new client ioc=%p master=%p creds=%p acltname=%s"
|
||||
qio_channel_tls_handshake_start(void *ioc) "TLS handshake start ioc=%p"
|
||||
qio_channel_tls_handshake_pending(void *ioc, int status) "TLS handshake pending ioc=%p status=%d"
|
||||
qio_channel_tls_handshake_fail(void *ioc) "TLS handshake fail ioc=%p"
|
||||
qio_channel_tls_handshake_complete(void *ioc) "TLS handshake complete ioc=%p"
|
||||
qio_channel_tls_credentials_allow(void *ioc) "TLS credentials allow ioc=%p"
|
||||
qio_channel_tls_credentials_deny(void *ioc) "TLS credentials deny ioc=%p"
|
||||
|
||||
# io/channel-websock.c
|
||||
qio_channel_websock_new_server(void *ioc, void *master) "Websock new client ioc=%p master=%p"
|
||||
qio_channel_websock_handshake_start(void *ioc) "Websock handshake start ioc=%p"
|
||||
qio_channel_websock_handshake_pending(void *ioc, int status) "Websock handshake pending ioc=%p status=%d"
|
||||
qio_channel_websock_handshake_reply(void *ioc) "Websock handshake reply ioc=%p"
|
||||
qio_channel_websock_handshake_fail(void *ioc) "Websock handshake fail ioc=%p"
|
||||
qio_channel_websock_handshake_complete(void *ioc) "Websock handshake complete ioc=%p"
|
||||
|
||||
# io/channel-command.c
|
||||
qio_channel_command_new_pid(void *ioc, int writefd, int readfd, int pid) "Command new pid ioc=%p writefd=%d readfd=%d pid=%d"
|
||||
qio_channel_command_new_spawn(void *ioc, const char *binary, int flags) "Command new spawn ioc=%p binary=%s flags=%d"
|
||||
qio_channel_command_abort(void *ioc, int pid) "Command abort ioc=%p pid=%d"
|
||||
qio_channel_command_wait(void *ioc, int pid, int ret, int status) "Command abort ioc=%p pid=%d ret=%d status=%d"
|
||||
|
|
|
@ -1086,7 +1086,7 @@ socket_sockaddr_to_address_unix(struct sockaddr_storage *sa,
|
|||
}
|
||||
#endif /* WIN32 */
|
||||
|
||||
static SocketAddress *
|
||||
SocketAddress *
|
||||
socket_sockaddr_to_address(struct sockaddr_storage *sa,
|
||||
socklen_t salen,
|
||||
Error **errp)
|
||||
|
|
Loading…
Reference in New Issue