[Spice-devel] [PATCH spice-gtk 4/5] Add a suid root helper to open usb device nodes

Alon Levy alevy at redhat.com
Tue Nov 15 07:56:40 PST 2011


On Tue, Nov 15, 2011 at 04:31:00PM +0100, Hans de Goede wrote:
> spice-client needs to be able to open the device nodes under /dev/bus/usb
> to be able to redirect a usb device to the guest. Normally opening these
> nodes is only allowed by root. This patch adds a suid root helper which
> asks policykit if it is ok to grant raw usb device access, and if policykit
> says it is ok, opens up the acl so that the spice-client can open the device
> node.
> 
> As soon as spice-client closes the stdin of the helper, the helper removes
> the extra rights. This ensures that the acl gets put back to normal even if
> the spice client crashes. Normally the spice-client closes stdin directly
> after opening the device node.
> 
> Signed-off-by: Hans de Goede <hdegoede at redhat.com>
> ---
>  configure.ac                                  |   15 ++
>  data/Makefile.am                              |    4 +
>  data/org.spice-space.lowlevelusbaccess.policy |   20 ++

Why spice-space and not spice? because it has to be a domain?
(and for all other uses of spice-space as a namespace)
If so no objection.

>  gtk/Makefile.am                               |   39 ++++
>  gtk/channel-usbredir.c                        |   70 ++++++-
>  gtk/spice-client-glib-usb-acl-helper.c        |  284 ++++++++++++++++++++++++
>  gtk/usb-acl-helper.c                          |  287 +++++++++++++++++++++++++
>  gtk/usb-acl-helper.h                          |   75 +++++++
>  8 files changed, 792 insertions(+), 2 deletions(-)
>  create mode 100644 data/org.spice-space.lowlevelusbaccess.policy
>  create mode 100644 gtk/spice-client-glib-usb-acl-helper.c
>  create mode 100644 gtk/usb-acl-helper.c
>  create mode 100644 gtk/usb-acl-helper.h
> 
> diff --git a/configure.ac b/configure.ac
> index 09d97bc..8cfe145 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -315,15 +315,30 @@ AC_ARG_ENABLE([usbredir],
>                   [Enable usbredir support @<:@default=yes@:>@]),
>    [],
>    [enable_usbredir="yes"])
> +AC_ARG_ENABLE([polkit],
> +  AS_HELP_STRING([--enable-polkit=@<:@yes/no@:>@],
> +                 [Enable policykit support (for the usb acl helper)@<:@default=yes@:>@]),
> +  [],
> +  [enable_polkit="yes"])
>  
>  if test "x$enable_usbredir" = "xno"; then
>    AM_CONDITIONAL(WITH_USBREDIR, false)
> +  AM_CONDITIONAL(WITH_POLKIT, false)
>  else
>    PKG_CHECK_MODULES(GUDEV, gudev-1.0)
>    PKG_CHECK_MODULES(LIBUSB, libusb-1.0 >= 1.0.9)
>    PKG_CHECK_MODULES(LIBUSBREDIRHOST, libusbredirhost >= 0.3.1)
>    AC_DEFINE(USE_USBREDIR, [1], [Define if supporting usbredir proxying])
>    AM_CONDITIONAL(WITH_USBREDIR, true)
> +  if test "x$enable_usbredir" = "xno"; then
> +    AM_CONDITIONAL(WITH_POLKIT, false)
> +  else
> +    PKG_CHECK_MODULES(POLKIT, polkit-gobject-1)
> +    AC_DEFINE(USE_POLKIT, [1], [Define if supporting polkit])
> +    AM_CONDITIONAL(WITH_POLKIT, true)
> +    POLICYDIR=`${PKG_CONFIG} polkit-gobject-1 --variable=policydir`
> +    AC_SUBST(POLICYDIR)
> +  fi
>  fi
>  
>  AC_ARG_WITH([coroutine],
> diff --git a/data/Makefile.am b/data/Makefile.am
> index 5ad1157..71934af 100644
> --- a/data/Makefile.am
> +++ b/data/Makefile.am
> @@ -17,6 +17,7 @@ EXTRA_DIST =					\
>  	spice-protocol.vapi			\
>  	gtkrc					\
>  	$(desktop_in_files)			\
> +	org.spice-space.lowlevelusbaccess.policy \
>  	$(NULL)
>  
>  CLEANFILES =					\
> @@ -30,4 +31,7 @@ DISTCLEANFILES =				\
>  vapidir = $(VAPIDIR)
>  vapi_DATA = spice-protocol.vapi
>  
> +policydir = $(POLICYDIR)
> +policy_DATA = org.spice-space.lowlevelusbaccess.policy
> +
>  -include $(top_srcdir)/git.mk
> diff --git a/data/org.spice-space.lowlevelusbaccess.policy b/data/org.spice-space.lowlevelusbaccess.policy
> new file mode 100644
> index 0000000..170f5ff
> --- /dev/null
> +++ b/data/org.spice-space.lowlevelusbaccess.policy
> @@ -0,0 +1,20 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!DOCTYPE policyconfig PUBLIC
> +	  "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
> +	  "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
> +<policyconfig>
> +
> +  <vendor>The Spice Project</vendor>
> +  <vendor_url>http://spice-space.org/</vendor_url>
> +  <icon_name>spice</icon_name>
> +
> +  <action id="org.spice-space.lowlevelusbaccess">
> +    <description>Low level USB device access</description>
> +    <message>Privileges are required for low level USB device access (for usb device pass through).</message>
> +    <defaults>
> +      <allow_inactive>no</allow_inactive>
> +      <allow_active>auth_admin_keep</allow_active>
> +    </defaults>
> +  </action>
> +
> +</policyconfig>
> diff --git a/gtk/Makefile.am b/gtk/Makefile.am
> index 17efc89..eb92a5e 100644
> --- a/gtk/Makefile.am
> +++ b/gtk/Makefile.am
> @@ -21,6 +21,10 @@ EXTRA_DIST =					\
>  	$(NULL)
>  
>  bin_PROGRAMS = spicy snappy spicy-stats
> +if WITH_POLKIT
> +bin_PROGRAMS += spice-client-glib-usb-acl-helper
> +endif
> +
>  lib_LTLIBRARIES = \
>  	libspice-client-glib-2.0.la
>  
> @@ -188,6 +192,14 @@ else
>  GUSB_SRCS =
>  endif
>  
> +if WITH_POLKIT
> +USB_ACL_HELPER_SRCS = \
> +	usb-acl-helper.c		\
> +	usb-acl-helper.h
> +else
> +USB_ACL_HELPER_SRCS =
> +endif
> +
>  libspice_client_glib_2_0_la_SOURCES =	\
>  	glib-compat.h			\
>  	spice-audio.c			\
> @@ -224,6 +236,7 @@ libspice_client_glib_2_0_la_SOURCES =	\
>  	smartcard-manager-priv.h	\
>  	usb-device-manager.c		\
>  	$(GUSB_SRCS)			\
> +	$(USB_ACL_HELPER_SRCS)		\
>  	\
>  	decode.h			\
>  	decode-glz.c			\
> @@ -364,6 +377,32 @@ spicy_CPPFLAGS =			\
>  	$(NULL)
>  
>  
> +if WITH_POLKIT
> +spice_client_glib_usb_acl_helper_SOURCES =	\
> +	spice-client-glib-usb-acl-helper.c	\
> +	$(NULL)
> +
> +spice_client_glib_usb_acl_helper_LDADD =	\
> +	$(GLIB2_LIBS)				\
> +	$(GIO_LIBS)				\
> +	$(POLKIT_LIBS)				\
> +	-lacl					\
> +	$(NULL)
> +
> +spice_client_glib_usb_acl_helper_CPPFLAGS =	\
> +	$(SPICE_CFLAGS)				\
> +	$(GLIB2_CFLAGS)				\
> +	$(GIO_CFLAGS)				\
> +	$(POLKIT_CFLAGS)			\
> +	$(NULL)
> +
> +install-exec-hook:
> +	-chown root $(DESTDIR)$(bindir)/spice-client-glib-usb-acl-helper
> +	-chmod u+s  $(DESTDIR)$(bindir)/spice-client-glib-usb-acl-helper
> +
> +endif
> +
> +
>  snappy_SOURCES =			\
>  	snappy.c			\
>  	spice-cmdline.h			\
> diff --git a/gtk/channel-usbredir.c b/gtk/channel-usbredir.c
> index 94c801a..f64ab0e 100644
> --- a/gtk/channel-usbredir.c
> +++ b/gtk/channel-usbredir.c
> @@ -26,6 +26,9 @@
>  #include <gusb/gusb-context-private.h>
>  #include <gusb/gusb-device-private.h>
>  #include <gusb/gusb-util.h>
> +#if USE_POLKIT
> +#include "usb-acl-helper.h"
> +#endif
>  #include "channel-usbredir-priv.h"
>  #endif
>  
> @@ -61,6 +64,11 @@ struct _SpiceUsbredirChannelPrivate {
>      int read_buf_size;
>      SpiceMsgOut *msg_out;
>      gboolean up;
> +    gboolean connected;
> +#if USE_POLKIT
> +    GSimpleAsyncResult *result;
> +    SpiceUsbAclHelper *acl_helper;
> +#endif
>  #endif
>  };
>  
> @@ -145,10 +153,39 @@ static gboolean spice_usbredir_channel_open_device(
>      }
>  
>      spice_channel_connect(SPICE_CHANNEL(channel));
> +    priv->connected = TRUE;
>  
>      return TRUE;
>  }
>  
> +#if USE_POLKIT
> +static void spice_usbredir_channel_open_acl_cb(
> +    GObject *gobject, GAsyncResult *acl_res, gpointer user_data)
> +{
> +    SpiceUsbAclHelper *acl_helper = SPICE_USB_ACL_HELPER(gobject);
> +    SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(user_data);
> +    SpiceUsbredirChannelPrivate *priv = channel->priv;
> +    GError *err = NULL;
> +
> +    g_return_if_fail(acl_helper == priv->acl_helper);
> +
> +    if (spice_usb_acl_helper_open_acl_finish(acl_helper, acl_res, &err))
> +        spice_usbredir_channel_open_device(channel, &err);
> +
> +    if (err) {
> +        g_simple_async_result_take_error(priv->result, err);
> +        g_clear_object(&priv->context);
> +        g_clear_object(&priv->device);
> +    }
> +
> +    spice_usb_acl_helper_close_acl(priv->acl_helper);
> +    g_clear_object(&priv->acl_helper);
> +
> +    g_simple_async_result_complete_in_idle(priv->result);
> +    g_clear_object(&priv->result);
> +}
> +#endif
> +
>  G_GNUC_INTERNAL
>  void spice_usbredir_channel_connect(SpiceUsbredirChannel *channel,
>                                      GUsbContext          *context,
> @@ -180,9 +217,21 @@ void spice_usbredir_channel_connect(SpiceUsbredirChannel *channel,
>      priv->context = g_object_ref(context);
>      priv->device  = g_object_ref(device);
>      if (!spice_usbredir_channel_open_device(channel, &err)) {
> +#if USE_POLKIT
> +        priv->result = result;
> +        priv->acl_helper = spice_usb_acl_helper_new();
> +        spice_usb_acl_helper_open_acl(priv->acl_helper,
> +                                      g_usb_device_get_bus(device),
> +                                      g_usb_device_get_address(device),
> +                                      cancellable,
> +                                      spice_usbredir_channel_open_acl_cb,
> +                                      channel);
> +        return;
> +#else
>          g_simple_async_result_take_error(result, err);
>          g_clear_object(&priv->context);
>          g_clear_object(&priv->device);
> +#endif
>      }
>  
>  done:
> @@ -214,13 +263,30 @@ void spice_usbredir_channel_disconnect(SpiceUsbredirChannel *channel)
>  
>      SPICE_DEBUG("disconnecting usb channel %p", channel);
>  
> -    spice_channel_disconnect(SPICE_CHANNEL(channel), SPICE_CHANNEL_NONE);
> -    priv->up = FALSE;
> +    if (priv->connected) {
> +        spice_channel_disconnect(SPICE_CHANNEL(channel), SPICE_CHANNEL_NONE);
> +        priv->connected = FALSE;
> +        priv->up = FALSE;
> +    }
>  
>      if (priv->host) {
>          /* This also closes the libusb handle we passed to its _open */
>          usbredirhost_close(priv->host);
>          priv->host = NULL;
> +    }
> +
> +#if USE_POLKIT
> +    /*
> +     * If we're still waiting for the acl helper cancel it and don't clear
> +     * priv->device and context, keeping this channel "busy" until
> +     * spice_usbredir_channel_open_acl_cb() has run from the main loop,
> +     * avoiding a new connect happening before the old connect has finished.
> +     */
> +    if (priv->acl_helper) {
> +        spice_usb_acl_helper_close_acl(priv->acl_helper);
> +    } else
> +#endif
> +    {
>          g_clear_object(&priv->device);
>          g_clear_object(&priv->context);
>      }
> diff --git a/gtk/spice-client-glib-usb-acl-helper.c b/gtk/spice-client-glib-usb-acl-helper.c
> new file mode 100644
> index 0000000..2e31bd3
> --- /dev/null
> +++ b/gtk/spice-client-glib-usb-acl-helper.c
> @@ -0,0 +1,284 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> +   Copyright (C) 2011 Red Hat, Inc.
> +   Copyright (C) 2009 Kay Sievers <kay.sievers at vrfy.org>
> +
> +   Red Hat Authors:
> +   Hans de Goede <hdegoede at redhat.com>
> +
> +   This program is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU General Public License as published
> +   by the Free Software Foundation; either version 2 of the License,
> +   or (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License along
> +   with this program; if not, see <http://www.gnu.org/licenses/>.
> +*/
> +
> +#ifdef HAVE_CONFIG_H
> +# include "config.h"
> +#endif
> +
> +#include <ctype.h>
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <gio/gunixinputstream.h>
> +#include <polkit/polkit.h>
> +#include <acl/libacl.h>
> +
> +#define FATAL_ERROR(...) \
> +    do { \
> +        /* We print the error both to stdout, for the app invoking us and \
> +           stderr for the end user */ \
> +        fprintf(stdout, "Error " __VA_ARGS__); \
> +        fprintf(stderr, "spice-client-glib-usb-helper: Error " __VA_ARGS__); \
> +        exit_status = 1; \
> +        cleanup(); \
> +    } while (0)
> +
> +#define ERROR(...) \
> +    do { \
> +        fprintf(stdout, __VA_ARGS__); \
> +        cleanup(); \
> +    } while (0)
> +
> +enum state {
> +    STATE_WAITING_FOR_BUS_N_DEV,
> +    STATE_WAITING_FOR_POL_KIT,
> +    STATE_WAITING_FOR_STDIN_EOF,
> +};
> +
> +static enum state state = STATE_WAITING_FOR_BUS_N_DEV;
> +static int exit_status;
> +static int busnum, devnum;
> +static char path[PATH_MAX];
> +static GMainLoop *loop;
> +static GDataInputStream *stdin_stream;
> +static GCancellable *polkit_cancellable;
> +static PolkitSubject *subject;
> +static PolkitAuthority *authority;
> +
> +/*
> + * This function is a copy of the same function in udev, written by Kay
> + * Sievers, you can find it in udev in extras/udev-acl/udev-acl.c
> + */
> +static int set_facl(const char* filename, uid_t uid, int add)
> +{
> +    int get;
> +    acl_t acl;
> +    acl_entry_t entry = NULL;
> +    acl_entry_t e;
> +    acl_permset_t permset;
> +    int ret;
> +
> +    /* don't touch ACLs for root */
> +    if (uid == 0)
> +        return 0;
> +
> +    /* read current record */
> +    acl = acl_get_file(filename, ACL_TYPE_ACCESS);
> +    if (!acl)
> +        return -1;
> +
> +    /* locate ACL_USER entry for uid */
> +    get = acl_get_entry(acl, ACL_FIRST_ENTRY, &e);
> +    while (get == 1) {
> +        acl_tag_t t;
> +
> +        acl_get_tag_type(e, &t);
> +        if (t == ACL_USER) {
> +            uid_t *u;
> +
> +            u = (uid_t*)acl_get_qualifier(e);
> +            if (u == NULL) {
> +                ret = -1;
> +                goto out;
> +            }
> +            if (*u == uid) {
> +                entry = e;
> +                acl_free(u);
> +                break;
> +            }
> +            acl_free(u);
> +        }
> +
> +        get = acl_get_entry(acl, ACL_NEXT_ENTRY, &e);
> +    }
> +
> +    /* remove ACL_USER entry for uid */
> +    if (!add) {
> +        if (entry == NULL) {
> +            ret = 0;
> +            goto out;
> +        }
> +        acl_delete_entry(acl, entry);
> +        goto update;
> +    }
> +
> +    /* create ACL_USER entry for uid */
> +    if (entry == NULL) {
> +        ret = acl_create_entry(&acl, &entry);
> +        if (ret != 0)
> +            goto out;
> +        acl_set_tag_type(entry, ACL_USER);
> +        acl_set_qualifier(entry, &uid);
> +    }
> +
> +    /* add permissions for uid */
> +    acl_get_permset(entry, &permset);
> +    acl_add_perm(permset, ACL_READ|ACL_WRITE);
> +update:
> +    /* update record */
> +    acl_calc_mask(&acl);
> +    ret = acl_set_file(filename, ACL_TYPE_ACCESS, acl);
> +    if (ret != 0)
> +        goto out;
> +out:
> +    acl_free(acl);
> +    return ret;
> +}
> +
> +static void cleanup()
> +{
> +    if (polkit_cancellable)
> +        g_cancellable_cancel(polkit_cancellable);
> +
> +    if (state == STATE_WAITING_FOR_STDIN_EOF)
> +        set_facl(path, getuid(), 0);
> +
> +    g_main_loop_quit(loop);
> +}
> +
> +static void check_authorization_cb(PolkitAuthority *authority,
> +                                   GAsyncResult *res, gpointer data)
> +{
> +    PolkitAuthorizationResult *result;
> +    GError *err = NULL;
> +
> +    g_clear_object(&polkit_cancellable);
> +
> +    result = polkit_authority_check_authorization_finish(authority, res, &err);
> +    if (err) {
> +        FATAL_ERROR("PoliciKit error: %s\n", err->message);
> +        g_error_free(err);
> +        return;
> +    }
> +
> +    if (!polkit_authorization_result_get_is_authorized(result)) {
> +        ERROR("Not authorized\n");
> +        return;
> +    }
> +
> +    snprintf(path, PATH_MAX, "/dev/bus/usb/%03d/%03d", busnum, devnum);
> +    if (set_facl(path, getuid(), 1)) {
> +        FATAL_ERROR("setting facl: %s\n", strerror(errno));
> +        return;
> +    }
> +
> +    fprintf(stdout, "SUCCESS\n");
> +    fflush(stdout);
> +    state = STATE_WAITING_FOR_STDIN_EOF;
> +}
> +
> +static void stdin_read_complete(GObject *src, GAsyncResult *res, gpointer data)
> +{
> +    char *s, *ep;
> +    GError *err = NULL;
> +    gsize len;
> +
> +    s = g_data_input_stream_read_line_finish(G_DATA_INPUT_STREAM(src), res,
> +                                             &len, &err);
> +    if (!s) {
> +        if (err) {
> +            FATAL_ERROR("Reading from stdin: %s\n", err->message);
> +            g_error_free(err);
> +            return;
> +        }
> +
> +        switch (state) {
> +        case STATE_WAITING_FOR_BUS_N_DEV:
> +            FATAL_ERROR("EOF while waiting for bus and device num\n");
> +            break;
> +        case STATE_WAITING_FOR_POL_KIT:
> +            ERROR("Cancelled while waiting for authorization\n");
> +            break;
> +        case STATE_WAITING_FOR_STDIN_EOF:
> +            cleanup();
> +            break;
> +        }
> +        return;
> +    }
> +
> +    switch (state) {
> +    case STATE_WAITING_FOR_BUS_N_DEV:
> +        busnum = strtol(s, &ep, 10);
> +        if (!isspace(*ep)) {
> +            FATAL_ERROR("Invalid busnum / devnum: %s\n", s);
> +            break;
> +        }
> +        devnum = strtol(ep, &ep, 10);
> +        if (*ep != '\0') {
> +            FATAL_ERROR("Invalid busnum / devnum: %s\n", s);
> +            break;
> +        }
> +
> +        state = STATE_WAITING_FOR_POL_KIT;
> +
> +        polkit_cancellable = g_cancellable_new();
> +        polkit_authority_check_authorization(
> +            authority, subject, "org.spice-space.lowlevelusbaccess", NULL,
> +            POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
> +            polkit_cancellable, (GAsyncReadyCallback)check_authorization_cb,
> +            loop);
> +
> +        g_data_input_stream_read_line_async(stdin_stream, G_PRIORITY_DEFAULT,
> +                                            NULL, stdin_read_complete, NULL);
> +        break;
> +    default:
> +        FATAL_ERROR("Unexpected extra input in state %d: %s\n", state, s);
> +    }
> +}
> +
> +int main(void)
> +{
> +    pid_t parent_pid;
> +    GInputStream *stdin_unix_stream;
> +
> +    g_type_init();
> +
> +    loop = g_main_loop_new(NULL, FALSE);
> +
> +    authority = polkit_authority_get_sync(NULL, NULL);
> +    parent_pid = getppid ();
> +    if (parent_pid == 1) {
> +            FATAL_ERROR("Parent process was reaped by init(1)\n");
> +            return 1;
> +    }
> +    subject = polkit_unix_process_new(parent_pid);
> +
> +    stdin_unix_stream = g_unix_input_stream_new(STDIN_FILENO, 0);
> +    stdin_stream = g_data_input_stream_new(stdin_unix_stream);
> +    g_data_input_stream_set_newline_type(stdin_stream,
> +                                         G_DATA_STREAM_NEWLINE_TYPE_LF);
> +    g_clear_object(&stdin_unix_stream);
> +    g_data_input_stream_read_line_async(stdin_stream, G_PRIORITY_DEFAULT, NULL,
> +                                        stdin_read_complete, NULL);
> +
> +    g_main_loop_run(loop);
> +
> +    if (polkit_cancellable)
> +        g_clear_object(&polkit_cancellable);
> +    g_object_unref(stdin_stream);
> +    g_object_unref(authority);
> +    g_object_unref(subject);
> +    g_main_loop_unref(loop);
> +
> +    return exit_status;
> +}
> diff --git a/gtk/usb-acl-helper.c b/gtk/usb-acl-helper.c
> new file mode 100644
> index 0000000..e5f8b4d
> --- /dev/null
> +++ b/gtk/usb-acl-helper.c
> @@ -0,0 +1,287 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> +   Copyright (C) 2011 Red Hat, Inc.
> +
> +   Red Hat Authors:
> +   Hans de Goede <hdegoede at redhat.com>
> +
> +   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/>.
> +*/
> +
> +#include "config.h"
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <string.h>
> +
> +#include "usb-acl-helper.h"
> +
> +/* ------------------------------------------------------------------ */
> +/* gobject glue                                                       */
> +
> +#define SPICE_USB_ACL_HELPER_GET_PRIVATE(obj)                                  \
> +    (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperPrivate))
> +
> +struct _SpiceUsbAclHelperPrivate {
> +    GSimpleAsyncResult *result;
> +    GIOChannel *in_ch;
> +    GIOChannel *out_ch;
> +    GCancellable *cancellable;
> +    gulong cancellable_id;
> +};
> +
> +G_DEFINE_TYPE(SpiceUsbAclHelper, spice_usb_acl_helper, G_TYPE_OBJECT);
> +
> +static void spice_usb_acl_helper_init(SpiceUsbAclHelper *self)
> +{
> +    self->priv = SPICE_USB_ACL_HELPER_GET_PRIVATE(self);
> +}
> +
> +static void spice_usb_acl_helper_cleanup(SpiceUsbAclHelper *self)
> +{
> +    SpiceUsbAclHelperPrivate *priv = self->priv;
> +
> +    g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
> +    priv->cancellable = NULL;
> +    priv->cancellable_id = 0;
> +
> +    g_clear_object(&priv->result);
> +
> +    if (priv->in_ch) {
> +        g_io_channel_unref(priv->in_ch);
> +        priv->in_ch = NULL;
> +    }
> +
> +    if (priv->out_ch) {
> +        g_io_channel_unref(priv->out_ch);
> +        priv->out_ch = NULL;
> +    }
> +}
> +
> +static void spice_usb_acl_helper_finalize(GObject *gobject)
> +{
> +    spice_usb_acl_helper_cleanup(SPICE_USB_ACL_HELPER(gobject));
> +}
> +
> +static void spice_usb_acl_helper_class_init(SpiceUsbAclHelperClass *klass)
> +{
> +    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
> +
> +    gobject_class->finalize     = spice_usb_acl_helper_finalize;
> +
> +    g_type_class_add_private(klass, sizeof(SpiceUsbAclHelperPrivate));
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* callbacks                                                          */
> +
> +static gboolean cb_out_watch(GIOChannel    *channel,
> +                             GIOCondition   cond,
> +                             gpointer      *user_data)
> +{
> +    SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
> +    SpiceUsbAclHelperPrivate *priv = self->priv;
> +    gboolean success = FALSE;
> +    GError *err = NULL;
> +    GIOStatus status;
> +    gchar *string;
> +    gsize size;
> +
> +    /* Check that we've not been cancelled */
> +    if (priv->result == NULL)
> +        goto done;
> +
> +    g_return_val_if_fail(channel == priv->out_ch, FALSE);
> +
> +    status = g_io_channel_read_line(priv->out_ch, &string, &size, NULL, &err);
> +    switch (status) {
> +        case G_IO_STATUS_NORMAL:
> +            string[strlen(string) - 1] = 0;
> +            if (!strcmp(string, "SUCCESS")) {
> +                success = TRUE;
> +            } else {
> +                g_simple_async_result_set_error(priv->result,
> +                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> +                            "Error setting USB device node ACL: '%s'",
> +                            string);
> +            }
> +            g_free(string);
> +            break;
> +        case G_IO_STATUS_ERROR:
> +            g_simple_async_result_take_error(priv->result, err);
> +            break;
> +        case G_IO_STATUS_EOF:
> +            g_simple_async_result_set_error(priv->result,
> +                        SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> +                        "Unexpected EOF reading from acl helper stdout");
> +        case G_IO_STATUS_AGAIN:
> +            return TRUE; /* Wait for more input */
> +    }
> +
> +    g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
> +    priv->cancellable = NULL;
> +    priv->cancellable_id = 0;
> +
> +    g_simple_async_result_complete_in_idle(priv->result);
> +    g_clear_object(&priv->result);
> +
> +    if (!success)
> +        spice_usb_acl_helper_cleanup(self);
> +
> +done:
> +    g_object_unref(self);
> +    return FALSE;
> +}
> +
> +static void cancelled_cb(GCancellable *cancellable, gpointer user_data)
> +{
> +    SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
> +
> +    spice_usb_acl_helper_close_acl(self);
> +}
> +
> +static void helper_child_watch_cb(GPid pid, gint status, gpointer user_data)
> +{
> +    /* Nothing to do, but we need the child watch to avoid zombies */
> +}
> +
> +/* ------------------------------------------------------------------ */
> +/* private api                                                        */
> +
> +G_GNUC_INTERNAL
> +SpiceUsbAclHelper *spice_usb_acl_helper_new(void)
> +{
> +    GObject *obj;
> +
> +    obj = g_object_new(SPICE_TYPE_USB_ACL_HELPER, NULL);
> +
> +    return SPICE_USB_ACL_HELPER(obj);
> +}
> +
> +G_GNUC_INTERNAL
> +void spice_usb_acl_helper_open_acl(SpiceUsbAclHelper *self,
> +                                   gint busnum, gint devnum,
> +                                   GCancellable *cancellable,
> +                                   GAsyncReadyCallback callback,
> +                                   gpointer user_data)
> +{
> +    g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
> +
> +    SpiceUsbAclHelperPrivate *priv = self->priv;
> +    GSimpleAsyncResult *result;
> +    GError *err = NULL;
> +    GIOStatus status;
> +    GPid helper_pid;
> +    gsize bytes_written;
> +    gchar *argv[] = { "spice-client-glib-usb-acl-helper", NULL };
> +    gint in, out;
> +    gchar buf[128];
> +
> +    result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
> +                                       spice_usb_acl_helper_open_acl);
> +
> +    if (priv->out_ch) {
> +        g_simple_async_result_set_error(result,
> +                            SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> +                            "Error acl-helper already has an acl open");
> +        goto done;
> +    }
> +
> +    if (g_cancellable_set_error_if_cancelled(cancellable, &err)) {
> +        g_simple_async_result_take_error(result, err);
> +        goto done;
> +    }
> +
> +    if (!g_spawn_async_with_pipes(NULL, argv, NULL,
> +                           G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
> +                           NULL, NULL, &helper_pid, &in, &out, NULL, &err)) {
> +        g_simple_async_result_take_error(result, err);
> +        goto done;
> +    }
> +    g_child_watch_add(helper_pid, helper_child_watch_cb, NULL);
> +
> +    priv->in_ch = g_io_channel_unix_new(in);
> +    g_io_channel_set_close_on_unref(priv->in_ch, TRUE);
> +
> +    priv->out_ch = g_io_channel_unix_new(out);
> +    g_io_channel_set_close_on_unref(priv->out_ch, TRUE);
> +    status = g_io_channel_set_flags(priv->out_ch, G_IO_FLAG_NONBLOCK, &err);
> +    if (status != G_IO_STATUS_NORMAL) {
> +        g_simple_async_result_take_error(result, err);
> +        goto done;
> +    }
> +
> +    snprintf(buf, sizeof(buf), "%d %d\n", busnum, devnum);
> +    status = g_io_channel_write_chars(priv->in_ch, buf, -1,
> +                                      &bytes_written, &err);
> +    if (status != G_IO_STATUS_NORMAL) {
> +        g_simple_async_result_take_error(result, err);
> +        goto done;
> +    }
> +    status = g_io_channel_flush(priv->in_ch, &err);
> +    if (status != G_IO_STATUS_NORMAL) {
> +        g_simple_async_result_take_error(result, err);
> +        goto done;
> +    }
> +
> +    priv->result = result;
> +    if (cancellable) {
> +        priv->cancellable = cancellable;
> +        priv->cancellable_id = g_cancellable_connect(cancellable,
> +                                                     G_CALLBACK(cancelled_cb),
> +                                                     self, NULL);
> +    }
> +    g_io_add_watch(priv->out_ch, G_IO_IN|G_IO_HUP,
> +                   (GIOFunc)cb_out_watch, g_object_ref(self));
> +    return;
> +
> +done:
> +    spice_usb_acl_helper_cleanup(self);
> +    g_simple_async_result_complete_in_idle(result);
> +    g_object_unref(result);
> +}
> +
> +G_GNUC_INTERNAL
> +gboolean spice_usb_acl_helper_open_acl_finish(
> +    SpiceUsbAclHelper *self, GAsyncResult *res, GError **err)
> +{
> +    GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
> +
> +    g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
> +                                               spice_usb_acl_helper_open_acl),
> +                         FALSE);
> +
> +    if (g_simple_async_result_propagate_error(result, err))
> +        return FALSE;
> +
> +    return TRUE;
> +}
> +
> +G_GNUC_INTERNAL
> +void spice_usb_acl_helper_close_acl(SpiceUsbAclHelper *self)
> +{
> +    g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
> +
> +    SpiceUsbAclHelperPrivate *priv = self->priv;
> +
> +    /* If the acl open has not completed yet report it as cancelled */
> +    if (priv->result) {
> +        g_simple_async_result_set_error(priv->result,
> +                    G_IO_ERROR, G_IO_ERROR_CANCELLED,
> +                    "Setting USB device node ACL cancelled");
> +        g_simple_async_result_complete_in_idle(priv->result);
> +    }
> +
> +    spice_usb_acl_helper_cleanup(self);
> +}
> diff --git a/gtk/usb-acl-helper.h b/gtk/usb-acl-helper.h
> new file mode 100644
> index 0000000..c43bdd0
> --- /dev/null
> +++ b/gtk/usb-acl-helper.h
> @@ -0,0 +1,75 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> +   Copyright (C) 2011 Red Hat, Inc.
> +
> +   Red Hat Authors:
> +   Hans de Goede <hdegoede at redhat.com>
> +
> +   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/>.
> +*/
> +#ifndef __SPICE_USB_ACL_HELPER_H__
> +#define __SPICE_USB_ACL_HELPER_H__
> +
> +#include "spice-client.h"
> +#include <gio/gio.h>
> +
> +/* Note the entire usb-acl-helper class is private to spice-client-glib !! */
> +
> +G_BEGIN_DECLS
> +
> +#define SPICE_TYPE_USB_ACL_HELPER            (spice_usb_acl_helper_get_type ())
> +#define SPICE_USB_ACL_HELPER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelper))
> +#define SPICE_USB_ACL_HELPER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperClass))
> +#define SPICE_IS_USB_ACL_HELPER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_USB_ACL_HELPER))
> +#define SPICE_IS_USB_ACL_HELPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_USB_ACL_HELPER))
> +#define SPICE_USB_ACL_HELPER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_USB_ACL_HELPER, SpiceUsbAclHelperClass))
> +
> +#define SPICE_TYPE_USB_DEVICE                    (spice_usb_device_get_type())
> +
> +typedef struct _SpiceUsbAclHelper SpiceUsbAclHelper;
> +typedef struct _SpiceUsbAclHelperClass SpiceUsbAclHelperClass;
> +typedef struct _SpiceUsbAclHelperPrivate SpiceUsbAclHelperPrivate;
> +
> +struct _SpiceUsbAclHelper
> +{
> +    GObject parent;
> +
> +    /*< private >*/
> +    SpiceUsbAclHelperPrivate *priv;
> +    /* Do not add fields to this struct */
> +};
> +
> +struct _SpiceUsbAclHelperClass
> +{
> +    GObjectClass parent_class;
> +};
> +
> +GType spice_usb_device_get_type(void);
> +GType spice_usb_acl_helper_get_type(void);
> +
> +SpiceUsbAclHelper *spice_usb_acl_helper_new(void);
> +
> +void spice_usb_acl_helper_open_acl(SpiceUsbAclHelper *self,
> +                                   gint busnum, gint devnum,
> +                                   GCancellable *cancellable,
> +                                   GAsyncReadyCallback callback,
> +                                   gpointer user_data);
> +gboolean spice_usb_acl_helper_open_acl_finish(
> +    SpiceUsbAclHelper *self, GAsyncResult *res, GError **err);
> +
> +void spice_usb_acl_helper_close_acl(SpiceUsbAclHelper *self);
> +
> +G_END_DECLS
> +
> +#endif /* __SPICE_USB_ACL_HELPER_H__ */
> -- 
> 1.7.7.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