[Spice-devel] [PATCH spice-server 3/5] server: Limit the access to SpiceDataHeader of messages - only via red_channel.
Alon Levy
alevy at redhat.com
Mon Jan 9 00:20:13 PST 2012
On Sun, Jan 08, 2012 at 10:53:31AM +0200, Yonit Halperin wrote:
ACK
> ---
> server/inputs_channel.c | 10 +++++++---
> server/main_channel.c | 8 ++++++--
> server/red_channel.c | 34 +++++++++++++++++++++++++---------
> server/red_channel.h | 24 ++++++++++--------------
> server/red_tunnel_worker.c | 43 ++++++++++++++++++++++---------------------
> server/red_worker.c | 9 ++++-----
> server/smartcard.c | 18 +++++++++++-------
> server/spicevmc.c | 24 ++++++++++++++----------
> 8 files changed, 99 insertions(+), 71 deletions(-)
>
> diff --git a/server/inputs_channel.c b/server/inputs_channel.c
> index c8b42e3..a987478 100644
> --- a/server/inputs_channel.c
> +++ b/server/inputs_channel.c
> @@ -168,18 +168,22 @@ const VDAgentMouseState *inputs_get_mouse_state(void)
> return &g_inputs_channel->mouse_state;
> }
>
> -static uint8_t *inputs_channel_alloc_msg_rcv_buf(RedChannelClient *rcc, SpiceDataHeader *msg_header)
> +static uint8_t *inputs_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size)
> {
> InputsChannel *inputs_channel = SPICE_CONTAINEROF(rcc->channel, InputsChannel, base);
>
> - if (msg_header->size > RECEIVE_BUF_SIZE) {
> + if (size > RECEIVE_BUF_SIZE) {
> red_printf("error: too large incoming message");
> return NULL;
> }
> return inputs_channel->recv_buf;
> }
>
> -static void inputs_channel_release_msg_rcv_buf(RedChannelClient *rcc, SpiceDataHeader *msg_header,
> +static void inputs_channel_release_msg_rcv_buf(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size,
> uint8_t *msg)
> {
> }
> diff --git a/server/main_channel.c b/server/main_channel.c
> index 4b5b669..b55bf00 100644
> --- a/server/main_channel.c
> +++ b/server/main_channel.c
> @@ -852,14 +852,18 @@ static int main_channel_handle_parsed(RedChannelClient *rcc, uint32_t size, uint
> return TRUE;
> }
>
> -static uint8_t *main_channel_alloc_msg_rcv_buf(RedChannelClient *rcc, SpiceDataHeader *msg_header)
> +static uint8_t *main_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size)
> {
> MainChannel *main_chan = SPICE_CONTAINEROF(rcc->channel, MainChannel, base);
>
> return main_chan->recv_buf;
> }
>
> -static void main_channel_release_msg_rcv_buf(RedChannelClient *rcc, SpiceDataHeader *msg_header,
> +static void main_channel_release_msg_rcv_buf(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size,
> uint8_t *msg)
> {
> }
> diff --git a/server/red_channel.c b/server/red_channel.c
> index 86387cb..3a9f254 100644
> --- a/server/red_channel.c
> +++ b/server/red_channel.c
> @@ -103,7 +103,9 @@ static void red_peer_handle_incoming(RedsStream *stream, IncomingHandler *handle
>
> if (handler->msg_pos < handler->header.size) {
> if (!handler->msg) {
> - handler->msg = handler->cb->alloc_msg_buf(handler->opaque, &handler->header);
> + handler->msg = handler->cb->alloc_msg_buf(handler->opaque,
> + handler->header.type,
> + handler->header.size);
> if (handler->msg == NULL) {
> red_printf("ERROR: channel refused to allocate buffer.");
> handler->cb->on_error(handler->opaque);
> @@ -115,7 +117,10 @@ static void red_peer_handle_incoming(RedsStream *stream, IncomingHandler *handle
> handler->msg + handler->msg_pos,
> handler->header.size - handler->msg_pos);
> if (bytes_read == -1) {
> - handler->cb->release_msg_buf(handler->opaque, &handler->header, handler->msg);
> + handler->cb->release_msg_buf(handler->opaque,
> + handler->header.type,
> + handler->header.size,
> + handler->msg);
> handler->cb->on_error(handler->opaque);
> return;
> }
> @@ -131,7 +136,9 @@ static void red_peer_handle_incoming(RedsStream *stream, IncomingHandler *handle
> SPICE_VERSION_MINOR, &parsed_size, &parsed_free);
> if (parsed == NULL) {
> red_printf("failed to parse message type %d", handler->header.type);
> - handler->cb->release_msg_buf(handler->opaque, &handler->header, handler->msg);
> + handler->cb->release_msg_buf(handler->opaque, handler->header.type,
> + handler->header.size,
> + handler->msg);
> handler->cb->on_error(handler->opaque);
> return;
> }
> @@ -139,11 +146,16 @@ static void red_peer_handle_incoming(RedsStream *stream, IncomingHandler *handle
> handler->header.type, parsed);
> parsed_free(parsed);
> } else {
> - ret_handle = handler->cb->handle_message(handler->opaque, &handler->header,
> - handler->msg);
> + ret_handle = handler->cb->handle_message(handler->opaque,
> + handler->header.type,
> + handler->header.size,
> + handler->msg);
> }
> handler->msg_pos = 0;
> - handler->cb->release_msg_buf(handler->opaque, &handler->header, handler->msg);
> + handler->cb->release_msg_buf(handler->opaque,
> + handler->header.type,
> + handler->header.size,
> + handler->msg);
> handler->msg = NULL;
> handler->header_pos = 0;
>
> @@ -577,7 +589,10 @@ RedChannel *red_channel_create_dummy(int size, uint32_t type, uint32_t id)
> return channel;
> }
>
> -static int do_nothing_handle_message(RedChannelClient *rcc, SpiceDataHeader *header, uint8_t *msg)
> +static int do_nothing_handle_message(RedChannelClient *rcc,
> + uint16_t type,
> + uint32_t size,
> + uint8_t *msg)
> {
> return TRUE;
> }
> @@ -1195,10 +1210,11 @@ RedClient *red_channel_client_get_client(RedChannelClient *rcc)
> return rcc->client;
> }
>
> -SpiceDataHeader *red_channel_client_get_header(RedChannelClient *rcc)
> +void red_channel_client_set_header_sub_list(RedChannelClient *rcc, uint32_t sub_list)
> {
> - return rcc->send_data.header;
> + rcc->send_data.header->sub_list = sub_list;
> }
> +
> /* end of accessors */
>
> int red_channel_get_first_socket(RedChannel *channel)
> diff --git a/server/red_channel.h b/server/red_channel.h
> index cb80100..40792c1 100644
> --- a/server/red_channel.h
> +++ b/server/red_channel.h
> @@ -38,11 +38,11 @@
> At the final stage, this interface shouldn't be exposed. Only RedChannel will use it. */
>
> typedef int (*handle_message_proc)(void *opaque,
> - SpiceDataHeader *header, uint8_t *msg);
> + uint16_t type, uint32_t size, uint8_t *msg);
> typedef int (*handle_parsed_proc)(void *opaque, uint32_t size, uint16_t type, void *message);
> -typedef uint8_t *(*alloc_msg_recv_buf_proc)(void *opaque, SpiceDataHeader *msg_header);
> +typedef uint8_t *(*alloc_msg_recv_buf_proc)(void *opaque, uint16_t type, uint32_t size);
> typedef void (*release_msg_recv_buf_proc)(void *opaque,
> - SpiceDataHeader *msg_header, uint8_t *msg);
> + uint16_t type, uint32_t size, uint8_t *msg);
> typedef void (*on_incoming_error_proc)(void *opaque);
>
> typedef struct IncomingHandlerInterface {
> @@ -119,13 +119,13 @@ typedef struct PipeItem {
> } PipeItem;
>
> typedef uint8_t *(*channel_alloc_msg_recv_buf_proc)(RedChannelClient *channel,
> - SpiceDataHeader *msg_header);
> + uint16_t type, uint32_t size);
> typedef int (*channel_handle_parsed_proc)(RedChannelClient *rcc, uint32_t size, uint16_t type,
> void *message);
> typedef int (*channel_handle_message_proc)(RedChannelClient *rcc,
> - SpiceDataHeader *header, uint8_t *msg);
> + uint16_t type, uint32_t size, uint8_t *msg);
> typedef void (*channel_release_msg_recv_buf_proc)(RedChannelClient *channel,
> - SpiceDataHeader *msg_header, uint8_t *msg);
> + uint16_t type, uint32_t size, uint8_t *msg);
> typedef void (*channel_disconnect_proc)(RedChannelClient *rcc);
> typedef int (*channel_configure_socket_proc)(RedChannelClient *rcc);
> typedef void (*channel_send_pipe_item_proc)(RedChannelClient *rcc, PipeItem *item);
> @@ -334,7 +334,7 @@ void red_channel_init_outgoing_messages_window(RedChannel *channel);
>
> /* handles general channel msgs from the client */
> int red_channel_client_handle_message(RedChannelClient *rcc, uint32_t size,
> - uint16_t type, void *message);
> + uint16_t type, void *message);
>
> /* when preparing send_data: should call init and then use marshaller */
> void red_channel_client_init_send_data(RedChannelClient *rcc, uint16_t msg_type, PipeItem *item);
> @@ -444,13 +444,9 @@ SpiceMarshaller *red_channel_client_get_marshaller(RedChannelClient *rcc);
> RedsStream *red_channel_client_get_stream(RedChannelClient *rcc);
> RedClient *red_channel_client_get_client(RedChannelClient *rcc);
>
> -/* this is a convenience function for sending messages, sometimes (migration only?)
> - * the serial from the header needs to be available for sending. Note that the header
> - * pointer retrieved is not valid except between red_channel_reset_send_data and
> - * red_channel_begin_send_message. red_channel_init_send_data changes the header (sets
> - * the type in it) as a convenience function. It is preffered to do that through it and
> - * not via the below accessor and direct header manipulation. */
> -SpiceDataHeader *red_channel_client_get_header(RedChannelClient *rcc);
> +/* Note that the header is valid only between red_channel_reset_send_data and
> + * red_channel_begin_send_message.*/
> +void red_channel_client_set_header_sub_list(RedChannelClient *rcc, uint32_t sub_list);
>
> /* return the sum of all the rcc pipe size */
> uint32_t red_channel_max_pipe_size(RedChannel *channel);
> diff --git a/server/red_tunnel_worker.c b/server/red_tunnel_worker.c
> index 1e8267e..250e8b3 100644
> --- a/server/red_tunnel_worker.c
> +++ b/server/red_tunnel_worker.c
> @@ -1644,27 +1644,27 @@ static int tunnel_channel_handle_socket_token(TunnelChannelClient *channel, RedS
> }
>
> static uint8_t *tunnel_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> - SpiceDataHeader *msg_header)
> + uint16_t type, uint32_t size)
> {
> TunnelChannelClient *tunnel_channel = (TunnelChannelClient *)rcc->channel;
>
> - if (msg_header->type == SPICE_MSGC_TUNNEL_SOCKET_DATA) {
> + if (type == SPICE_MSGC_TUNNEL_SOCKET_DATA) {
> return (__tunnel_worker_alloc_socket_rcv_buf(tunnel_channel->worker)->buf);
> - } else if ((msg_header->type == SPICE_MSGC_MIGRATE_DATA) ||
> - (msg_header->type == SPICE_MSGC_TUNNEL_SERVICE_ADD)) {
> - return spice_malloc(msg_header->size);
> + } else if ((type == SPICE_MSGC_MIGRATE_DATA) ||
> + (type == SPICE_MSGC_TUNNEL_SERVICE_ADD)) {
> + return spice_malloc(size);
> } else {
> return (tunnel_channel->control_rcv_buf);
> }
> }
>
> // called by the receive routine of the channel, before the buffer was assigned to a socket
> -static void tunnel_channel_release_msg_rcv_buf(RedChannelClient *rcc, SpiceDataHeader *msg_header,
> +static void tunnel_channel_release_msg_rcv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size,
> uint8_t *msg)
> {
> TunnelChannelClient *tunnel_channel = (TunnelChannelClient *)rcc->channel;
>
> - if (msg_header->type == SPICE_MSGC_TUNNEL_SOCKET_DATA) {
> + if (type == SPICE_MSGC_TUNNEL_SOCKET_DATA) {
> ASSERT(!(SPICE_CONTAINEROF(msg, RedSocketRawRcvBuf, buf)->base.usr_opaque));
> __tunnel_worker_free_socket_rcv_buf(tunnel_channel->worker,
> SPICE_CONTAINEROF(msg, RedSocketRawRcvBuf, buf));
> @@ -2243,12 +2243,13 @@ error:
> }
>
> // msg was allocated by tunnel_channel_alloc_msg_rcv_buf
> -static int tunnel_channel_handle_message(RedChannelClient *rcc, SpiceDataHeader *header, uint8_t *msg)
> +static int tunnel_channel_handle_message(RedChannelClient *rcc, uint16_t type,
> + uint32_t size, uint8_t *msg)
> {
> TunnelChannelClient *tunnel_channel = (TunnelChannelClient *)rcc->channel;
> RedSocket *sckt = NULL;
> // retrieve the sckt
> - switch (header->type) {
> + switch (type) {
> case SPICE_MSGC_MIGRATE_FLUSH_MARK:
> case SPICE_MSGC_MIGRATE_DATA:
> case SPICE_MSGC_TUNNEL_SERVICE_ADD:
> @@ -2269,12 +2270,12 @@ static int tunnel_channel_handle_message(RedChannelClient *rcc, SpiceDataHeader
> }
> break;
> default:
> - return red_channel_client_handle_message(rcc, header->size, header->type, msg);
> + return red_channel_client_handle_message(rcc, size, type, msg);
> }
>
> - switch (header->type) {
> + switch (type) {
> case SPICE_MSGC_TUNNEL_SERVICE_ADD:
> - if (header->size < sizeof(SpiceMsgcTunnelAddGenericService)) {
> + if (size < sizeof(SpiceMsgcTunnelAddGenericService)) {
> red_printf("bad message size");
> free(msg);
> return FALSE;
> @@ -2285,7 +2286,7 @@ static int tunnel_channel_handle_message(RedChannelClient *rcc, SpiceDataHeader
> red_printf("REDC_TUNNEL_REMOVE_SERVICE not supported yet");
> return FALSE;
> case SPICE_MSGC_TUNNEL_SOCKET_OPEN_ACK:
> - if (header->size != sizeof(SpiceMsgcTunnelSocketOpenAck)) {
> + if (size != sizeof(SpiceMsgcTunnelSocketOpenAck)) {
> red_printf("bad message size");
> return FALSE;
> }
> @@ -2294,7 +2295,7 @@ static int tunnel_channel_handle_message(RedChannelClient *rcc, SpiceDataHeader
> ((SpiceMsgcTunnelSocketOpenAck *)msg)->tokens);
>
> case SPICE_MSGC_TUNNEL_SOCKET_OPEN_NACK:
> - if (header->size != sizeof(SpiceMsgcTunnelSocketOpenNack)) {
> + if (size != sizeof(SpiceMsgcTunnelSocketOpenNack)) {
> red_printf("bad message size");
> return FALSE;
> }
> @@ -2302,35 +2303,35 @@ static int tunnel_channel_handle_message(RedChannelClient *rcc, SpiceDataHeader
> return tunnel_channel_handle_socket_connect_nack(tunnel_channel, sckt);
> case SPICE_MSGC_TUNNEL_SOCKET_DATA:
> {
> - if (header->size < sizeof(SpiceMsgcTunnelSocketData)) {
> + if (size < sizeof(SpiceMsgcTunnelSocketData)) {
> red_printf("bad message size");
> return FALSE;
> }
>
> return tunnel_channel_handle_socket_receive_data(tunnel_channel, sckt,
> SPICE_CONTAINEROF(msg, RedSocketRawRcvBuf, buf),
> - header->size - sizeof(SpiceMsgcTunnelSocketData));
> + size - sizeof(SpiceMsgcTunnelSocketData));
> }
> case SPICE_MSGC_TUNNEL_SOCKET_FIN:
> - if (header->size != sizeof(SpiceMsgcTunnelSocketFin)) {
> + if (size != sizeof(SpiceMsgcTunnelSocketFin)) {
> red_printf("bad message size");
> return FALSE;
> }
> return tunnel_channel_handle_socket_fin(tunnel_channel, sckt);
> case SPICE_MSGC_TUNNEL_SOCKET_CLOSED:
> - if (header->size != sizeof(SpiceMsgcTunnelSocketClosed)) {
> + if (size != sizeof(SpiceMsgcTunnelSocketClosed)) {
> red_printf("bad message size");
> return FALSE;
> }
> return tunnel_channel_handle_socket_closed(tunnel_channel, sckt);
> case SPICE_MSGC_TUNNEL_SOCKET_CLOSED_ACK:
> - if (header->size != sizeof(SpiceMsgcTunnelSocketClosedAck)) {
> + if (size != sizeof(SpiceMsgcTunnelSocketClosedAck)) {
> red_printf("bad message size");
> return FALSE;
> }
> return tunnel_channel_handle_socket_closed_ack(tunnel_channel, sckt);
> case SPICE_MSGC_TUNNEL_SOCKET_TOKEN:
> - if (header->size != sizeof(SpiceMsgcTunnelSocketTokens)) {
> + if (size != sizeof(SpiceMsgcTunnelSocketTokens)) {
> red_printf("bad message size");
> return FALSE;
> }
> @@ -2338,7 +2339,7 @@ static int tunnel_channel_handle_message(RedChannelClient *rcc, SpiceDataHeader
> return tunnel_channel_handle_socket_token(tunnel_channel, sckt,
> (SpiceMsgcTunnelSocketTokens *)msg);
> default:
> - return red_channel_client_handle_message(rcc, header->size, header->type, msg);
> + return red_channel_client_handle_message(rcc, size, type, msg);
> }
> return TRUE;
> }
> diff --git a/server/red_worker.c b/server/red_worker.c
> index d82c84e..f454302 100644
> --- a/server/red_worker.c
> +++ b/server/red_worker.c
> @@ -1517,15 +1517,15 @@ static void release_upgrade_item(RedWorker* worker, UpgradeItem *item)
> }
> }
>
> -static uint8_t *common_alloc_recv_buf(RedChannelClient *rcc, SpiceDataHeader *msg_header)
> +static uint8_t *common_alloc_recv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size)
> {
> CommonChannel *common = SPICE_CONTAINEROF(rcc->channel, CommonChannel, base);
>
> return common->recv_buf;
> }
>
> -static void common_release_recv_buf(RedChannelClient *rcc,
> - SpiceDataHeader *msg_header, uint8_t* msg)
> +static void common_release_recv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size,
> + uint8_t* msg)
> {
> }
>
> @@ -7785,7 +7785,6 @@ static inline void display_begin_send_message(RedChannelClient *rcc,
> {
> DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
> FreeList *free_list = &dcc->send_data.free_list;
> - SpiceDataHeader *header = red_channel_client_get_header(rcc);
>
> if (free_list->res->count) {
> int sub_list_len = 1;
> @@ -7828,7 +7827,7 @@ static inline void display_begin_send_message(RedChannelClient *rcc,
> spice_marshaller_add_uint32(sub_list_m, spice_marshaller_get_offset(wait_m));
> }
> spice_marshaller_add_uint32(sub_list_m, spice_marshaller_get_offset(inval_m));
> - header->sub_list = spice_marshaller_get_offset(sub_list_m);
> + red_channel_client_set_header_sub_list(rcc, spice_marshaller_get_offset(sub_list_m));
> }
> red_channel_client_begin_send_message(rcc);
> }
> diff --git a/server/smartcard.c b/server/smartcard.c
> index f9cafdf..08ba3da 100644
> --- a/server/smartcard.c
> +++ b/server/smartcard.c
> @@ -273,15 +273,18 @@ static int smartcard_channel_client_config_socket(RedChannelClient *rcc)
> }
>
> static uint8_t *smartcard_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> - SpiceDataHeader *msg_header)
> + uint16_t type,
> + uint32_t size)
> {
> - return spice_malloc(msg_header->size);
> + return spice_malloc(size);
> }
>
> static void smartcard_channel_release_msg_rcv_buf(RedChannelClient *rcc,
> - SpiceDataHeader *msg_header, uint8_t *msg)
> + uint16_t type,
> + uint32_t size,
> + uint8_t *msg)
> {
> - red_printf("freeing %d bytes", msg_header->size);
> + red_printf("freeing %d bytes", size);
> free(msg);
> }
>
> @@ -439,14 +442,15 @@ static void smartcard_channel_write_to_reader(VSCMsgHeader *vheader)
> }
>
> static int smartcard_channel_handle_message(RedChannelClient *rcc,
> - SpiceDataHeader *header,
> + uint16_t type,
> + uint32_t size,
> uint8_t *msg)
> {
> VSCMsgHeader* vheader = (VSCMsgHeader*)msg;
>
> - if (header->type != SPICE_MSGC_SMARTCARD_DATA) {
> + if (type != SPICE_MSGC_SMARTCARD_DATA) {
> /* handle ack's, spicy sends them while spicec does not */
> - return red_channel_client_handle_message(rcc, header->size, header->type, msg);
> + return red_channel_client_handle_message(rcc, size, type, msg);
> }
>
> ASSERT(header->size == vheader->length + sizeof(VSCMsgHeader));
> diff --git a/server/spicevmc.c b/server/spicevmc.c
> index bed8488..c2e249c 100644
> --- a/server/spicevmc.c
> +++ b/server/spicevmc.c
> @@ -126,7 +126,9 @@ static void spicevmc_red_channel_client_on_disconnect(RedChannelClient *rcc)
> }
>
> static int spicevmc_red_channel_client_handle_message(RedChannelClient *rcc,
> - SpiceDataHeader *header, uint8_t *msg)
> + uint16_t type,
> + uint32_t size,
> + uint8_t *msg)
> {
> SpiceVmcState *state;
> SpiceCharDeviceInstance *sin;
> @@ -136,22 +138,22 @@ static int spicevmc_red_channel_client_handle_message(RedChannelClient *rcc,
> sin = state->chardev_sin;
> sif = SPICE_CONTAINEROF(sin->base.sif, SpiceCharDeviceInterface, base);
>
> - if (header->type != SPICE_MSGC_SPICEVMC_DATA) {
> - return red_channel_client_handle_message(rcc, header->size,
> - header->type, msg);
> + if (type != SPICE_MSGC_SPICEVMC_DATA) {
> + return red_channel_client_handle_message(rcc, size, type, msg);
> }
>
> /*
> * qemu spicevmc will consume everything we give it, no need for
> * flow control checks (or to use a pipe).
> */
> - sif->write(sin, msg, header->size);
> + sif->write(sin, msg, size);
>
> return TRUE;
> }
>
> static uint8_t *spicevmc_red_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> - SpiceDataHeader *msg_header)
> + uint16_t type,
> + uint32_t size)
> {
> SpiceVmcState *state;
>
> @@ -159,9 +161,9 @@ static uint8_t *spicevmc_red_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
>
> assert(!state->rcv_buf_in_use);
>
> - if (msg_header->size > state->rcv_buf_size) {
> - state->rcv_buf = spice_realloc(state->rcv_buf, msg_header->size);
> - state->rcv_buf_size = msg_header->size;
> + if (size > state->rcv_buf_size) {
> + state->rcv_buf = spice_realloc(state->rcv_buf, size);
> + state->rcv_buf_size = size;
> }
>
> state->rcv_buf_in_use = 1;
> @@ -170,7 +172,9 @@ static uint8_t *spicevmc_red_channel_alloc_msg_rcv_buf(RedChannelClient *rcc,
> }
>
> static void spicevmc_red_channel_release_msg_rcv_buf(RedChannelClient *rcc,
> - SpiceDataHeader *msg_header, uint8_t *msg)
> + uint16_t type,
> + uint32_t size,
> + uint8_t *msg)
> {
> SpiceVmcState *state;
>
> --
> 1.7.6.4
>
> _______________________________________________
> Spice-devel mailing list
> Spice-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/spice-devel
More information about the Spice-devel
mailing list