discover: Finalise boot options during commit
[petitboot] / discover / device-handler.c
1
2 #include <assert.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <sys/stat.h>
8 #include <sys/wait.h>
9
10 #include <talloc/talloc.h>
11 #include <list/list.h>
12 #include <log/log.h>
13 #include <types/types.h>
14 #include <system/system.h>
15 #include <url/url.h>
16
17 #include "device-handler.h"
18 #include "discover-server.h"
19 #include "event.h"
20 #include "parser.h"
21 #include "resource.h"
22 #include "udev.h"
23 #include "paths.h"
24 #include "boot.h"
25
26 struct device_handler {
27         struct discover_server  *server;
28         int                     dry_run;
29
30         struct discover_device  **devices;
31         unsigned int            n_devices;
32 };
33
34 static bool resource_is_resolved(struct resource *res)
35 {
36         return !res || res->resolved;
37 }
38
39 /* We only use this in an assert, which will disappear if we're compiling
40  * with NDEBUG, so we need the 'used' attribute for these builds */
41 static bool __attribute__((used)) boot_option_is_resolved(
42                 struct discover_boot_option *opt)
43 {
44         return resource_is_resolved(opt->boot_image) &&
45                 resource_is_resolved(opt->initrd) &&
46                 resource_is_resolved(opt->icon);
47 }
48
49 static void boot_option_finalise(struct discover_boot_option *opt)
50 {
51         assert(boot_option_is_resolved(opt));
52
53         /* check that the parsers haven't set any of the final data */
54         assert(!opt->option->boot_image_file);
55         assert(!opt->option->initrd_file);
56         assert(!opt->option->icon_file);
57
58         if (opt->boot_image)
59                 opt->option->boot_image_file = opt->boot_image->url->full;
60         if (opt->initrd)
61                 opt->option->initrd_file = opt->initrd->url->full;
62         if (opt->icon)
63                 opt->option->icon_file = opt->icon->url->full;
64 }
65
66 /**
67  * context_commit - Commit a temporary discovery context to the handler,
68  * and notify the clients about any new options / devices
69  */
70 static void context_commit(struct device_handler *handler,
71                 struct discover_context *ctx)
72 {
73         struct discover_device *dev = ctx->device;
74         struct discover_boot_option *opt, *tmp;
75         unsigned int i, existing_device = 0;
76
77         /* do we already have this device? */
78         for (i = 0; i < handler->n_devices; i++) {
79                 if (ctx->device == handler->devices[i]) {
80                         existing_device = 1;
81                         break;
82                 }
83         }
84
85         /* if not already present, add the device to the handler's array */
86         if (!existing_device) {
87                 handler->n_devices++;
88                 handler->devices = talloc_realloc(handler, handler->devices,
89                         struct discover_device *, handler->n_devices);
90                 handler->devices[handler->n_devices - 1] = dev;
91                 talloc_steal(handler, dev);
92
93                 discover_server_notify_device_add(handler->server, dev->device);
94         }
95
96
97         /* move boot options from the context to the device */
98         list_for_each_entry_safe(&ctx->boot_options, opt, tmp, list) {
99                 list_remove(&opt->list);
100                 list_add(&dev->boot_options, &opt->list);
101                 talloc_steal(dev, opt);
102                 boot_option_finalise(opt);
103                 discover_server_notify_boot_option_add(handler->server,
104                                                         opt->option);
105         }
106 }
107
108 void discover_context_add_boot_option(struct discover_context *ctx,
109                 struct discover_boot_option *boot_option)
110 {
111         list_add(&ctx->boot_options, &boot_option->list);
112         talloc_steal(ctx, boot_option);
113 }
114
115 /**
116  * device_handler_remove - Remove a device from the handler device array.
117  */
118
119 static void device_handler_remove(struct device_handler *handler,
120         struct discover_device *device)
121 {
122         unsigned int i;
123
124         for (i = 0; i < handler->n_devices; i++)
125                 if (handler->devices[i] == device)
126                         break;
127
128         if (i == handler->n_devices) {
129                 assert(0 && "unknown device");
130                 return;
131         }
132
133         handler->n_devices--;
134         memmove(&handler->devices[i], &handler->devices[i + 1],
135                 (handler->n_devices - i) * sizeof(handler->devices[0]));
136         handler->devices = talloc_realloc(handler, handler->devices,
137                 struct discover_device *, handler->n_devices);
138
139         discover_server_notify_device_remove(handler->server, device->device);
140
141         talloc_free(device);
142 }
143
144 /**
145  * device_handler_get_device_count - Get the count of current handler devices.
146  */
147
148 int device_handler_get_device_count(const struct device_handler *handler)
149 {
150         return handler->n_devices;
151 }
152
153 /**
154  * device_handler_get_device - Get a handler device by index.
155  */
156
157 const struct discover_device *device_handler_get_device(
158         const struct device_handler *handler, unsigned int index)
159 {
160         if (index >= handler->n_devices) {
161                 assert(0 && "bad index");
162                 return NULL;
163         }
164
165         return handler->devices[index];
166 }
167
168 static int mount_device(struct discover_device *dev)
169 {
170         const char *argv[6];
171
172         if (!dev->mount_path)
173                 dev->mount_path = join_paths(dev, mount_base(),
174                                                 dev->device_path);
175
176         if (pb_mkdir_recursive(dev->mount_path))
177                 pb_log("couldn't create mount directory %s: %s\n",
178                                 dev->mount_path, strerror(errno));
179
180         argv[0] = pb_system_apps.mount;
181         argv[1] = dev->device_path;
182         argv[2] = dev->mount_path;
183         argv[3] = "-o";
184         argv[4] = "ro";
185         argv[5] = NULL;
186
187         if (pb_run_cmd(argv, 1, 0)) {
188
189                 /* Retry mount without ro option. */
190
191                 argv[0] = pb_system_apps.mount;
192                 argv[1] = dev->device_path;
193                 argv[2] = dev->mount_path;
194                 argv[3] = NULL;
195
196                 if (pb_run_cmd(argv, 1, 0))
197                         goto out_rmdir;
198         }
199
200         return 0;
201
202 out_rmdir:
203         pb_rmdir_recursive(mount_base(), dev->mount_path);
204         return -1;
205 }
206
207 static int umount_device(struct discover_device *dev)
208 {
209         int status;
210         pid_t pid;
211
212         if (!dev->mount_path)
213                 return 0;
214
215         pid = fork();
216         if (pid == -1) {
217                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
218                 return -1;
219         }
220
221         if (pid == 0) {
222                 execl(pb_system_apps.umount, pb_system_apps.umount,
223                                                 dev->mount_path, NULL);
224                 exit(EXIT_FAILURE);
225         }
226
227         if (waitpid(pid, &status, 0) == -1) {
228                 pb_log("%s: waitpid failed: %s\n", __func__,
229                                 strerror(errno));
230                 return -1;
231         }
232
233         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
234                 return -1;
235
236         pb_rmdir_recursive(mount_base(), dev->mount_path);
237
238         return 0;
239 }
240
241 static struct discover_device *find_device(struct device_handler *handler,
242                 const char *id)
243 {
244         struct discover_device *dev;
245         unsigned int i;
246
247         for (i = 0; i < handler->n_devices; i++) {
248                 dev = handler->devices[i];
249                 if (!strcmp(dev->device->id, id))
250                         return dev;
251         }
252
253         return NULL;
254 }
255
256 static int destroy_device(void *arg)
257 {
258         struct discover_device *dev = arg;
259
260         umount_device(dev);
261
262         return 0;
263 }
264
265 static struct discover_device *discover_device_create(
266                 struct device_handler *handler,
267                 struct discover_context *ctx,
268                 struct event *event)
269 {
270         struct discover_device *dev;
271         const char *devname;
272
273         dev = find_device(handler, event->device);
274         if (dev)
275                 return dev;
276
277         dev = talloc_zero(ctx, struct discover_device);
278         dev->device = talloc_zero(dev, struct device);
279         list_init(&dev->boot_options);
280
281         devname = event_get_param(ctx->event, "DEVNAME");
282         if (devname)
283                 dev->device_path = talloc_strdup(dev, devname);
284
285         dev->device->id = talloc_strdup(dev, event->device);
286
287         talloc_set_destructor(dev, destroy_device);
288
289         return dev;
290 }
291
292 struct discover_boot_option *discover_boot_option_create(
293                 struct discover_context *ctx,
294                 struct discover_device *device)
295 {
296         struct discover_boot_option *opt;
297
298         opt = talloc_zero(ctx, struct discover_boot_option);
299         opt->option = talloc_zero(opt, struct boot_option);
300         opt->device = device;
301
302         return opt;
303 }
304
305
306 static int handle_add_udev_event(struct device_handler *handler,
307                 struct event *event)
308 {
309         struct discover_context *ctx;
310         struct discover_device *dev;
311         const char *param;
312         int rc;
313
314         /* create our context */
315         ctx = talloc(handler, struct discover_context);
316         ctx->event = event;
317         list_init(&ctx->boot_options);
318
319         /* create our top-level device */
320         dev = discover_device_create(handler, ctx, event);
321
322         ctx->device = dev;
323
324         /* try to parse UUID and labels */
325         param = event_get_param(ctx->event, "ID_FS_UUID");
326         if (param)
327                 dev->uuid = talloc_strdup(dev, param);
328
329         param = event_get_param(ctx->event, "ID_FS_LABEL");
330         if (param)
331                 dev->label = talloc_strdup(dev, param);
332
333         rc = mount_device(dev);
334         if (rc) {
335                 talloc_free(ctx);
336                 return 0;
337         }
338
339         /* run the parsers. This will populate the ctx's boot_option list. */
340         iterate_parsers(ctx);
341
342         /* add discovered stuff to the handler */
343         context_commit(handler, ctx);
344
345         talloc_free(ctx);
346
347         return 0;
348 }
349
350 static int handle_remove_udev_event(struct device_handler *handler,
351                 struct event *event)
352 {
353         struct discover_device *dev;
354
355         dev = find_device(handler, event->device);
356         if (!dev)
357                 return 0;
358
359         /* remove device from handler device array */
360         device_handler_remove(handler, dev);
361
362         return 0;
363 }
364
365 static int handle_add_user_event(struct device_handler *handler,
366                 struct event *event)
367 {
368         struct discover_context *ctx;
369         struct discover_device *dev;
370         int rc;
371
372         assert(event->device);
373
374         ctx = talloc(handler, struct discover_context);
375         ctx->event = event;
376         list_init(&ctx->boot_options);
377
378         dev = discover_device_create(handler, ctx, event);
379         ctx->device = dev;
380
381         rc = parse_user_event(ctx, event);
382
383         if (!rc)
384                 context_commit(handler, ctx);
385
386         return rc;
387 }
388
389 static int handle_remove_user_event(struct device_handler *handler,
390                 struct event *event)
391 {
392         struct discover_device *dev = find_device(handler, event->device);
393
394         if (!dev)
395                 return 0;
396
397         /* remove device from handler device array */
398         device_handler_remove(handler, dev);
399
400         return 0;
401 }
402
403 typedef int (*event_handler)(struct device_handler *, struct event *);
404
405 static event_handler handlers[EVENT_TYPE_MAX][EVENT_ACTION_MAX] = {
406         [EVENT_TYPE_UDEV] = {
407                 [EVENT_ACTION_ADD]      = handle_add_udev_event,
408                 [EVENT_ACTION_REMOVE]   = handle_remove_udev_event,
409         },
410         [EVENT_TYPE_USER] = {
411                 [EVENT_ACTION_ADD]      = handle_add_user_event,
412                 [EVENT_ACTION_REMOVE]   = handle_remove_user_event,
413         }
414 };
415
416 int device_handler_event(struct device_handler *handler,
417                 struct event *event)
418 {
419         if (event->type >= EVENT_TYPE_MAX ||
420                         event->action >= EVENT_ACTION_MAX ||
421                         !handlers[event->type][event->action]) {
422                 pb_log("%s unknown type/action: %d/%d\n", __func__,
423                                 event->type, event->action);
424                 return 0;
425         }
426
427         return handlers[event->type][event->action](handler, event);
428 }
429
430 struct device_handler *device_handler_init(struct discover_server *server,
431                 int dry_run)
432 {
433         struct device_handler *handler;
434
435         handler = talloc(NULL, struct device_handler);
436         handler->devices = NULL;
437         handler->n_devices = 0;
438         handler->server = server;
439         handler->dry_run = dry_run;
440
441         /* set up our mount point base */
442         pb_mkdir_recursive(mount_base());
443
444         parser_init();
445
446         return handler;
447 }
448
449 void device_handler_destroy(struct device_handler *handler)
450 {
451         talloc_free(handler);
452 }
453
454 static int device_match_path(struct discover_device *dev, const char *path)
455 {
456         return !strcmp(dev->device_path, path);
457 }
458
459 static int device_match_uuid(struct discover_device *dev, const char *uuid)
460 {
461         return dev->uuid && !strcmp(dev->uuid, uuid);
462 }
463
464 static int device_match_label(struct discover_device *dev, const char *label)
465 {
466         return dev->label && !strcmp(dev->label, label);
467 }
468
469 static int device_match_id(struct discover_device *dev, const char *id)
470 {
471         return !strcmp(dev->device->id, id);
472 }
473
474 static struct discover_device *device_lookup(
475                 struct device_handler *device_handler,
476                 int (match_fn)(struct discover_device *, const char *),
477                 const char *str)
478 {
479         struct discover_device *dev;
480         unsigned int i;
481
482         if (!str)
483                 return NULL;
484
485         for (i = 0; i < device_handler->n_devices; i++) {
486                 dev = device_handler->devices[i];
487
488                 if (match_fn(dev, str))
489                         return dev;
490         }
491
492         return NULL;
493 }
494
495 struct discover_device *device_lookup_by_name(struct device_handler *handler,
496                 const char *name)
497 {
498         struct discover_device *dev;
499         char *path;
500
501         if (strncmp(name, "/dev/", strlen("/dev/")))
502                 path = talloc_asprintf(NULL, "/dev/%s", name);
503         else
504                 path = talloc_strdup(NULL, name);
505
506         dev = device_lookup_by_path(handler, path);
507
508         talloc_free(path);
509
510         return dev;
511 }
512
513 struct discover_device *device_lookup_by_path(
514                 struct device_handler *device_handler,
515                 const char *path)
516 {
517         return device_lookup(device_handler, device_match_path, path);
518 }
519
520 struct discover_device *device_lookup_by_uuid(
521                 struct device_handler *device_handler,
522                 const char *uuid)
523 {
524         return device_lookup(device_handler, device_match_uuid, uuid);
525 }
526
527 struct discover_device *device_lookup_by_label(
528                 struct device_handler *device_handler,
529                 const char *label)
530 {
531         return device_lookup(device_handler, device_match_label, label);
532 }
533
534 struct discover_device *device_lookup_by_id(
535                 struct device_handler *device_handler,
536                 const char *id)
537 {
538         return device_lookup(device_handler, device_match_id, id);
539 }
540
541 static struct discover_boot_option *find_boot_option_by_id(
542                 struct device_handler *handler, const char *id)
543 {
544         unsigned int i;
545
546         for (i = 0; i < handler->n_devices; i++) {
547                 struct discover_device *dev = handler->devices[i];
548                 struct discover_boot_option *opt;
549
550                 list_for_each_entry(&dev->boot_options, opt, list)
551                         if (!strcmp(opt->option->id, id))
552                                 return opt;
553         }
554
555         return NULL;
556 }
557
558 void device_handler_boot(struct device_handler *handler,
559                 struct boot_command *cmd)
560 {
561         struct discover_boot_option *opt;
562
563         opt = find_boot_option_by_id(handler, cmd->option_id);
564
565         boot(handler, opt, cmd, handler->dry_run);
566 }