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