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

Hans de Goede hdegoede at redhat.com
Tue Nov 15 07:31:00 PST 2011


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 ++
 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



More information about the Spice-devel mailing list