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