]> git.ozlabs.org Git - petitboot/blob - discover/udev.c
7a99eeaf9d772433c4e7f507a4be2f5e7f5dfa06
[petitboot] / discover / udev.c
1
2 #if defined(HAVE_CONFIG_H)
3 #include "config.h"
4 #endif
5
6 #include <assert.h>
7 #include <errno.h>
8 #include <libudev.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <sys/socket.h>
14 #include <sys/types.h>
15 #include <sys/un.h>
16
17 #include <log/log.h>
18 #include <types/types.h>
19 #include <talloc/talloc.h>
20 #include <waiter/waiter.h>
21 #include <system/system.h>
22
23 #include "event.h"
24 #include "udev.h"
25 #include "pb-discover.h"
26 #include "device-handler.h"
27 #include "cdrom.h"
28
29 struct pb_udev {
30         struct udev *udev;
31         struct udev_monitor *monitor;
32         struct device_handler *handler;
33 };
34
35 static int udev_destructor(void *p)
36 {
37         struct pb_udev *udev = p;
38
39         udev_monitor_unref(udev->monitor);
40         udev->monitor = NULL;
41
42         udev_unref(udev->udev);
43         udev->udev = NULL;
44
45         return 0;
46 }
47
48 static void udev_setup_device_params(struct udev_device *udev,
49                 struct discover_device *dev)
50 {
51         struct udev_list_entry *list, *entry;
52
53         list = udev_device_get_properties_list_entry(udev);
54         if (!list)
55                 return;
56
57         udev_list_entry_foreach(entry, list)
58                 discover_device_set_param(dev,
59                                 udev_list_entry_get_name(entry),
60                                 udev_list_entry_get_value(entry));
61 }
62
63 static int udev_handle_block_add(struct pb_udev *udev, struct udev_device *dev,
64                 const char *name)
65 {
66         struct discover_device *ddev;
67         const char *typestr;
68         const char *uuid;
69         const char *path;
70         const char *node;
71         const char *prop;
72         const char *type;
73         bool cdrom;
74
75         typestr = udev_device_get_devtype(dev);
76         if (!typestr) {
77                 pb_debug("udev_device_get_devtype failed\n");
78                 return -1;
79         }
80
81         if (!(!strcmp(typestr, "disk") || !strcmp(typestr, "partition"))) {
82                 pb_debug("SKIP %s: invalid type %s\n", name, typestr);
83                 return 0;
84         }
85
86         node = udev_device_get_devnode(dev);
87         path = udev_device_get_devpath(dev);
88         if (path && (strstr(path, "virtual/block/loop")
89                         || strstr(path, "virtual/block/ram"))) {
90                 pb_debug("SKIP: %s: ignored (path=%s)\n", name, path);
91                 return 0;
92         }
93
94         cdrom = node && !!udev_device_get_property_value(dev, "ID_CDROM");
95         if (cdrom) {
96                 /* CDROMs require a little initialisation, to get
97                  * petitboot-compatible tray behaviour */
98                 cdrom_init(node);
99                 if (!cdrom_media_present(node)) {
100                         pb_debug("SKIP: %s: no media present\n", name);
101                         return 0;
102                 }
103         }
104
105         type = udev_device_get_property_value(dev, "ID_FS_TYPE");
106         if (!type) {
107                 pb_debug("SKIP: %s: no ID_FS_TYPE property\n", name);
108                 return 0;
109         }
110
111         /* We may see multipath devices; they'll have the same uuid as an
112          * existing device, so only parse the first. */
113         uuid = udev_device_get_property_value(dev, "ID_FS_UUID");
114         if (uuid) {
115                 ddev = device_lookup_by_uuid(udev->handler, uuid);
116                 if (ddev) {
117                         pb_log("SKIP: %s UUID [%s] already present (as %s)\n",
118                                         name, uuid, ddev->device->id);
119                         return -1;
120                 }
121         }
122
123         ddev = discover_device_create(udev->handler, name);
124
125         ddev->device_path = talloc_strdup(ddev, node);
126
127         if (uuid)
128                 ddev->uuid = talloc_strdup(ddev, uuid);
129         prop = udev_device_get_property_value(dev, "ID_FS_LABEL");
130         if (prop)
131                 ddev->label = talloc_strdup(ddev, prop);
132         ddev->device->type = cdrom ? DEVICE_TYPE_OPTICAL : DEVICE_TYPE_DISK;
133
134         udev_setup_device_params(dev, ddev);
135
136         device_handler_discover(udev->handler, ddev);
137
138         return 0;
139 }
140
141 static int udev_handle_dev_add(struct pb_udev *udev, struct udev_device *dev)
142 {
143         const char *subsys;
144         const char *name;
145
146         name = udev_device_get_sysname(dev);
147         if (!name) {
148                 pb_debug("udev_device_get_sysname failed\n");
149                 return -1;
150         }
151
152         subsys = udev_device_get_subsystem(dev);
153         if (!subsys) {
154                 pb_debug("udev_device_get_subsystem failed\n");
155                 return -1;
156         }
157
158         if (device_lookup_by_id(udev->handler, name)) {
159                 pb_debug("device %s is already present?\n", name);
160                 return -1;
161         }
162
163         if (!strcmp(subsys, "block")) {
164                 return udev_handle_block_add(udev, dev, name);
165         }
166
167         pb_debug("SKIP %s: unknown subsystem %s\n", name, subsys);
168         return -1;
169 }
170
171
172 static int udev_handle_dev_remove(struct pb_udev *udev, struct udev_device *dev)
173 {
174         struct discover_device *ddev;
175         const char *name;
176
177         name = udev_device_get_sysname(dev);
178         if (!name) {
179                 pb_debug("udev_device_get_sysname failed\n");
180                 return -1;
181         }
182
183         ddev = device_lookup_by_id(udev->handler, name);
184         if (!ddev)
185                 return 0;
186
187         device_handler_remove(udev->handler, ddev);
188
189         return 0;
190 }
191
192 /* returns true if further event processing should stop (eg., we've
193  * ejected the cdrom)
194  */
195 static bool udev_handle_cdrom_events(struct pb_udev *udev,
196                 struct udev_device *dev, struct discover_device *ddev)
197 {
198         const char *node;
199
200         node = udev_device_get_devnode(dev);
201
202         /* handle CDROM eject requests */
203         if (udev_device_get_property_value(dev, "DISK_EJECT_REQUEST")) {
204                 bool eject = false;
205
206                 pb_debug("udev: eject request\n");
207
208                 /* If the device is mounted, cdrom_id's own eject request may
209                  * have failed. So, we'll need to do our own here.
210                  */
211                 if (ddev) {
212                         eject = ddev->mounted;
213                         udev_handle_dev_remove(udev, dev);
214                         return false;
215                 }
216
217                 if (eject)
218                         cdrom_eject(node);
219
220                 return true;
221         }
222
223         if (udev_device_get_property_value(dev, "DISK_MEDIA_CHANGE")) {
224                 if (cdrom_media_present(node))
225                         udev_handle_dev_add(udev, dev);
226                 else
227                         udev_handle_dev_remove(udev, dev);
228                 return true;
229         }
230
231         return false;
232 }
233
234 static int udev_handle_dev_change(struct pb_udev *udev, struct udev_device *dev)
235 {
236         struct discover_device *ddev;
237         const char *name;
238         int rc = 0;
239
240         name = udev_device_get_sysname(dev);
241
242         ddev = device_lookup_by_id(udev->handler, name);
243
244         /* if this is a CDROM device, process eject & media change requests;
245          * these may stop further processing */
246         if (!udev_device_get_property_value(dev, "ID_CDROM")) {
247                 if (udev_handle_cdrom_events(udev, dev, ddev))
248                         return 0;
249         }
250
251         /* if this is a new device, treat it as an add */
252         if (!ddev)
253                 rc = udev_handle_dev_add(udev, dev);
254
255         return rc;
256 }
257
258 static int udev_handle_dev_action(struct udev_device *dev, const char *action)
259 {
260         struct pb_udev *udev = udev_get_userdata(udev_device_get_udev(dev));
261
262 #ifdef DEBUG
263         {
264                 struct udev_list_entry *list;
265                 const char *name;
266
267                 list = udev_device_get_properties_list_entry(dev);
268                 name = udev_device_get_sysname(dev);
269
270                 pb_debug("%s: action %s, device %s\n", __func__, action, name);
271                 pb_debug("%s properties:\n", __func__);
272
273                 for (; list; list = udev_list_entry_get_next(list))
274                         pb_log("\t%-20s: %s\n", udev_list_entry_get_name(list),
275                                         udev_list_entry_get_value(list));
276         } while (0);
277 #endif
278
279         if (!strcmp(action, "add"))
280                 return udev_handle_dev_add(udev, dev);
281
282         else if (!strcmp(action, "remove"))
283                 return udev_handle_dev_remove(udev, dev);
284
285         else if (!strcmp(action, "change"))
286                 return udev_handle_dev_change(udev, dev);
287
288         return 0;
289 }
290
291 static int udev_enumerate(struct udev *udev)
292 {
293         int result;
294         struct udev_list_entry *list, *entry;
295         struct udev_enumerate *enumerate;
296
297         enumerate = udev_enumerate_new(udev);
298
299         if (!enumerate) {
300                 pb_log("udev_enumerate_new failed\n");
301                 return -1;
302         }
303
304         result = udev_enumerate_add_match_subsystem(enumerate, "block");
305         if (result) {
306                 pb_log("udev_enumerate_add_match_subsystem failed\n");
307                 goto fail;
308         }
309
310         result = udev_enumerate_add_match_is_initialized(enumerate);
311         if (result) {
312                 pb_log("udev_enumerate_add_match_is_initialised failed\n");
313                 goto fail;
314         }
315
316         udev_enumerate_scan_devices(enumerate);
317
318         list = udev_enumerate_get_list_entry(enumerate);
319
320         if (!list) {
321                 pb_log("udev_enumerate_get_list_entry failed\n");
322                 goto fail;
323         }
324
325         udev_list_entry_foreach(entry, list) {
326                 const char *syspath;
327                 struct udev_device *dev;
328
329                 syspath = udev_list_entry_get_name(entry);
330                 dev = udev_device_new_from_syspath(udev, syspath);
331
332                 udev_handle_dev_action(dev, "add");
333
334                 udev_device_unref(dev);
335         }
336
337         udev_enumerate_unref(enumerate);
338         return 0;
339
340 fail:
341         udev_enumerate_unref(enumerate);
342         return -1;
343 }
344
345 static int udev_setup_monitor(struct udev *udev, struct udev_monitor **monitor)
346 {
347         int result;
348         struct udev_monitor *m;
349
350         *monitor = NULL;
351         m = udev_monitor_new_from_netlink(udev, "udev");
352
353         if (!m) {
354                 pb_log("udev_monitor_new_from_netlink failed\n");
355                 goto out_err;
356         }
357
358         result = udev_monitor_filter_add_match_subsystem_devtype(m, "block",
359                 NULL);
360
361         if (result) {
362                 pb_log("udev_monitor_filter_add_match_subsystem_devtype failed\n");
363                 goto out_err;
364         }
365
366         result = udev_monitor_enable_receiving(m);
367
368         if (result) {
369                 pb_log("udev_monitor_enable_receiving failed\n");
370                 goto out_err;
371         }
372
373         *monitor = m;
374         return 0;
375
376 out_err:
377         udev_monitor_unref(m);
378         return -1;
379 }
380
381 /*
382  * udev_process - waiter callback for monitor netlink.
383  */
384
385 static int udev_process(void *arg)
386 {
387         struct udev_monitor *monitor = arg;
388         struct udev_device *dev;
389         const char *action;
390         int result;
391
392         dev = udev_monitor_receive_device(monitor);
393
394         if (!dev) {
395                 pb_log("udev_monitor_receive_device failed\n");
396                 return -1;
397         }
398
399         action = udev_device_get_action(dev);
400
401         if (!action) {
402                 pb_log("udev_device_get_action failed\n");
403                 goto fail;
404         }
405
406         result = udev_handle_dev_action(dev, action);
407
408         udev_device_unref(dev);
409         return result;
410
411 fail:
412         udev_device_unref(dev);
413         return -1;
414 }
415
416 static void udev_log_fn(struct udev __attribute__((unused)) *udev,
417         int __attribute__((unused)) priority, const char *file, int line,
418         const char *fn, const char *format, va_list args)
419 {
420       pb_log("libudev: %s %s:%d: ", fn, file, line);
421       vfprintf(pb_log_get_stream(), format, args);
422 }
423
424 struct pb_udev *udev_init(struct waitset *waitset,
425         struct device_handler *handler)
426 {
427         int result;
428         struct pb_udev *udev = talloc(NULL, struct pb_udev);
429
430         talloc_set_destructor(udev, udev_destructor);
431         udev->handler = handler;
432
433         udev->udev = udev_new();
434
435         if (!udev->udev) {
436                 pb_log("udev_new failed\n");
437                 goto fail_new;
438         }
439
440         udev_set_userdata(udev->udev, udev);
441
442         udev_set_log_fn(udev->udev, udev_log_fn);
443
444         result = udev_setup_monitor(udev->udev, &udev->monitor);
445         if (result)
446                 goto fail_monitor;
447
448         result = udev_enumerate(udev->udev);
449         if (result)
450                 goto fail_enumerate;
451
452         waiter_register_io(waitset, udev_monitor_get_fd(udev->monitor), WAIT_IN,
453                 udev_process, udev->monitor);
454
455         pb_debug("%s: waiting on udev\n", __func__);
456
457         return udev;
458
459 fail_monitor:
460         udev_monitor_unref(udev->monitor);
461 fail_enumerate:
462         udev_unref(udev->udev);
463 fail_new:
464         talloc_free(udev);
465         return NULL;
466 }
467
468 void udev_destroy(struct pb_udev *udev)
469 {
470         talloc_free(udev);
471 }