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