discover: fix return value for discover_device_get_param
[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 static int udev_handle_dev_change(struct pb_udev *udev, struct udev_device *dev)
187 {
188         struct discover_device *ddev;
189         const char *name, *node;
190
191         name = udev_device_get_sysname(dev);
192         node = udev_device_get_devnode(dev);
193
194         /* we're only interested in CDROM change events at present */
195         if (!udev_device_get_property_value(dev, "ID_CDROM"))
196                 return 0;
197
198         /* handle CDROM eject requests */
199         if (udev_device_get_property_value(dev, "DISK_EJECT_REQUEST")) {
200                 bool eject = false;
201
202                 pb_debug("udev: eject request\n");
203
204                 /* If the device is mounted, cdrom_id's own eject request may
205                  * have failed. So, we'll need to do our own here.
206                  */
207                 ddev = device_lookup_by_id(udev->handler, name);
208                 if (ddev) {
209                         eject = ddev->mounted;
210                         udev_handle_dev_remove(udev, dev);
211                 }
212
213                 if (eject)
214                         cdrom_eject(node);
215
216                 return 0;
217         }
218
219         if (udev_device_get_property_value(dev, "DISK_MEDIA_CHANGE")) {
220                 if (cdrom_media_present(node))
221                         return udev_handle_dev_add(udev, dev);
222                 else
223                         return udev_handle_dev_remove(udev, dev);
224         }
225
226         return 0;
227 }
228
229 static int udev_handle_dev_action(struct udev_device *dev, const char *action)
230 {
231         struct pb_udev *udev = udev_get_userdata(udev_device_get_udev(dev));
232
233 #ifdef DEBUG
234         {
235                 struct udev_list_entry *list;
236                 const char *name;
237
238                 list = udev_device_get_properties_list_entry(dev);
239                 name = udev_device_get_sysname(dev);
240
241                 pb_debug("%s: action %s, device %s\n", __func__, action, name);
242                 pb_debug("%s properties:\n", __func__);
243
244                 for (; list; list = udev_list_entry_get_next(list))
245                         pb_log("\t%-20s: %s\n", udev_list_entry_get_name(list),
246                                         udev_list_entry_get_value(list));
247         } while (0);
248 #endif
249
250         if (!strcmp(action, "add"))
251                 return udev_handle_dev_add(udev, dev);
252
253         else if (!strcmp(action, "remove"))
254                 return udev_handle_dev_remove(udev, dev);
255
256         else if (!strcmp(action, "change"))
257                 return udev_handle_dev_change(udev, dev);
258
259         return 0;
260 }
261
262 static int udev_enumerate(struct udev *udev)
263 {
264         int result;
265         struct udev_list_entry *list, *entry;
266         struct udev_enumerate *enumerate;
267
268         enumerate = udev_enumerate_new(udev);
269
270         if (!enumerate) {
271                 pb_log("udev_enumerate_new failed\n");
272                 return -1;
273         }
274
275         result = udev_enumerate_add_match_subsystem(enumerate, "block");
276
277         if (result) {
278                 pb_log("udev_enumerate_add_match_subsystem failed\n");
279                 goto fail;
280         }
281
282         udev_enumerate_scan_devices(enumerate);
283
284         list = udev_enumerate_get_list_entry(enumerate);
285
286         if (!list) {
287                 pb_log("udev_enumerate_get_list_entry failed\n");
288                 goto fail;
289         }
290
291         udev_list_entry_foreach(entry, list) {
292                 const char *syspath;
293                 struct udev_device *dev;
294
295                 syspath = udev_list_entry_get_name(entry);
296                 dev = udev_device_new_from_syspath(udev, syspath);
297
298                 udev_handle_dev_action(dev, "add");
299
300                 udev_device_unref(dev);
301         }
302
303         udev_enumerate_unref(enumerate);
304         return 0;
305
306 fail:
307         udev_enumerate_unref(enumerate);
308         return -1;
309 }
310
311 static int udev_setup_monitor(struct udev *udev, struct udev_monitor **monitor)
312 {
313         int result;
314         struct udev_monitor *m;
315
316         *monitor = NULL;
317         m = udev_monitor_new_from_netlink(udev, "udev");
318
319         if (!m) {
320                 pb_log("udev_monitor_new_from_netlink failed\n");
321                 goto out_err;
322         }
323
324         result = udev_monitor_filter_add_match_subsystem_devtype(m, "block",
325                 NULL);
326
327         if (result) {
328                 pb_log("udev_monitor_filter_add_match_subsystem_devtype failed\n");
329                 goto out_err;
330         }
331
332         result = udev_monitor_enable_receiving(m);
333
334         if (result) {
335                 pb_log("udev_monitor_enable_receiving failed\n");
336                 goto out_err;
337         }
338
339         *monitor = m;
340         return 0;
341
342 out_err:
343         udev_monitor_unref(m);
344         return -1;
345 }
346
347 /*
348  * udev_process - waiter callback for monitor netlink.
349  */
350
351 static int udev_process(void *arg)
352 {
353         struct udev_monitor *monitor = arg;
354         struct udev_device *dev;
355         const char *action;
356         int result;
357
358         dev = udev_monitor_receive_device(monitor);
359
360         if (!dev) {
361                 pb_log("udev_monitor_receive_device failed\n");
362                 return -1;
363         }
364
365         action = udev_device_get_action(dev);
366
367         if (!action) {
368                 pb_log("udev_device_get_action failed\n");
369                 goto fail;
370         }
371
372         result = udev_handle_dev_action(dev, action);
373
374         udev_device_unref(dev);
375         return result;
376
377 fail:
378         udev_device_unref(dev);
379         return -1;
380 }
381
382 static void udev_log_fn(struct udev __attribute__((unused)) *udev,
383         int __attribute__((unused)) priority, const char *file, int line,
384         const char *fn, const char *format, va_list args)
385 {
386       pb_log("libudev: %s %s:%d: ", fn, file, line);
387       vfprintf(pb_log_get_stream(), format, args);
388 }
389
390 struct pb_udev *udev_init(struct waitset *waitset,
391         struct device_handler *handler)
392 {
393         int result;
394         struct pb_udev *udev = talloc(NULL, struct pb_udev);
395
396         talloc_set_destructor(udev, udev_destructor);
397         udev->handler = handler;
398
399         udev->udev = udev_new();
400
401         if (!udev->udev) {
402                 pb_log("udev_new failed\n");
403                 goto fail_new;
404         }
405
406         udev_set_userdata(udev->udev, udev);
407
408         udev_set_log_fn(udev->udev, udev_log_fn);
409
410         result = udev_enumerate(udev->udev);
411
412         if (result)
413                 goto fail_enumerate;
414
415         result = udev_setup_monitor(udev->udev, &udev->monitor);
416
417         if (result)
418                 goto fail_monitor;
419
420         waiter_register_io(waitset, udev_monitor_get_fd(udev->monitor), WAIT_IN,
421                 udev_process, udev->monitor);
422
423         pb_debug("%s: waiting on udev\n", __func__);
424
425         return udev;
426
427 fail_monitor:
428 fail_enumerate:
429         udev_unref(udev->udev);
430 fail_new:
431         talloc_free(udev);
432         return NULL;
433 }
434
435 void udev_destroy(struct pb_udev *udev)
436 {
437         talloc_free(udev);
438 }