[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