]> git.ozlabs.org Git - petitboot/blob - discover/udev.c
lib/pb-protocol: fix deserialise of boot option priorities
[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 /* We set a default monitor buffer size, as we may not process monitor
30  * events while performing device discvoery. systemd uses a 128M buffer, so
31  * we'll do the same here */
32 static const int monitor_bufsize = 128 * 1024 * 1024;
33
34 struct pb_udev {
35         struct udev *udev;
36         struct udev_monitor *monitor;
37         struct device_handler *handler;
38 };
39
40 static int udev_destructor(void *p)
41 {
42         struct pb_udev *udev = p;
43
44         udev_monitor_unref(udev->monitor);
45         udev->monitor = NULL;
46
47         udev_unref(udev->udev);
48         udev->udev = NULL;
49
50         return 0;
51 }
52
53 static void udev_setup_device_params(struct udev_device *udev,
54                 struct discover_device *dev)
55 {
56         struct udev_list_entry *list, *entry;
57
58         list = udev_device_get_properties_list_entry(udev);
59         if (!list)
60                 return;
61
62         udev_list_entry_foreach(entry, list)
63                 discover_device_set_param(dev,
64                                 udev_list_entry_get_name(entry),
65                                 udev_list_entry_get_value(entry));
66 }
67
68 static int udev_handle_block_add(struct pb_udev *udev, struct udev_device *dev,
69                 const char *name)
70 {
71         struct discover_device *ddev;
72         const char *typestr;
73         const char *uuid;
74         const char *path;
75         const char *node;
76         const char *prop;
77         const char *type;
78         bool cdrom;
79
80         typestr = udev_device_get_devtype(dev);
81         if (!typestr) {
82                 pb_debug("udev_device_get_devtype failed\n");
83                 return -1;
84         }
85
86         if (!(!strcmp(typestr, "disk") || !strcmp(typestr, "partition"))) {
87                 pb_log("SKIP %s: invalid type %s\n", name, typestr);
88                 return 0;
89         }
90
91         node = udev_device_get_devnode(dev);
92         path = udev_device_get_devpath(dev);
93         if (path && (strstr(path, "virtual/block/loop")
94                         || strstr(path, "virtual/block/ram"))) {
95                 pb_log("SKIP: %s: ignored (path=%s)\n", name, path);
96                 return 0;
97         }
98
99         cdrom = node && !!udev_device_get_property_value(dev, "ID_CDROM");
100         if (cdrom) {
101                 /* CDROMs require a little initialisation, to get
102                  * petitboot-compatible tray behaviour */
103                 cdrom_init(node);
104                 if (!cdrom_media_present(node)) {
105                         pb_log("SKIP: %s: no media present\n", name);
106                         return 0;
107                 }
108         }
109
110         type = udev_device_get_property_value(dev, "ID_FS_TYPE");
111         if (!type) {
112                 pb_log("SKIP: %s: no ID_FS_TYPE property\n", name);
113                 return 0;
114         }
115
116         /* We may see multipath devices; they'll have the same uuid as an
117          * existing device, so only parse the first. */
118         uuid = udev_device_get_property_value(dev, "ID_FS_UUID");
119         if (uuid) {
120                 ddev = device_lookup_by_uuid(udev->handler, uuid);
121                 if (ddev) {
122                         pb_log("SKIP: %s UUID [%s] already present (as %s)\n",
123                                         name, uuid, ddev->device->id);
124                         return 0;
125                 }
126         }
127
128         ddev = discover_device_create(udev->handler, name);
129
130         ddev->device_path = talloc_strdup(ddev, node);
131
132         if (uuid)
133                 ddev->uuid = talloc_strdup(ddev, uuid);
134         prop = udev_device_get_property_value(dev, "ID_FS_LABEL");
135         if (prop)
136                 ddev->label = talloc_strdup(ddev, prop);
137         ddev->device->type = cdrom ? DEVICE_TYPE_OPTICAL : DEVICE_TYPE_DISK;
138
139         udev_setup_device_params(dev, ddev);
140
141         device_handler_discover(udev->handler, ddev);
142
143         return 0;
144 }
145
146 static int udev_handle_dev_add(struct pb_udev *udev, struct udev_device *dev)
147 {
148         const char *subsys;
149         const char *name;
150
151         name = udev_device_get_sysname(dev);
152         if (!name) {
153                 pb_debug("udev_device_get_sysname failed\n");
154                 return -1;
155         }
156
157         subsys = udev_device_get_subsystem(dev);
158         if (!subsys) {
159                 pb_debug("udev_device_get_subsystem failed\n");
160                 return -1;
161         }
162
163         if (device_lookup_by_id(udev->handler, name)) {
164                 pb_debug("device %s is already present?\n", name);
165                 return -1;
166         }
167
168         if (!strcmp(subsys, "block")) {
169                 return udev_handle_block_add(udev, dev, name);
170         }
171
172         pb_log("SKIP %s: unknown subsystem %s\n", name, subsys);
173         return -1;
174 }
175
176
177 static int udev_handle_dev_remove(struct pb_udev *udev, struct udev_device *dev)
178 {
179         struct discover_device *ddev;
180         const char *name;
181
182         name = udev_device_get_sysname(dev);
183         if (!name) {
184                 pb_debug("udev_device_get_sysname failed\n");
185                 return -1;
186         }
187
188         ddev = device_lookup_by_id(udev->handler, name);
189         if (!ddev)
190                 return 0;
191
192         device_handler_remove(udev->handler, ddev);
193
194         return 0;
195 }
196
197 /* returns true if further event processing should stop (eg., we've
198  * ejected the cdrom)
199  */
200 static bool udev_handle_cdrom_events(struct pb_udev *udev,
201                 struct udev_device *dev, struct discover_device *ddev)
202 {
203         const char *node;
204
205         node = udev_device_get_devnode(dev);
206
207         /* handle CDROM eject requests */
208         if (udev_device_get_property_value(dev, "DISK_EJECT_REQUEST")) {
209                 bool eject = false;
210
211                 pb_debug("udev: eject request\n");
212
213                 /* If the device is mounted, cdrom_id's own eject request may
214                  * have failed. So, we'll need to do our own here.
215                  */
216                 if (ddev) {
217                         eject = ddev->mounted;
218                         udev_handle_dev_remove(udev, dev);
219                         return false;
220                 }
221
222                 if (eject)
223                         cdrom_eject(node);
224
225                 return true;
226         }
227
228         if (udev_device_get_property_value(dev, "DISK_MEDIA_CHANGE")) {
229                 if (cdrom_media_present(node))
230                         udev_handle_dev_add(udev, dev);
231                 else
232                         udev_handle_dev_remove(udev, dev);
233                 return true;
234         }
235
236         return false;
237 }
238
239 static int udev_handle_dev_change(struct pb_udev *udev, struct udev_device *dev)
240 {
241         struct discover_device *ddev;
242         const char *name;
243         int rc = 0;
244
245         name = udev_device_get_sysname(dev);
246
247         ddev = device_lookup_by_id(udev->handler, name);
248
249         /* if this is a CDROM device, process eject & media change requests;
250          * these may stop further processing */
251         if (!udev_device_get_property_value(dev, "ID_CDROM")) {
252                 if (udev_handle_cdrom_events(udev, dev, ddev))
253                         return 0;
254         }
255
256         /* if this is a new device, treat it as an add */
257         if (!ddev)
258                 rc = udev_handle_dev_add(udev, dev);
259
260         return rc;
261 }
262
263 static int udev_handle_dev_action(struct udev_device *dev, const char *action)
264 {
265         struct pb_udev *udev = udev_get_userdata(udev_device_get_udev(dev));
266         struct udev_list_entry *list;
267         const char *name;
268
269         list = udev_device_get_properties_list_entry(dev);
270         name = udev_device_get_sysname(dev);
271
272         pb_debug("udev: action %s, device %s\n", action, name);
273         pb_debug("udev: properties:\n");
274
275         for (; list; list = udev_list_entry_get_next(list))
276                 pb_debug("\t%-20s: %s\n", udev_list_entry_get_name(list),
277                                 udev_list_entry_get_value(list));
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_set_receive_buffer_size(m, monitor_bufsize);
359         if (result) {
360                 pb_log("udev_monitor_set_rx_bufsize(%d) failed\n",
361                         monitor_bufsize);
362         }
363
364         result = udev_monitor_filter_add_match_subsystem_devtype(m, "block",
365                 NULL);
366
367         if (result) {
368                 pb_log("udev_monitor_filter_add_match_subsystem_devtype failed\n");
369                 goto out_err;
370         }
371
372         result = udev_monitor_enable_receiving(m);
373
374         if (result) {
375                 pb_log("udev_monitor_enable_receiving failed\n");
376                 goto out_err;
377         }
378
379         *monitor = m;
380         return 0;
381
382 out_err:
383         udev_monitor_unref(m);
384         return -1;
385 }
386
387 /*
388  * udev_process - waiter callback for monitor netlink.
389  */
390
391 static int udev_process(void *arg)
392 {
393         struct udev_monitor *monitor = arg;
394         struct udev_device *dev;
395         const char *action;
396
397         dev = udev_monitor_receive_device(monitor);
398         if (!dev) {
399                 pb_log("udev_monitor_receive_device failed\n");
400                 return -1;
401         }
402
403         action = udev_device_get_action(dev);
404
405         if (!action) {
406                 pb_log("udev_device_get_action failed\n");
407         } else {
408                 udev_handle_dev_action(dev, action);
409         }
410
411         udev_device_unref(dev);
412         return 0;
413 }
414
415 static void udev_log_fn(struct udev __attribute__((unused)) *udev,
416         int __attribute__((unused)) priority, const char *file, int line,
417         const char *fn, const char *format, va_list args)
418 {
419       pb_log("libudev: %s %s:%d: ", fn, file, line);
420       vfprintf(pb_log_get_stream(), format, args);
421 }
422
423 struct pb_udev *udev_init(struct device_handler *handler,
424                 struct waitset *waitset)
425 {
426         struct pb_udev *udev;
427         int result;
428
429         udev = talloc(handler, struct pb_udev);
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_reinit(struct pb_udev *udev)
469 {
470         pb_log("udev: reinit requested, starting enumeration\n");
471         udev_enumerate(udev->udev);
472 }