]>
Commit | Line | Data |
---|---|---|
bf05675a DZ |
1 | /* -*- Mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- |
2 | * | |
cbdf255e | 3 | * Copyright (C) 2008-2010 David Zeuthen <davidz@redhat.com> |
bf05675a DZ |
4 | * |
5 | * This library is free software; you can redistribute it and/or | |
6 | * modify it under the terms of the GNU Lesser General Public | |
7 | * License as published by the Free Software Foundation; either | |
8 | * version 2 of the License, or (at your option) any later version. | |
9 | * | |
10 | * This library is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | * Lesser General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU Lesser General Public | |
16 | * License along with this library; if not, write to the | |
17 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
18 | * Boston, MA 02111-1307, USA. | |
19 | * | |
20 | */ | |
21 | ||
22 | #ifdef HAVE_CONFIG_H | |
23 | # include "config.h" | |
24 | #endif | |
25 | ||
26 | #include <stdlib.h> | |
27 | #include <string.h> | |
28 | ||
29 | #include "gudevclient.h" | |
30 | #include "gudevdevice.h" | |
31 | #include "gudevmarshal.h" | |
32 | #include "gudevprivate.h" | |
33 | ||
34 | /** | |
35 | * SECTION:gudevclient | |
36 | * @short_description: Query devices and listen to uevents | |
37 | * | |
38 | * #GUdevClient is used to query information about devices on a Linux | |
39 | * system from the Linux kernel and the udev device | |
40 | * manager. | |
41 | * | |
42 | * Device information is retrieved from the kernel (through the | |
43 | * <literal>sysfs</literal> filesystem) and the udev daemon (through a | |
44 | * <literal>tmpfs</literal> filesystem) and presented through | |
45 | * #GUdevDevice objects. This means that no blocking IO ever happens | |
46 | * (in both cases, we are essentially just reading data from kernel | |
47 | * memory) and as such there are no asynchronous versions of the | |
48 | * provided methods. | |
49 | * | |
0976fd06 | 50 | * To get #GUdevDevice objects, use |
bf05675a DZ |
51 | * g_udev_client_query_by_subsystem(), |
52 | * g_udev_client_query_by_device_number(), | |
53 | * g_udev_client_query_by_device_file(), | |
0976fd06 DZ |
54 | * g_udev_client_query_by_sysfs_path(), |
55 | * g_udev_client_query_by_subsystem_and_name() | |
56 | * or the #GUdevEnumerator type. | |
bf05675a DZ |
57 | * |
58 | * To listen to uevents, connect to the #GUdevClient::uevent signal. | |
59 | */ | |
60 | ||
61 | struct _GUdevClientPrivate | |
62 | { | |
cbdf255e | 63 | GSource *watch_source; |
bf05675a DZ |
64 | struct udev *udev; |
65 | struct udev_monitor *monitor; | |
66 | ||
67 | gchar **subsystems; | |
68 | }; | |
69 | ||
70 | enum | |
71 | { | |
72 | PROP_0, | |
73 | PROP_SUBSYSTEMS, | |
74 | }; | |
75 | ||
76 | enum | |
77 | { | |
78 | UEVENT_SIGNAL, | |
79 | LAST_SIGNAL, | |
80 | }; | |
81 | ||
82 | static guint signals[LAST_SIGNAL] = { 0 }; | |
83 | ||
84 | G_DEFINE_TYPE (GUdevClient, g_udev_client, G_TYPE_OBJECT) | |
85 | ||
86 | /* ---------------------------------------------------------------------------------------------------- */ | |
87 | ||
88 | static gboolean | |
89 | monitor_event (GIOChannel *source, | |
912541b0 KS |
90 | GIOCondition condition, |
91 | gpointer data) | |
bf05675a DZ |
92 | { |
93 | GUdevClient *client = (GUdevClient *) data; | |
94 | GUdevDevice *device; | |
95 | struct udev_device *udevice; | |
96 | ||
e4dcdc4a MP |
97 | if (client->priv->monitor == NULL) |
98 | goto out; | |
bf05675a DZ |
99 | udevice = udev_monitor_receive_device (client->priv->monitor); |
100 | if (udevice == NULL) | |
101 | goto out; | |
102 | ||
103 | device = _g_udev_device_new (udevice); | |
104 | udev_device_unref (udevice); | |
105 | g_signal_emit (client, | |
106 | signals[UEVENT_SIGNAL], | |
107 | 0, | |
108 | g_udev_device_get_action (device), | |
109 | device); | |
110 | g_object_unref (device); | |
111 | ||
112 | out: | |
113 | return TRUE; | |
114 | } | |
115 | ||
116 | static void | |
117 | g_udev_client_finalize (GObject *object) | |
118 | { | |
119 | GUdevClient *client = G_UDEV_CLIENT (object); | |
120 | ||
cbdf255e | 121 | if (client->priv->watch_source != NULL) |
bf05675a | 122 | { |
cbdf255e DZ |
123 | g_source_destroy (client->priv->watch_source); |
124 | client->priv->watch_source = NULL; | |
bf05675a DZ |
125 | } |
126 | ||
127 | if (client->priv->monitor != NULL) | |
128 | { | |
129 | udev_monitor_unref (client->priv->monitor); | |
130 | client->priv->monitor = NULL; | |
131 | } | |
132 | ||
133 | if (client->priv->udev != NULL) | |
134 | { | |
135 | udev_unref (client->priv->udev); | |
136 | client->priv->udev = NULL; | |
137 | } | |
138 | ||
139 | g_strfreev (client->priv->subsystems); | |
140 | ||
141 | if (G_OBJECT_CLASS (g_udev_client_parent_class)->finalize != NULL) | |
142 | G_OBJECT_CLASS (g_udev_client_parent_class)->finalize (object); | |
143 | } | |
144 | ||
145 | static void | |
146 | g_udev_client_set_property (GObject *object, | |
147 | guint prop_id, | |
148 | const GValue *value, | |
149 | GParamSpec *pspec) | |
150 | { | |
151 | GUdevClient *client = G_UDEV_CLIENT (object); | |
152 | ||
153 | switch (prop_id) | |
154 | { | |
155 | case PROP_SUBSYSTEMS: | |
156 | if (client->priv->subsystems != NULL) | |
157 | g_strfreev (client->priv->subsystems); | |
158 | client->priv->subsystems = g_strdupv (g_value_get_boxed (value)); | |
159 | break; | |
160 | ||
161 | default: | |
162 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
163 | break; | |
164 | } | |
165 | } | |
166 | ||
167 | static void | |
168 | g_udev_client_get_property (GObject *object, | |
169 | guint prop_id, | |
170 | GValue *value, | |
171 | GParamSpec *pspec) | |
172 | { | |
173 | GUdevClient *client = G_UDEV_CLIENT (object); | |
174 | ||
175 | switch (prop_id) | |
176 | { | |
177 | case PROP_SUBSYSTEMS: | |
178 | g_value_set_boxed (value, client->priv->subsystems); | |
179 | break; | |
180 | ||
181 | default: | |
182 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
183 | break; | |
184 | } | |
185 | } | |
186 | ||
187 | static void | |
188 | g_udev_client_constructed (GObject *object) | |
189 | { | |
190 | GUdevClient *client = G_UDEV_CLIENT (object); | |
191 | GIOChannel *channel; | |
192 | guint n; | |
193 | ||
194 | client->priv->udev = udev_new (); | |
195 | ||
196 | /* connect to event source */ | |
197 | client->priv->monitor = udev_monitor_new_from_netlink (client->priv->udev, "udev"); | |
198 | ||
199 | //g_debug ("ss = %p", client->priv->subsystems); | |
200 | ||
201 | if (client->priv->subsystems != NULL) | |
202 | { | |
214a6c79 | 203 | /* install subsystem filters to only wake up for certain events */ |
bf05675a DZ |
204 | for (n = 0; client->priv->subsystems[n] != NULL; n++) |
205 | { | |
206 | gchar *subsystem; | |
207 | gchar *devtype; | |
208 | gchar *s; | |
209 | ||
210 | subsystem = g_strdup (client->priv->subsystems[n]); | |
211 | devtype = NULL; | |
212 | ||
213 | //g_debug ("s = '%s'", subsystem); | |
214 | ||
215 | s = strstr (subsystem, "/"); | |
216 | if (s != NULL) | |
217 | { | |
218 | devtype = s + 1; | |
219 | *s = '\0'; | |
220 | } | |
221 | ||
e4dcdc4a MP |
222 | if (client->priv->monitor != NULL) |
223 | udev_monitor_filter_add_match_subsystem_devtype (client->priv->monitor, subsystem, devtype); | |
bf05675a DZ |
224 | |
225 | g_free (subsystem); | |
226 | } | |
227 | ||
228 | /* listen to events, and buffer them */ | |
e4dcdc4a MP |
229 | if (client->priv->monitor != NULL) |
230 | { | |
231 | udev_monitor_enable_receiving (client->priv->monitor); | |
232 | channel = g_io_channel_unix_new (udev_monitor_get_fd (client->priv->monitor)); | |
cbdf255e | 233 | client->priv->watch_source = g_io_create_watch (channel, G_IO_IN); |
e4dcdc4a | 234 | g_io_channel_unref (channel); |
cbdf255e DZ |
235 | g_source_set_callback (client->priv->watch_source, (GSourceFunc) monitor_event, client, NULL); |
236 | g_source_attach (client->priv->watch_source, g_main_context_get_thread_default ()); | |
237 | g_source_unref (client->priv->watch_source); | |
238 | } | |
239 | else | |
240 | { | |
241 | client->priv->watch_source = NULL; | |
242 | } | |
bf05675a DZ |
243 | } |
244 | ||
245 | if (G_OBJECT_CLASS (g_udev_client_parent_class)->constructed != NULL) | |
246 | G_OBJECT_CLASS (g_udev_client_parent_class)->constructed (object); | |
247 | } | |
248 | ||
249 | ||
250 | static void | |
251 | g_udev_client_class_init (GUdevClientClass *klass) | |
252 | { | |
253 | GObjectClass *gobject_class = (GObjectClass *) klass; | |
254 | ||
255 | gobject_class->constructed = g_udev_client_constructed; | |
256 | gobject_class->set_property = g_udev_client_set_property; | |
257 | gobject_class->get_property = g_udev_client_get_property; | |
258 | gobject_class->finalize = g_udev_client_finalize; | |
259 | ||
260 | /** | |
261 | * GUdevClient:subsystems: | |
262 | * | |
263 | * The subsystems to listen for uevents on. | |
264 | * | |
265 | * To listen for only a specific DEVTYPE for a given SUBSYSTEM, use | |
266 | * "subsystem/devtype". For example, to only listen for uevents | |
267 | * where SUBSYSTEM is usb and DEVTYPE is usb_interface, use | |
268 | * "usb/usb_interface". | |
269 | * | |
270 | * If this property is %NULL, then no events will be reported. If | |
271 | * it's the empty array, events from all subsystems will be | |
272 | * reported. | |
273 | */ | |
274 | g_object_class_install_property (gobject_class, | |
275 | PROP_SUBSYSTEMS, | |
276 | g_param_spec_boxed ("subsystems", | |
277 | "The subsystems to listen for changes on", | |
278 | "The subsystems to listen for changes on", | |
279 | G_TYPE_STRV, | |
280 | G_PARAM_CONSTRUCT_ONLY | | |
281 | G_PARAM_READWRITE)); | |
282 | ||
283 | /** | |
284 | * GUdevClient::uevent: | |
285 | * @client: The #GUdevClient receiving the event. | |
286 | * @action: The action for the uevent e.g. "add", "remove", "change", "move", etc. | |
287 | * @device: Details about the #GUdevDevice the event is for. | |
288 | * | |
289 | * Emitted when @client receives an uevent. | |
cbdf255e DZ |
290 | * |
291 | * This signal is emitted in the | |
292 | * <link linkend="g-main-context-push-thread-default">thread-default main loop</link> | |
293 | * of the thread that @client was created in. | |
bf05675a DZ |
294 | */ |
295 | signals[UEVENT_SIGNAL] = g_signal_new ("uevent", | |
296 | G_TYPE_FROM_CLASS (klass), | |
297 | G_SIGNAL_RUN_LAST, | |
298 | G_STRUCT_OFFSET (GUdevClientClass, uevent), | |
299 | NULL, | |
300 | NULL, | |
301 | g_udev_marshal_VOID__STRING_OBJECT, | |
302 | G_TYPE_NONE, | |
303 | 2, | |
304 | G_TYPE_STRING, | |
305 | G_UDEV_TYPE_DEVICE); | |
306 | ||
307 | g_type_class_add_private (klass, sizeof (GUdevClientPrivate)); | |
308 | } | |
309 | ||
310 | static void | |
311 | g_udev_client_init (GUdevClient *client) | |
312 | { | |
313 | client->priv = G_TYPE_INSTANCE_GET_PRIVATE (client, | |
314 | G_UDEV_TYPE_CLIENT, | |
315 | GUdevClientPrivate); | |
316 | } | |
317 | ||
318 | /** | |
319 | * g_udev_client_new: | |
daef8ae3 | 320 | * @subsystems: (array zero-terminated=1) (element-type utf8) (transfer none) (allow-none): A %NULL terminated string array of subsystems to listen for uevents on, %NULL to not listen on uevents at all, or an empty array to listen to uevents on all subsystems. See the documentation for the #GUdevClient:subsystems property for details on this parameter. |
bf05675a DZ |
321 | * |
322 | * Constructs a #GUdevClient object that can be used to query | |
323 | * information about devices. Connect to the #GUdevClient::uevent | |
cbdf255e DZ |
324 | * signal to listen for uevents. Note that signals are emitted in the |
325 | * <link linkend="g-main-context-push-thread-default">thread-default main loop</link> | |
326 | * of the thread that you call this constructor from. | |
bf05675a DZ |
327 | * |
328 | * Returns: A new #GUdevClient object. Free with g_object_unref(). | |
329 | */ | |
330 | GUdevClient * | |
331 | g_udev_client_new (const gchar * const *subsystems) | |
332 | { | |
333 | return G_UDEV_CLIENT (g_object_new (G_UDEV_TYPE_CLIENT, "subsystems", subsystems, NULL)); | |
334 | } | |
335 | ||
336 | /** | |
337 | * g_udev_client_query_by_subsystem: | |
338 | * @client: A #GUdevClient. | |
339 | * @subsystem: (allow-none): The subsystem to get devices for or %NULL to get all devices. | |
340 | * | |
341 | * Gets all devices belonging to @subsystem. | |
342 | * | |
daef8ae3 | 343 | * Returns: (element-type GUdevDevice) (transfer full): A list of #GUdevDevice objects. The caller should free the result by using g_object_unref() on each element in the list and then g_list_free() on the list. |
bf05675a DZ |
344 | */ |
345 | GList * | |
346 | g_udev_client_query_by_subsystem (GUdevClient *client, | |
347 | const gchar *subsystem) | |
348 | { | |
349 | struct udev_enumerate *enumerate; | |
350 | struct udev_list_entry *l, *devices; | |
351 | GList *ret; | |
352 | ||
353 | g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL); | |
354 | ||
355 | ret = NULL; | |
356 | ||
357 | /* prepare a device scan */ | |
358 | enumerate = udev_enumerate_new (client->priv->udev); | |
359 | ||
360 | /* filter for subsystem */ | |
361 | if (subsystem != NULL) | |
362 | udev_enumerate_add_match_subsystem (enumerate, subsystem); | |
363 | /* retrieve the list */ | |
364 | udev_enumerate_scan_devices (enumerate); | |
365 | ||
366 | /* add devices to the list */ | |
367 | devices = udev_enumerate_get_list_entry (enumerate); | |
368 | for (l = devices; l != NULL; l = udev_list_entry_get_next (l)) | |
369 | { | |
370 | struct udev_device *udevice; | |
371 | GUdevDevice *device; | |
372 | ||
373 | udevice = udev_device_new_from_syspath (udev_enumerate_get_udev (enumerate), | |
374 | udev_list_entry_get_name (l)); | |
375 | if (udevice == NULL) | |
376 | continue; | |
377 | device = _g_udev_device_new (udevice); | |
378 | udev_device_unref (udevice); | |
379 | ret = g_list_prepend (ret, device); | |
380 | } | |
381 | udev_enumerate_unref (enumerate); | |
382 | ||
383 | ret = g_list_reverse (ret); | |
384 | ||
385 | return ret; | |
386 | } | |
387 | ||
388 | /** | |
389 | * g_udev_client_query_by_device_number: | |
390 | * @client: A #GUdevClient. | |
391 | * @type: A value from the #GUdevDeviceType enumeration. | |
392 | * @number: A device number. | |
393 | * | |
394 | * Looks up a device for a type and device number. | |
395 | * | |
daef8ae3 | 396 | * Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref(). |
bf05675a DZ |
397 | */ |
398 | GUdevDevice * | |
399 | g_udev_client_query_by_device_number (GUdevClient *client, | |
400 | GUdevDeviceType type, | |
401 | GUdevDeviceNumber number) | |
402 | { | |
403 | struct udev_device *udevice; | |
404 | GUdevDevice *device; | |
405 | ||
406 | g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL); | |
407 | ||
408 | device = NULL; | |
409 | udevice = udev_device_new_from_devnum (client->priv->udev, type, number); | |
410 | ||
411 | if (udevice == NULL) | |
412 | goto out; | |
413 | ||
414 | device = _g_udev_device_new (udevice); | |
415 | udev_device_unref (udevice); | |
416 | ||
417 | out: | |
418 | return device; | |
419 | } | |
420 | ||
421 | /** | |
422 | * g_udev_client_query_by_device_file: | |
423 | * @client: A #GUdevClient. | |
424 | * @device_file: A device file. | |
425 | * | |
426 | * Looks up a device for a device file. | |
427 | * | |
daef8ae3 | 428 | * Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref(). |
bf05675a DZ |
429 | */ |
430 | GUdevDevice * | |
431 | g_udev_client_query_by_device_file (GUdevClient *client, | |
432 | const gchar *device_file) | |
433 | { | |
434 | struct stat stat_buf; | |
435 | GUdevDevice *device; | |
436 | ||
437 | g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL); | |
438 | g_return_val_if_fail (device_file != NULL, NULL); | |
439 | ||
440 | device = NULL; | |
441 | ||
442 | if (stat (device_file, &stat_buf) != 0) | |
443 | goto out; | |
444 | ||
445 | if (stat_buf.st_rdev == 0) | |
446 | goto out; | |
447 | ||
448 | if (S_ISBLK (stat_buf.st_mode)) | |
449 | device = g_udev_client_query_by_device_number (client, G_UDEV_DEVICE_TYPE_BLOCK, stat_buf.st_rdev); | |
450 | else if (S_ISCHR (stat_buf.st_mode)) | |
451 | device = g_udev_client_query_by_device_number (client, G_UDEV_DEVICE_TYPE_CHAR, stat_buf.st_rdev); | |
452 | ||
453 | out: | |
454 | return device; | |
455 | } | |
456 | ||
457 | /** | |
458 | * g_udev_client_query_by_sysfs_path: | |
459 | * @client: A #GUdevClient. | |
460 | * @sysfs_path: A sysfs path. | |
461 | * | |
462 | * Looks up a device for a sysfs path. | |
079de714 KS |
463 | * |
464 | * Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref(). | |
bf05675a DZ |
465 | */ |
466 | GUdevDevice * | |
467 | g_udev_client_query_by_sysfs_path (GUdevClient *client, | |
468 | const gchar *sysfs_path) | |
469 | { | |
470 | struct udev_device *udevice; | |
471 | GUdevDevice *device; | |
472 | ||
473 | g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL); | |
474 | g_return_val_if_fail (sysfs_path != NULL, NULL); | |
475 | ||
476 | device = NULL; | |
477 | udevice = udev_device_new_from_syspath (client->priv->udev, sysfs_path); | |
478 | if (udevice == NULL) | |
479 | goto out; | |
480 | ||
481 | device = _g_udev_device_new (udevice); | |
482 | udev_device_unref (udevice); | |
483 | ||
484 | out: | |
485 | return device; | |
486 | } | |
487 | ||
488 | /** | |
489 | * g_udev_client_query_by_subsystem_and_name: | |
490 | * @client: A #GUdevClient. | |
491 | * @subsystem: A subsystem name. | |
492 | * @name: The name of the device. | |
493 | * | |
494 | * Looks up a device for a subsystem and name. | |
495 | * | |
daef8ae3 | 496 | * Returns: (transfer full): A #GUdevDevice object or %NULL if the device was not found. Free with g_object_unref(). |
bf05675a DZ |
497 | */ |
498 | GUdevDevice * | |
499 | g_udev_client_query_by_subsystem_and_name (GUdevClient *client, | |
500 | const gchar *subsystem, | |
501 | const gchar *name) | |
502 | { | |
503 | struct udev_device *udevice; | |
504 | GUdevDevice *device; | |
505 | ||
506 | g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL); | |
507 | g_return_val_if_fail (subsystem != NULL, NULL); | |
508 | g_return_val_if_fail (name != NULL, NULL); | |
509 | ||
510 | device = NULL; | |
511 | udevice = udev_device_new_from_subsystem_sysname (client->priv->udev, subsystem, name); | |
512 | if (udevice == NULL) | |
513 | goto out; | |
514 | ||
515 | device = _g_udev_device_new (udevice); | |
516 | udev_device_unref (udevice); | |
517 | ||
518 | out: | |
519 | return device; | |
520 | } | |
521 | ||
0976fd06 DZ |
522 | struct udev * |
523 | _g_udev_client_get_udev (GUdevClient *client) | |
524 | { | |
525 | g_return_val_if_fail (G_UDEV_IS_CLIENT (client), NULL); | |
526 | return client->priv->udev; | |
527 | } |