discover: move device sources to the device handler
[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
267 #ifdef DEBUG
268         {
269                 struct udev_list_entry *list;
270                 const char *name;
271
272                 list = udev_device_get_properties_list_entry(dev);
273                 name = udev_device_get_sysname(dev);
274
275                 pb_debug("%s: action %s, device %s\n", __func__, action, name);
276                 pb_debug("%s properties:\n", __func__);
277
278                 for (; list; list = udev_list_entry_get_next(list))
279                         pb_log("\t%-20s: %s\n", udev_list_entry_get_name(list),
280                                         udev_list_entry_get_value(list));
281         } while (0);
282 #endif
283
284         if (!strcmp(action, "add"))
285                 return udev_handle_dev_add(udev, dev);
286
287         else if (!strcmp(action, "remove"))
288                 return udev_handle_dev_remove(udev, dev);
289
290         else if (!strcmp(action, "change"))
291                 return udev_handle_dev_change(udev, dev);
292
293         return 0;
294 }
295
296 static int udev_enumerate(struct udev *udev)
297 {
298         int result;
299         struct udev_list_entry *list, *entry;
300         struct udev_enumerate *enumerate;
301
302         enumerate = udev_enumerate_new(udev);
303
304         if (!enumerate) {
305                 pb_log("udev_enumerate_new failed\n");
306                 return -1;
307         }
308
309         result = udev_enumerate_add_match_subsystem(enumerate, "block");
310         if (result) {
311                 pb_log("udev_enumerate_add_match_subsystem failed\n");
312                 goto fail;
313         }
314
315         result = udev_enumerate_add_match_is_initialized(enumerate);
316         if (result) {
317                 pb_log("udev_enumerate_add_match_is_initialised failed\n");
318                 goto fail;
319         }
320
321         udev_enumerate_scan_devices(enumerate);
322
323         list = udev_enumerate_get_list_entry(enumerate);
324
325         if (!list) {
326                 pb_log("udev_enumerate_get_list_entry failed\n");
327                 goto fail;
328         }
329
330         udev_list_entry_foreach(entry, list) {
331                 const char *syspath;
332                 struct udev_device *dev;
333
334                 syspath = udev_list_entry_get_name(entry);
335                 dev = udev_device_new_from_syspath(udev, syspath);
336
337                 udev_handle_dev_action(dev, "add");
338
339                 udev_device_unref(dev);
340         }
341
342         udev_enumerate_unref(enumerate);
343         return 0;
344
345 fail:
346         udev_enumerate_unref(enumerate);
347         return -1;
348 }
349
350 static int udev_setup_monitor(struct udev *udev, struct udev_monitor **monitor)
351 {
352         int result;
353         struct udev_monitor *m;
354
355         *monitor = NULL;
356         m = udev_monitor_new_from_netlink(udev, "udev");
357
358         if (!m) {
359                 pb_log("udev_monitor_new_from_netlink failed\n");
360                 goto out_err;
361         }
362
363         result = udev_monitor_set_receive_buffer_size(m, monitor_bufsize);
364         if (result) {
365                 pb_log("udev_monitor_set_rx_bufsize(%d) failed\n",
366                         monitor_bufsize);
367         }
368
369         result = udev_monitor_filter_add_match_subsystem_devtype(m, "block",
370                 NULL);
371
372         if (result) {
373                 pb_log("udev_monitor_filter_add_match_subsystem_devtype failed\n");
374                 goto out_err;
375         }
376
377         result = udev_monitor_enable_receiving(m);
378
379         if (result) {
380                 pb_log("udev_monitor_enable_receiving failed\n");
381                 goto out_err;
382         }
383
384         *monitor = m;
385         return 0;
386
387 out_err:
388         udev_monitor_unref(m);
389         return -1;
390 }
391
392 /*
393  * udev_process - waiter callback for monitor netlink.
394  */
395
396 static int udev_process(void *arg)
397 {
398         struct udev_monitor *monitor = arg;
399         struct udev_device *dev;
400         const char *action;
401
402         dev = udev_monitor_receive_device(monitor);
403         if (!dev) {
404                 pb_log("udev_monitor_receive_device failed\n");
405                 return -1;
406         }
407
408         action = udev_device_get_action(dev);
409
410         if (!action) {
411                 pb_log("udev_device_get_action failed\n");
412         } else {
413                 udev_handle_dev_action(dev, action);
414         }
415
416         udev_device_unref(dev);
417         return 0;
418 }
419
420 static void udev_log_fn(struct udev __attribute__((unused)) *udev,
421         int __attribute__((unused)) priority, const char *file, int line,
422         const char *fn, const char *format, va_list args)
423 {
424       pb_log("libudev: %s %s:%d: ", fn, file, line);
425       vfprintf(pb_log_get_stream(), format, args);
426 }
427
428 struct pb_udev *udev_init(struct device_handler *handler,
429                 struct waitset *waitset)
430 {
431         struct pb_udev *udev;
432         int result;
433
434         udev = talloc(handler, struct pb_udev);
435         talloc_set_destructor(udev, udev_destructor);
436         udev->handler = handler;
437
438         udev->udev = udev_new();
439
440         if (!udev->udev) {
441                 pb_log("udev_new failed\n");
442                 goto fail_new;
443         }
444
445         udev_set_userdata(udev->udev, udev);
446
447         udev_set_log_fn(udev->udev, udev_log_fn);
448
449         result = udev_setup_monitor(udev->udev, &udev->monitor);
450         if (result)
451                 goto fail_monitor;
452
453         result = udev_enumerate(udev->udev);
454         if (result)
455                 goto fail_enumerate;
456
457         waiter_register_io(waitset, udev_monitor_get_fd(udev->monitor), WAIT_IN,
458                 udev_process, udev->monitor);
459
460         pb_debug("%s: waiting on udev\n", __func__);
461
462         return udev;
463
464 fail_monitor:
465         udev_monitor_unref(udev->monitor);
466 fail_enumerate:
467         udev_unref(udev->udev);
468 fail_new:
469         talloc_free(udev);
470         return NULL;
471 }