1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3    Copyright (C) 2011 Red Hat, Inc.
4 
5    Red Hat Authors:
6    Hans de Goede <hdegoede@redhat.com>
7 
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Lesser General Public
10    License as published by the Free Software Foundation; either
11    version 2.1 of the License, or (at your option) any later version.
12 
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Lesser General Public License for more details.
17 
18    You should have received a copy of the GNU Lesser General Public
19    License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 #include "config.h"
23 
24 #include <errno.h>
25 #include <stdio.h>
26 #include <string.h>
27 
28 #include "usb-acl-helper.h"
29 
30 struct _SpiceUsbAclHelperPrivate {
31     GTask *task;
32     GIOChannel *in_ch;
33     GIOChannel *out_ch;
34     GCancellable *cancellable;
35     gulong cancellable_id;
36 };
37 
G_DEFINE_TYPE_WITH_PRIVATE(SpiceUsbAclHelper,spice_usb_acl_helper,G_TYPE_OBJECT)38 G_DEFINE_TYPE_WITH_PRIVATE(SpiceUsbAclHelper, spice_usb_acl_helper, G_TYPE_OBJECT)
39 
40 static void spice_usb_acl_helper_init(SpiceUsbAclHelper *self)
41 {
42     self->priv = spice_usb_acl_helper_get_instance_private(self);
43 }
44 
spice_usb_acl_helper_cleanup(SpiceUsbAclHelper * self)45 static void spice_usb_acl_helper_cleanup(SpiceUsbAclHelper *self)
46 {
47     SpiceUsbAclHelperPrivate *priv = self->priv;
48 
49     g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
50     priv->cancellable = NULL;
51     priv->cancellable_id = 0;
52 
53     g_clear_object(&priv->task);
54 
55     g_clear_pointer(&priv->in_ch, g_io_channel_unref);
56     g_clear_pointer(&priv->out_ch, g_io_channel_unref);
57 }
58 
spice_usb_acl_helper_finalize(GObject * gobject)59 static void spice_usb_acl_helper_finalize(GObject *gobject)
60 {
61     spice_usb_acl_helper_cleanup(SPICE_USB_ACL_HELPER(gobject));
62 
63     if (G_OBJECT_CLASS(spice_usb_acl_helper_parent_class)->finalize)
64         G_OBJECT_CLASS(spice_usb_acl_helper_parent_class)->finalize(gobject);
65 }
66 
spice_usb_acl_helper_class_init(SpiceUsbAclHelperClass * klass)67 static void spice_usb_acl_helper_class_init(SpiceUsbAclHelperClass *klass)
68 {
69     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
70 
71     gobject_class->finalize     = spice_usb_acl_helper_finalize;
72 }
73 
74 /* ------------------------------------------------------------------ */
75 /* callbacks                                                          */
76 
async_result_set_cancelled(GTask * task)77 static void async_result_set_cancelled(GTask *task)
78 {
79     g_task_return_new_error(task,
80                 G_IO_ERROR, G_IO_ERROR_CANCELLED,
81                 "Setting USB device node ACL cancelled");
82 }
83 
cb_out_watch(GIOChannel * channel,GIOCondition cond,gpointer * user_data)84 static gboolean cb_out_watch(GIOChannel    *channel,
85                              GIOCondition   cond,
86                              gpointer      *user_data)
87 {
88     SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
89     SpiceUsbAclHelperPrivate *priv = self->priv;
90     gboolean success = FALSE;
91     GError *err = NULL;
92     GIOStatus status;
93     gchar *string;
94     gsize size;
95 
96     /* Check that we've not been cancelled */
97     if (priv->task == NULL)
98         goto done;
99 
100     g_return_val_if_fail(channel == priv->out_ch, FALSE);
101 
102     status = g_io_channel_read_line(priv->out_ch, &string, &size, NULL, &err);
103     switch (status) {
104         case G_IO_STATUS_NORMAL:
105             string[strlen(string) - 1] = 0;
106             if (!strcmp(string, "SUCCESS")) {
107                 success = TRUE;
108                 g_task_return_boolean(priv->task, TRUE);
109             } else if (!strcmp(string, "CANCELED")) {
110                 async_result_set_cancelled(priv->task);
111             } else {
112                 g_task_return_new_error(priv->task,
113                             SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
114                             "Error setting USB device node ACL: '%s'",
115                             string);
116             }
117             g_free(string);
118             break;
119         case G_IO_STATUS_ERROR:
120             g_task_return_error(priv->task, err);
121             break;
122         case G_IO_STATUS_EOF:
123             g_task_return_new_error(priv->task,
124                         SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
125                         "Unexpected EOF reading from acl helper stdout");
126             break;
127         case G_IO_STATUS_AGAIN:
128             return TRUE; /* Wait for more input */
129     }
130 
131     g_cancellable_disconnect(priv->cancellable, priv->cancellable_id);
132     priv->cancellable = NULL;
133     priv->cancellable_id = 0;
134 
135     g_clear_object(&priv->task);
136 
137     if (!success)
138         spice_usb_acl_helper_cleanup(self);
139 
140 done:
141     g_object_unref(self);
142     return FALSE;
143 }
144 
cancelled_cb(GCancellable * cancellable,gpointer user_data)145 static void cancelled_cb(GCancellable *cancellable, gpointer user_data)
146 {
147     SpiceUsbAclHelper *self = SPICE_USB_ACL_HELPER(user_data);
148 
149     spice_usb_acl_helper_cancel(self);
150 }
151 
helper_child_watch_cb(GPid pid,gint status,gpointer user_data)152 static void helper_child_watch_cb(GPid pid, gint status, gpointer user_data)
153 {
154     /* Nothing to do, but we need the child watch to avoid zombies */
155 }
156 
157 /* ------------------------------------------------------------------ */
158 /* private api                                                        */
159 
160 G_GNUC_INTERNAL
spice_usb_acl_helper_new(void)161 SpiceUsbAclHelper *spice_usb_acl_helper_new(void)
162 {
163     GObject *obj;
164 
165     obj = g_object_new(SPICE_TYPE_USB_ACL_HELPER, NULL);
166 
167     return SPICE_USB_ACL_HELPER(obj);
168 }
169 
170 G_GNUC_INTERNAL
spice_usb_acl_helper_open_acl_async(SpiceUsbAclHelper * self,gint busnum,gint devnum,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)171 void spice_usb_acl_helper_open_acl_async(SpiceUsbAclHelper *self,
172                                          gint busnum, gint devnum,
173                                          GCancellable *cancellable,
174                                          GAsyncReadyCallback callback,
175                                          gpointer user_data)
176 {
177     g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
178 
179     SpiceUsbAclHelperPrivate *priv = self->priv;
180     GTask *task;
181     GError *err = NULL;
182     GIOStatus status;
183     GPid helper_pid;
184     gsize bytes_written;
185     const gchar *acl_helper = g_getenv("SPICE_USB_ACL_BINARY");
186     if (acl_helper == NULL)
187         acl_helper = ACL_HELPER_PATH"/spice-client-glib-usb-acl-helper";
188     gchar *argv[] = { (char*)acl_helper, NULL };
189     gint in, out;
190     gchar buf[128];
191 
192     task = g_task_new(self, cancellable, callback, user_data);
193 
194     if (priv->out_ch) {
195         g_task_return_new_error(task,
196                             SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
197                             "Error acl-helper already has an acl open");
198         goto done;
199     }
200 
201     if (g_cancellable_set_error_if_cancelled(cancellable, &err)) {
202         g_task_return_error(task, err);
203         goto done;
204     }
205 
206     if (!g_spawn_async_with_pipes(NULL, argv, NULL,
207                            G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
208                            NULL, NULL, &helper_pid, &in, &out, NULL, &err)) {
209         g_task_return_error(task, err);
210         goto done;
211     }
212     g_child_watch_add(helper_pid, helper_child_watch_cb, NULL);
213 
214     priv->in_ch = g_io_channel_unix_new(in);
215     g_io_channel_set_close_on_unref(priv->in_ch, TRUE);
216 
217     priv->out_ch = g_io_channel_unix_new(out);
218     g_io_channel_set_close_on_unref(priv->out_ch, TRUE);
219     status = g_io_channel_set_flags(priv->out_ch, G_IO_FLAG_NONBLOCK, &err);
220     if (status != G_IO_STATUS_NORMAL) {
221         g_task_return_error(task, err);
222         goto done;
223     }
224 
225     snprintf(buf, sizeof(buf), "%d %d\n", busnum, devnum);
226     status = g_io_channel_write_chars(priv->in_ch, buf, -1,
227                                       &bytes_written, &err);
228     if (status != G_IO_STATUS_NORMAL) {
229         g_task_return_error(task, err);
230         goto done;
231     }
232     status = g_io_channel_flush(priv->in_ch, &err);
233     if (status != G_IO_STATUS_NORMAL) {
234         g_task_return_error(task, err);
235         goto done;
236     }
237 
238     priv->task = task;
239     if (cancellable) {
240         priv->cancellable = cancellable;
241         priv->cancellable_id = g_cancellable_connect(cancellable,
242                                                      G_CALLBACK(cancelled_cb),
243                                                      self, NULL);
244     }
245     g_io_add_watch(priv->out_ch, G_IO_IN|G_IO_HUP,
246                    (GIOFunc)cb_out_watch, g_object_ref(self));
247     return;
248 
249 done:
250     spice_usb_acl_helper_cleanup(self);
251     g_object_unref(task);
252 }
253 
254 G_GNUC_INTERNAL
spice_usb_acl_helper_open_acl_finish(SpiceUsbAclHelper * self,GAsyncResult * res,GError ** err)255 gboolean spice_usb_acl_helper_open_acl_finish(
256     SpiceUsbAclHelper *self, GAsyncResult *res, GError **err)
257 {
258     GTask *task = G_TASK(res);
259 
260     g_return_val_if_fail(g_task_is_valid(task, self),
261                          FALSE);
262 
263     return g_task_propagate_boolean(task, err);
264 }
265 
266 G_GNUC_INTERNAL
spice_usb_acl_helper_cancel(SpiceUsbAclHelper * self)267 void spice_usb_acl_helper_cancel(SpiceUsbAclHelper *self)
268 {
269     g_return_if_fail(SPICE_IS_USB_ACL_HELPER(self));
270 
271     SpiceUsbAclHelperPrivate *priv = self->priv;
272     g_return_if_fail(priv->task != NULL);
273 
274     async_result_set_cancelled(priv->task);
275     g_clear_object(&priv->task);
276 }
277