[Spice-devel] [PATCH 1/2] vdagent: Allow disabling the server-side udscs support
Victor Toso
lists at victortoso.com
Wed Nov 11 03:47:01 PST 2015
Hi,
On Thu, Nov 05, 2015 at 07:21:38PM +0100, Francois Gouget wrote:
> To do so define UDSCS_NO_SERVER.
> This simplifies reuse in client-only scenarios that don't need peer
> credential support for instance.
> The patch also reorders the client and server code so the
> implementation is not peppered with #ifdefs.
The problem with reordering the code is that it can affect git blame
(which I like using) when understanding code. Now, udscs.c has only 4
commits so I don't think it is a big issue in this case.
Would you mind doing the reordering in one patch and using and defining
UDSCS_NO_SERVER in other patch?
Also, did you send this with git-send-email? It does not seem proper
threaded in patchwork.freedesktop.org/project/Spice neither in mailman.
Best,
Victor Toso
>
> Signed-off-by: Francois Gouget <fgouget at codeweavers.com>
> ---
> src/udscs.c | 486 ++++++++++++++++++++++++++++++------------------------------
> src/udscs.h | 128 +++++++++-------
> 2 files changed, 321 insertions(+), 293 deletions(-)
>
> diff --git a/src/udscs.c b/src/udscs.c
> index 288aca2..334d54a 100644
> --- a/src/udscs.c
> +++ b/src/udscs.c
> @@ -46,8 +46,10 @@ struct udscs_connection {
> const char * const *type_to_string;
> int no_types;
> int debug;
> - struct ucred peer_cred;
> void *user_data;
> +#ifndef UDSCS_NO_SERVER
> + struct ucred peer_cred;
> +#endif
>
> /* Read stuff, single buffer, separate header and data buffer */
> int header_read;
> @@ -66,86 +68,6 @@ struct udscs_connection {
> struct udscs_connection *prev;
> };
>
> -struct udscs_server {
> - int fd;
> - const char * const *type_to_string;
> - int no_types;
> - int debug;
> - struct udscs_connection connections_head;
> - udscs_connect_callback connect_callback;
> - udscs_read_callback read_callback;
> - udscs_disconnect_callback disconnect_callback;
> -};
> -
> -static void udscs_do_write(struct udscs_connection **connp);
> -static void udscs_do_read(struct udscs_connection **connp);
> -
> -
> -struct udscs_server *udscs_create_server(const char *socketname,
> - udscs_connect_callback connect_callback,
> - udscs_read_callback read_callback,
> - udscs_disconnect_callback disconnect_callback,
> - const char * const type_to_string[], int no_types, int debug)
> -{
> - int c;
> - struct sockaddr_un address;
> - struct udscs_server *server;
> -
> - server = calloc(1, sizeof(*server));
> - if (!server)
> - return NULL;
> -
> - server->type_to_string = type_to_string;
> - server->no_types = no_types;
> - server->debug = debug;
> -
> - server->fd = socket(PF_UNIX, SOCK_STREAM, 0);
> - if (server->fd == -1) {
> - syslog(LOG_ERR, "creating unix domain socket: %m");
> - free(server);
> - return NULL;
> - }
> -
> - address.sun_family = AF_UNIX;
> - snprintf(address.sun_path, sizeof(address.sun_path), "%s", socketname);
> - c = bind(server->fd, (struct sockaddr *)&address, sizeof(address));
> - if (c != 0) {
> - syslog(LOG_ERR, "bind %s: %m", socketname);
> - free(server);
> - return NULL;
> - }
> -
> - c = listen(server->fd, 5);
> - if (c != 0) {
> - syslog(LOG_ERR, "listen: %m");
> - free(server);
> - return NULL;
> - }
> -
> - server->connect_callback = connect_callback;
> - server->read_callback = read_callback;
> - server->disconnect_callback = disconnect_callback;
> -
> - return server;
> -}
> -
> -void udscs_destroy_server(struct udscs_server *server)
> -{
> - struct udscs_connection *conn, *next_conn;
> -
> - if (!server)
> - return;
> -
> - conn = server->connections_head.next;
> - while (conn) {
> - next_conn = conn->next;
> - udscs_destroy_connection(&conn);
> - conn = next_conn;
> - }
> - close(server->fd);
> - free(server);
> -}
> -
> struct udscs_connection *udscs_connect(const char *socketname,
> udscs_read_callback read_callback,
> udscs_disconnect_callback disconnect_callback,
> @@ -225,131 +147,17 @@ void udscs_destroy_connection(struct udscs_connection **connp)
> *connp = NULL;
> }
>
> -struct ucred udscs_get_peer_cred(struct udscs_connection *conn)
> -{
> - return conn->peer_cred;
> -}
> -
> -int udscs_server_fill_fds(struct udscs_server *server, fd_set *readfds,
> - fd_set *writefds)
> +void udscs_set_user_data(struct udscs_connection *conn, void *data)
> {
> - struct udscs_connection *conn;
> - int nfds = server->fd + 1;
> -
> - if (!server)
> - return -1;
> -
> - FD_SET(server->fd, readfds);
> -
> - conn = server->connections_head.next;
> - while (conn) {
> - int conn_nfds = udscs_client_fill_fds(conn, readfds, writefds);
> - if (conn_nfds > nfds)
> - nfds = conn_nfds;
> -
> - conn = conn->next;
> - }
> -
> - return nfds;
> + conn->user_data = data;
> }
>
> -int udscs_client_fill_fds(struct udscs_connection *conn, fd_set *readfds,
> - fd_set *writefds)
> +void *udscs_get_user_data(struct udscs_connection *conn)
> {
> if (!conn)
> - return -1;
> -
> - FD_SET(conn->fd, readfds);
> - if (conn->write_buf)
> - FD_SET(conn->fd, writefds);
> -
> - return conn->fd + 1;
> -}
> -
> -static void udscs_server_accept(struct udscs_server *server) {
> - struct udscs_connection *new_conn, *conn;
> - struct sockaddr_un address;
> - socklen_t length = sizeof(address);
> - int r, fd;
> -
> - fd = accept(server->fd, (struct sockaddr *)&address, &length);
> - if (fd == -1) {
> - if (errno == EINTR)
> - return;
> - syslog(LOG_ERR, "accept: %m");
> - return;
> - }
> -
> - new_conn = calloc(1, sizeof(*conn));
> - if (!new_conn) {
> - syslog(LOG_ERR, "out of memory, disconnecting new client");
> - close(fd);
> - return;
> - }
> -
> - new_conn->fd = fd;
> - new_conn->type_to_string = server->type_to_string;
> - new_conn->no_types = server->no_types;
> - new_conn->debug = server->debug;
> - new_conn->read_callback = server->read_callback;
> - new_conn->disconnect_callback = server->disconnect_callback;
> -
> - length = sizeof(new_conn->peer_cred);
> - r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &new_conn->peer_cred, &length);
> - if (r != 0) {
> - syslog(LOG_ERR, "Could not get peercred, disconnecting new client");
> - close(fd);
> - free(new_conn);
> - return;
> - }
> -
> - conn = &server->connections_head;
> - while (conn->next)
> - conn = conn->next;
> -
> - new_conn->prev = conn;
> - conn->next = new_conn;
> -
> - if (server->debug)
> - syslog(LOG_DEBUG, "new client accepted: %p, pid: %d",
> - new_conn, (int)new_conn->peer_cred.pid);
> -
> - if (server->connect_callback)
> - server->connect_callback(new_conn);
> -}
> -
> -void udscs_server_handle_fds(struct udscs_server *server, fd_set *readfds,
> - fd_set *writefds)
> -{
> - struct udscs_connection *conn, *next_conn;
> -
> - if (!server)
> - return;
> -
> - if (FD_ISSET(server->fd, readfds))
> - udscs_server_accept(server);
> -
> - conn = server->connections_head.next;
> - while (conn) {
> - /* conn maybe destroyed by udscs_client_handle_fds (when disconnected),
> - so get the next connection first. */
> - next_conn = conn->next;
> - udscs_client_handle_fds(&conn, readfds, writefds);
> - conn = next_conn;
> - }
> -}
> -
> -void udscs_client_handle_fds(struct udscs_connection **connp, fd_set *readfds,
> - fd_set *writefds)
> -{
> - if (!*connp)
> - return;
> -
> - if (FD_ISSET((*connp)->fd, readfds))
> - udscs_do_read(connp);
> + return NULL;
>
> - if (*connp && FD_ISSET((*connp)->fd, writefds))
> - udscs_do_write(connp);
> + return conn->user_data;
> }
>
> int udscs_write(struct udscs_connection *conn, uint32_t type, uint32_t arg1,
> @@ -404,41 +212,7 @@ int udscs_write(struct udscs_connection *conn, uint32_t type, uint32_t arg1,
> return 0;
> }
>
> -int udscs_server_write_all(struct udscs_server *server,
> - uint32_t type, uint32_t arg1, uint32_t arg2,
> - const uint8_t *data, uint32_t size)
> -{
> - struct udscs_connection *conn;
> -
> - conn = server->connections_head.next;
> - while (conn) {
> - if (udscs_write(conn, type, arg1, arg2, data, size))
> - return -1;
> - conn = conn->next;
> - }
> -
> - return 0;
> -}
> -
> -int udscs_server_for_all_clients(struct udscs_server *server,
> - udscs_for_all_clients_callback func, void *priv)
> -{
> - int r = 0;
> - struct udscs_connection *conn, *next_conn;
> -
> - if (!server)
> - return 0;
> -
> - conn = server->connections_head.next;
> - while (conn) {
> - /* Get next conn as func may destroy the current conn */
> - next_conn = conn->next;
> - r += func(&conn, priv);
> - conn = next_conn;
> - }
> - return r;
> -}
> -
> +/* A helper for udscs_do_read() */
> static void udscs_read_complete(struct udscs_connection **connp)
> {
> struct udscs_connection *conn = *connp;
> @@ -466,6 +240,7 @@ static void udscs_read_complete(struct udscs_connection **connp)
> memset(&conn->data, 0, sizeof(conn->data));
> }
>
> +/* A helper for udscs_client_handle_fds() */
> static void udscs_do_read(struct udscs_connection **connp)
> {
> ssize_t n;
> @@ -516,6 +291,7 @@ static void udscs_do_read(struct udscs_connection **connp)
> }
> }
>
> +/* A helper for udscs_client_handle_fds() */
> static void udscs_do_write(struct udscs_connection **connp)
> {
> ssize_t n;
> @@ -549,15 +325,247 @@ static void udscs_do_write(struct udscs_connection **connp)
> }
> }
>
> -void udscs_set_user_data(struct udscs_connection *conn, void *data)
> +void udscs_client_handle_fds(struct udscs_connection **connp, fd_set *readfds,
> + fd_set *writefds)
> {
> - conn->user_data = data;
> + if (!*connp)
> + return;
> +
> + if (FD_ISSET((*connp)->fd, readfds))
> + udscs_do_read(connp);
> +
> + if (*connp && FD_ISSET((*connp)->fd, writefds))
> + udscs_do_write(connp);
> }
>
> -void *udscs_get_user_data(struct udscs_connection *conn)
> +int udscs_client_fill_fds(struct udscs_connection *conn, fd_set *readfds,
> + fd_set *writefds)
> {
> if (!conn)
> + return -1;
> +
> + FD_SET(conn->fd, readfds);
> + if (conn->write_buf)
> + FD_SET(conn->fd, writefds);
> +
> + return conn->fd + 1;
> +}
> +
> +
> +#ifndef UDSCS_NO_SERVER
> +
> +/* ---------- Server-side implementation ---------- */
> +
> +struct udscs_server {
> + int fd;
> + const char * const *type_to_string;
> + int no_types;
> + int debug;
> + struct udscs_connection connections_head;
> + udscs_connect_callback connect_callback;
> + udscs_read_callback read_callback;
> + udscs_disconnect_callback disconnect_callback;
> +};
> +
> +struct udscs_server *udscs_create_server(const char *socketname,
> + udscs_connect_callback connect_callback,
> + udscs_read_callback read_callback,
> + udscs_disconnect_callback disconnect_callback,
> + const char * const type_to_string[], int no_types, int debug)
> +{
> + int c;
> + struct sockaddr_un address;
> + struct udscs_server *server;
> +
> + server = calloc(1, sizeof(*server));
> + if (!server)
> return NULL;
>
> - return conn->user_data;
> + server->type_to_string = type_to_string;
> + server->no_types = no_types;
> + server->debug = debug;
> +
> + server->fd = socket(PF_UNIX, SOCK_STREAM, 0);
> + if (server->fd == -1) {
> + syslog(LOG_ERR, "creating unix domain socket: %m");
> + free(server);
> + return NULL;
> + }
> +
> + address.sun_family = AF_UNIX;
> + snprintf(address.sun_path, sizeof(address.sun_path), "%s", socketname);
> + c = bind(server->fd, (struct sockaddr *)&address, sizeof(address));
> + if (c != 0) {
> + syslog(LOG_ERR, "bind %s: %m", socketname);
> + free(server);
> + return NULL;
> + }
> +
> + c = listen(server->fd, 5);
> + if (c != 0) {
> + syslog(LOG_ERR, "listen: %m");
> + free(server);
> + return NULL;
> + }
> +
> + server->connect_callback = connect_callback;
> + server->read_callback = read_callback;
> + server->disconnect_callback = disconnect_callback;
> +
> + return server;
> +}
> +
> +void udscs_destroy_server(struct udscs_server *server)
> +{
> + struct udscs_connection *conn, *next_conn;
> +
> + if (!server)
> + return;
> +
> + conn = server->connections_head.next;
> + while (conn) {
> + next_conn = conn->next;
> + udscs_destroy_connection(&conn);
> + conn = next_conn;
> + }
> + close(server->fd);
> + free(server);
> +}
> +
> +struct ucred udscs_get_peer_cred(struct udscs_connection *conn)
> +{
> + return conn->peer_cred;
> }
> +
> +static void udscs_server_accept(struct udscs_server *server) {
> + struct udscs_connection *new_conn, *conn;
> + struct sockaddr_un address;
> + socklen_t length = sizeof(address);
> + int r, fd;
> +
> + fd = accept(server->fd, (struct sockaddr *)&address, &length);
> + if (fd == -1) {
> + if (errno == EINTR)
> + return;
> + syslog(LOG_ERR, "accept: %m");
> + return;
> + }
> +
> + new_conn = calloc(1, sizeof(*conn));
> + if (!new_conn) {
> + syslog(LOG_ERR, "out of memory, disconnecting new client");
> + close(fd);
> + return;
> + }
> +
> + new_conn->fd = fd;
> + new_conn->type_to_string = server->type_to_string;
> + new_conn->no_types = server->no_types;
> + new_conn->debug = server->debug;
> + new_conn->read_callback = server->read_callback;
> + new_conn->disconnect_callback = server->disconnect_callback;
> +
> + length = sizeof(new_conn->peer_cred);
> + r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &new_conn->peer_cred, &length);
> + if (r != 0) {
> + syslog(LOG_ERR, "Could not get peercred, disconnecting new client");
> + close(fd);
> + free(new_conn);
> + return;
> + }
> +
> + conn = &server->connections_head;
> + while (conn->next)
> + conn = conn->next;
> +
> + new_conn->prev = conn;
> + conn->next = new_conn;
> +
> + if (server->debug)
> + syslog(LOG_DEBUG, "new client accepted: %p, pid: %d",
> + new_conn, (int)new_conn->peer_cred.pid);
> +
> + if (server->connect_callback)
> + server->connect_callback(new_conn);
> +}
> +
> +int udscs_server_fill_fds(struct udscs_server *server, fd_set *readfds,
> + fd_set *writefds)
> +{
> + struct udscs_connection *conn;
> + int nfds = server->fd + 1;
> +
> + if (!server)
> + return -1;
> +
> + FD_SET(server->fd, readfds);
> +
> + conn = server->connections_head.next;
> + while (conn) {
> + int conn_nfds = udscs_client_fill_fds(conn, readfds, writefds);
> + if (conn_nfds > nfds)
> + nfds = conn_nfds;
> +
> + conn = conn->next;
> + }
> +
> + return nfds;
> +}
> +
> +void udscs_server_handle_fds(struct udscs_server *server, fd_set *readfds,
> + fd_set *writefds)
> +{
> + struct udscs_connection *conn, *next_conn;
> +
> + if (!server)
> + return;
> +
> + if (FD_ISSET(server->fd, readfds))
> + udscs_server_accept(server);
> +
> + conn = server->connections_head.next;
> + while (conn) {
> + /* conn maybe destroyed by udscs_client_handle_fds (when disconnected),
> + so get the next connection first. */
> + next_conn = conn->next;
> + udscs_client_handle_fds(&conn, readfds, writefds);
> + conn = next_conn;
> + }
> +}
> +
> +int udscs_server_write_all(struct udscs_server *server,
> + uint32_t type, uint32_t arg1, uint32_t arg2,
> + const uint8_t *data, uint32_t size)
> +{
> + struct udscs_connection *conn;
> +
> + conn = server->connections_head.next;
> + while (conn) {
> + if (udscs_write(conn, type, arg1, arg2, data, size))
> + return -1;
> + conn = conn->next;
> + }
> +
> + return 0;
> +}
> +
> +int udscs_server_for_all_clients(struct udscs_server *server,
> + udscs_for_all_clients_callback func, void *priv)
> +{
> + int r = 0;
> + struct udscs_connection *conn, *next_conn;
> +
> + if (!server)
> + return 0;
> +
> + conn = server->connections_head.next;
> + while (conn) {
> + /* Get next conn as func may destroy the current conn */
> + next_conn = conn->next;
> + r += func(&conn, priv);
> + conn = next_conn;
> + }
> + return r;
> +}
> +
> +#endif
> diff --git a/src/udscs.h b/src/udscs.h
> index 89ab617..8b0b14e 100644
> --- a/src/udscs.h
> +++ b/src/udscs.h
> @@ -27,8 +27,10 @@
> #include <sys/select.h>
> #include <sys/socket.h>
>
> +
> +/* ---------- Generic bits and client-side API ---------- */
> +
> struct udscs_connection;
> -struct udscs_server;
> struct udscs_message_header {
> uint32_t type;
> uint32_t arg1;
> @@ -36,89 +38,107 @@ struct udscs_message_header {
> uint32_t size;
> };
>
> -/* Callbacks with this type will be called when a new connection to a
> - server is accepted. */
> -typedef void (*udscs_connect_callback)(struct udscs_connection *conn);
> /* Callbacks with this type will be called when a complete message has been
> - received. The callback may call udscs_destroy_connection, in which case
> - *connp must be made NULL (which udscs_destroy_connection takes care of) */
> + * received. The callback may call udscs_destroy_connection, in which case
> + * *connp must be made NULL (which udscs_destroy_connection takes care of).
> + */
> typedef void (*udscs_read_callback)(struct udscs_connection **connp,
> struct udscs_message_header *header, uint8_t *data);
> -/* Callback type for udscs_server_for_all_clients. Clients can be disconnected
> - from this callback just like with a read callback. */
> -typedef int (*udscs_for_all_clients_callback)(struct udscs_connection **connp,
> - void *priv);
> +
> /* Callbacks with this type will be called when the connection is disconnected.
> - Note:
> - 1) udscs will destroy the connection in question itself after
> - this callback has completed!
> - 2) This callback is always called, even if the disconnect is initiated
> - by the udscs user through returning -1 from a read callback, or
> - by explictly calling udscs_destroy_connection */
> + * Note:
> + * 1) udscs will destroy the connection in question itself after
> + * this callback has completed!
> + * 2) This callback is always called, even if the disconnect is initiated
> + * by the udscs user through returning -1 from a read callback, or
> + * by explictly calling udscs_destroy_connection.
> + */
> typedef void (*udscs_disconnect_callback)(struct udscs_connection *conn);
>
> -/* Create a unix domain socket named name and start listening on it. */
> -struct udscs_server *udscs_create_server(const char *socketname,
> - udscs_connect_callback connect_callback,
> - udscs_read_callback read_callback,
> - udscs_disconnect_callback disconnect_callback,
> - const char * const type_to_string[], int no_types, int debug);
> -
> -void udscs_destroy_server(struct udscs_server *server);
> -
> /* Connect to a unix domain socket named name. */
> struct udscs_connection *udscs_connect(const char *socketname,
> udscs_read_callback read_callback,
> udscs_disconnect_callback disconnect_callback,
> const char * const type_to_string[], int no_types, int debug);
>
> -/* The contents of connp will be made NULL */
> +/* The contents of connp will be made NULL. */
> void udscs_destroy_connection(struct udscs_connection **connp);
>
> -
> -/* Given an udscs server or client fill the fd_sets pointed to by readfds and
> - writefds for select() usage.
> -
> - Return value: value of the highest fd + 1 */
> -int udscs_server_fill_fds(struct udscs_server *server, fd_set *readfds,
> - fd_set *writefds);
> -
> int udscs_client_fill_fds(struct udscs_connection *conn, fd_set *readfds,
> - fd_set *writefds);
> -
> -/* Handle any events flagged by select for the given udscs server or client. */
> -void udscs_server_handle_fds(struct udscs_server *server, fd_set *readfds,
> - fd_set *writefds);
> + fd_set *writefds);
>
> /* Note the connection may be destroyed (when disconnected) by this call
> - in this case the disconnect calllback will get called before the
> - destruction and the contents of connp will be made NULL */
> + * in this case the disconnect calllback will get called before the
> + * destruction and the contents of connp will be made NULL.
> + */
> void udscs_client_handle_fds(struct udscs_connection **connp, fd_set *readfds,
> - fd_set *writefds);
> -
> + fd_set *writefds);
>
> /* Queue a message for delivery to the client connected through conn.
> -
> - Returns 0 on success -1 on error (only happens when malloc fails) */
> + * Returns 0 on success -1 on error (only happens when malloc fails).
> + */
> int udscs_write(struct udscs_connection *conn, uint32_t type, uint32_t arg1,
> uint32_t arg2, const uint8_t *data, uint32_t size);
>
> +/* To associate per connection data with a connection. */
> +void udscs_set_user_data(struct udscs_connection *conn, void *data);
> +void *udscs_get_user_data(struct udscs_connection *conn);
> +
> +
> +#ifndef UDSCS_NO_SERVER
> +
> +/* ---------- Server-side API ---------- */
> +
> +struct udscs_server;
> +
> +/* Callbacks with this type will be called when a new connection to a
> + * server is accepted.
> + */
> +typedef void (*udscs_connect_callback)(struct udscs_connection *conn);
> +
> +/* Create a unix domain socket named name and start listening on it. */
> +struct udscs_server *udscs_create_server(const char *socketname,
> + udscs_connect_callback connect_callback,
> + udscs_read_callback read_callback,
> + udscs_disconnect_callback disconnect_callback,
> + const char * const type_to_string[], int no_types, int debug);
> +
> +void udscs_destroy_server(struct udscs_server *server);
> +
> /* Like udscs_write, but then send the message to all clients connected to
> - the server */
> + * the server.
> + */
> int udscs_server_write_all(struct udscs_server *server,
> - uint32_t type, uint32_t arg1, uint32_t arg2,
> - const uint8_t *data, uint32_t size);
> + uint32_t type, uint32_t arg1, uint32_t arg2,
> + const uint8_t *data, uint32_t size);
> +
> +/* Callback type for udscs_server_for_all_clients. Clients can be disconnected
> + * from this callback just like with a read callback.
> + */
> +typedef int (*udscs_for_all_clients_callback)(struct udscs_connection **connp,
> + void *priv);
> +
> /* Call func for all clients connected to the server, passing through
> - priv to all func calls. Returns the total of the return values from all
> - calls to func */
> + * priv to all func calls. Returns the total of the return values from all
> + * calls to func.
> + */
> int udscs_server_for_all_clients(struct udscs_server *server,
> - udscs_for_all_clients_callback func, void *priv);
> + udscs_for_all_clients_callback func, void *priv);
>
> +/* Given an udscs server or client fill the fd_sets pointed to by readfds and
> + * writefds for select() usage.
> + * Return value: value of the highest fd + 1
> + */
> +int udscs_server_fill_fds(struct udscs_server *server, fd_set *readfds,
> + fd_set *writefds);
> +
> +/* Handle any events flagged by select for the given udscs server or client. */
> +void udscs_server_handle_fds(struct udscs_server *server, fd_set *readfds,
> + fd_set *writefds);
>
> +/* Returns the peer's user credentials. */
> struct ucred udscs_get_peer_cred(struct udscs_connection *conn);
>
> -/* For server use, to associate per connection data with a connection */
> -void udscs_set_user_data(struct udscs_connection *conn, void *data);
> -void *udscs_get_user_data(struct udscs_connection *conn);
> +#endif
>
> #endif
> --
> 2.6.1
>
> _______________________________________________
> 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