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