]> git.ozlabs.org Git - petitboot/blob - discover/device-handler.c
discover: Don't invert option discovery order
[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         struct list             unresolved_boot_options;
34 };
35
36 static bool resource_is_resolved(struct resource *res)
37 {
38         return !res || res->resolved;
39 }
40
41 /* We only use this in an assert, which will disappear if we're compiling
42  * with NDEBUG, so we need the 'used' attribute for these builds */
43 static bool __attribute__((used)) boot_option_is_resolved(
44                 struct discover_boot_option *opt)
45 {
46         return resource_is_resolved(opt->boot_image) &&
47                 resource_is_resolved(opt->initrd) &&
48                 resource_is_resolved(opt->icon);
49 }
50
51 static bool resource_resolve(struct resource *res, struct parser *parser,
52                 struct device_handler *handler)
53 {
54         if (resource_is_resolved(res))
55                 return true;
56
57         parser->resolve_resource(handler, res);
58
59         return res->resolved;
60 }
61
62 static bool boot_option_resolve(struct discover_boot_option *opt,
63                 struct device_handler *handler)
64 {
65         return resource_resolve(opt->boot_image, opt->source, handler) &&
66                 resource_resolve(opt->initrd, opt->source, handler) &&
67                 resource_resolve(opt->icon, opt->source, handler);
68 }
69
70 static void boot_option_finalise(struct discover_boot_option *opt)
71 {
72         assert(boot_option_is_resolved(opt));
73
74         /* check that the parsers haven't set any of the final data */
75         assert(!opt->option->boot_image_file);
76         assert(!opt->option->initrd_file);
77         assert(!opt->option->icon_file);
78
79         if (opt->boot_image)
80                 opt->option->boot_image_file = opt->boot_image->url->full;
81         if (opt->initrd)
82                 opt->option->initrd_file = opt->initrd->url->full;
83         if (opt->icon)
84                 opt->option->icon_file = opt->icon->url->full;
85 }
86
87 static void process_boot_option_queue(struct device_handler *handler)
88 {
89         struct discover_boot_option *opt, *tmp;
90
91         list_for_each_entry_safe(&handler->unresolved_boot_options,
92                         opt, tmp, list) {
93
94                 if (!boot_option_resolve(opt, handler))
95                         continue;
96
97                 list_remove(&opt->list);
98                 list_add(&opt->device->boot_options, &opt->list);
99                 talloc_steal(opt->device, opt);
100                 boot_option_finalise(opt);
101                 discover_server_notify_boot_option_add(handler->server,
102                                                         opt->option);
103         }
104 }
105
106 /**
107  * context_commit - Commit a temporary discovery context to the handler,
108  * and notify the clients about any new options / devices
109  */
110 static void context_commit(struct device_handler *handler,
111                 struct discover_context *ctx)
112 {
113         struct discover_device *dev = ctx->device;
114         struct discover_boot_option *opt, *tmp;
115         unsigned int i, existing_device = 0;
116
117         /* do we already have this device? */
118         for (i = 0; i < handler->n_devices; i++) {
119                 if (ctx->device == handler->devices[i]) {
120                         existing_device = 1;
121                         break;
122                 }
123         }
124
125         /* if not already present, add the device to the handler's array */
126         if (!existing_device) {
127                 handler->n_devices++;
128                 handler->devices = talloc_realloc(handler, handler->devices,
129                         struct discover_device *, handler->n_devices);
130                 handler->devices[handler->n_devices - 1] = dev;
131                 talloc_steal(handler, dev);
132
133                 discover_server_notify_device_add(handler->server, dev->device);
134
135                 /* this new device might be able to resolve existing boot
136                  * options */
137                 process_boot_option_queue(handler);
138         }
139
140
141         /* move boot options from the context to the device */
142         list_for_each_entry_safe(&ctx->boot_options, opt, tmp, list) {
143                 list_remove(&opt->list);
144
145                 if (boot_option_resolve(opt, handler)) {
146                         list_add(&dev->boot_options, &opt->list);
147                         talloc_steal(dev, opt);
148                         boot_option_finalise(opt);
149                         discover_server_notify_boot_option_add(handler->server,
150                                                                 opt->option);
151                 } else {
152                         if (!opt->source->resolve_resource) {
153                                 pb_log("parser %s gave us an unresolved "
154                                         "resource (%s), but no way to "
155                                         "resolve it\n",
156                                         opt->source->name, opt->option->id);
157                                 talloc_free(opt);
158                         } else {
159                                 pb_log("boot option %s is unresolved, "
160                                                 "adding to queue\n",
161                                                 opt->option->id);
162                                 list_add(&handler->unresolved_boot_options,
163                                                 &opt->list);
164                                 talloc_steal(handler, opt);
165                         }
166                 }
167         }
168 }
169
170 void discover_context_add_boot_option(struct discover_context *ctx,
171                 struct discover_boot_option *boot_option)
172 {
173         boot_option->source = ctx->parser;
174         list_add_tail(&ctx->boot_options, &boot_option->list);
175         talloc_steal(ctx, boot_option);
176 }
177
178 /**
179  * device_handler_remove - Remove a device from the handler device array.
180  */
181
182 static void device_handler_remove(struct device_handler *handler,
183         struct discover_device *device)
184 {
185         unsigned int i;
186
187         for (i = 0; i < handler->n_devices; i++)
188                 if (handler->devices[i] == device)
189                         break;
190
191         if (i == handler->n_devices) {
192                 assert(0 && "unknown device");
193                 return;
194         }
195
196         handler->n_devices--;
197         memmove(&handler->devices[i], &handler->devices[i + 1],
198                 (handler->n_devices - i) * sizeof(handler->devices[0]));
199         handler->devices = talloc_realloc(handler, handler->devices,
200                 struct discover_device *, handler->n_devices);
201
202         discover_server_notify_device_remove(handler->server, device->device);
203
204         talloc_free(device);
205 }
206
207 /**
208  * device_handler_get_device_count - Get the count of current handler devices.
209  */
210
211 int device_handler_get_device_count(const struct device_handler *handler)
212 {
213         return handler->n_devices;
214 }
215
216 /**
217  * device_handler_get_device - Get a handler device by index.
218  */
219
220 const struct discover_device *device_handler_get_device(
221         const struct device_handler *handler, unsigned int index)
222 {
223         if (index >= handler->n_devices) {
224                 assert(0 && "bad index");
225                 return NULL;
226         }
227
228         return handler->devices[index];
229 }
230
231 static int mount_device(struct discover_device *dev)
232 {
233         const char *argv[6];
234
235         if (!dev->mount_path)
236                 dev->mount_path = join_paths(dev, mount_base(),
237                                                 dev->device_path);
238
239         if (pb_mkdir_recursive(dev->mount_path))
240                 pb_log("couldn't create mount directory %s: %s\n",
241                                 dev->mount_path, strerror(errno));
242
243         argv[0] = pb_system_apps.mount;
244         argv[1] = dev->device_path;
245         argv[2] = dev->mount_path;
246         argv[3] = "-o";
247         argv[4] = "ro";
248         argv[5] = NULL;
249
250         if (pb_run_cmd(argv, 1, 0)) {
251
252                 /* Retry mount without ro option. */
253
254                 argv[0] = pb_system_apps.mount;
255                 argv[1] = dev->device_path;
256                 argv[2] = dev->mount_path;
257                 argv[3] = NULL;
258
259                 if (pb_run_cmd(argv, 1, 0))
260                         goto out_rmdir;
261         }
262
263         return 0;
264
265 out_rmdir:
266         pb_rmdir_recursive(mount_base(), dev->mount_path);
267         return -1;
268 }
269
270 static int umount_device(struct discover_device *dev)
271 {
272         int status;
273         pid_t pid;
274
275         if (!dev->mount_path)
276                 return 0;
277
278         pid = fork();
279         if (pid == -1) {
280                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
281                 return -1;
282         }
283
284         if (pid == 0) {
285                 execl(pb_system_apps.umount, pb_system_apps.umount,
286                                                 dev->mount_path, NULL);
287                 exit(EXIT_FAILURE);
288         }
289
290         if (waitpid(pid, &status, 0) == -1) {
291                 pb_log("%s: waitpid failed: %s\n", __func__,
292                                 strerror(errno));
293                 return -1;
294         }
295
296         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
297                 return -1;
298
299         pb_rmdir_recursive(mount_base(), dev->mount_path);
300
301         return 0;
302 }
303
304 static struct discover_device *find_device(struct device_handler *handler,
305                 const char *id)
306 {
307         struct discover_device *dev;
308         unsigned int i;
309
310         for (i = 0; i < handler->n_devices; i++) {
311                 dev = handler->devices[i];
312                 if (!strcmp(dev->device->id, id))
313                         return dev;
314         }
315
316         return NULL;
317 }
318
319 static int destroy_device(void *arg)
320 {
321         struct discover_device *dev = arg;
322
323         umount_device(dev);
324
325         return 0;
326 }
327
328 static struct discover_device *discover_device_create(
329                 struct device_handler *handler,
330                 struct discover_context *ctx,
331                 struct event *event)
332 {
333         struct discover_device *dev;
334         const char *devname;
335
336         dev = find_device(handler, event->device);
337         if (dev)
338                 return dev;
339
340         dev = talloc_zero(ctx, struct discover_device);
341         dev->device = talloc_zero(dev, struct device);
342         list_init(&dev->boot_options);
343
344         devname = event_get_param(ctx->event, "DEVNAME");
345         if (devname)
346                 dev->device_path = talloc_strdup(dev, devname);
347
348         dev->device->id = talloc_strdup(dev, event->device);
349
350         talloc_set_destructor(dev, destroy_device);
351
352         return dev;
353 }
354
355 struct discover_boot_option *discover_boot_option_create(
356                 struct discover_context *ctx,
357                 struct discover_device *device)
358 {
359         struct discover_boot_option *opt;
360
361         opt = talloc_zero(ctx, struct discover_boot_option);
362         opt->option = talloc_zero(opt, struct boot_option);
363         opt->device = device;
364
365         return opt;
366 }
367
368
369 static int handle_add_udev_event(struct device_handler *handler,
370                 struct event *event)
371 {
372         struct discover_context *ctx;
373         struct discover_device *dev;
374         const char *param;
375         int rc;
376
377         /* create our context */
378         ctx = talloc(handler, struct discover_context);
379         ctx->event = event;
380         list_init(&ctx->boot_options);
381
382         /* create our top-level device */
383         dev = discover_device_create(handler, ctx, event);
384
385         ctx->device = dev;
386
387         /* try to parse UUID and labels */
388         param = event_get_param(ctx->event, "ID_FS_UUID");
389         if (param)
390                 dev->uuid = talloc_strdup(dev, param);
391
392         param = event_get_param(ctx->event, "ID_FS_LABEL");
393         if (param)
394                 dev->label = talloc_strdup(dev, param);
395
396         rc = mount_device(dev);
397         if (rc) {
398                 talloc_free(ctx);
399                 return 0;
400         }
401
402         /* run the parsers. This will populate the ctx's boot_option list. */
403         iterate_parsers(ctx, CONF_METHOD_LOCAL_FILE);
404
405         /* add discovered stuff to the handler */
406         context_commit(handler, ctx);
407
408         talloc_free(ctx);
409
410         return 0;
411 }
412
413 static int handle_remove_udev_event(struct device_handler *handler,
414                 struct event *event)
415 {
416         struct discover_device *dev;
417
418         dev = find_device(handler, event->device);
419         if (!dev)
420                 return 0;
421
422         /* remove device from handler device array */
423         device_handler_remove(handler, dev);
424
425         return 0;
426 }
427
428 static int handle_add_user_event(struct device_handler *handler,
429                 struct event *event)
430 {
431         struct discover_context *ctx;
432         struct discover_device *dev;
433         int rc;
434
435         assert(event->device);
436
437         ctx = talloc(handler, struct discover_context);
438         ctx->event = event;
439         list_init(&ctx->boot_options);
440
441         dev = discover_device_create(handler, ctx, event);
442         ctx->device = dev;
443
444         rc = parse_user_event(ctx, event);
445
446         if (!rc)
447                 context_commit(handler, ctx);
448
449         return rc;
450 }
451
452 static int handle_remove_user_event(struct device_handler *handler,
453                 struct event *event)
454 {
455         struct discover_device *dev = find_device(handler, event->device);
456
457         if (!dev)
458                 return 0;
459
460         /* remove device from handler device array */
461         device_handler_remove(handler, dev);
462
463         return 0;
464 }
465
466 static enum conf_method parse_conf_method(const char *str)
467 {
468
469         if (!strcasecmp(str, "dhcp")) {
470                 return CONF_METHOD_DHCP;
471         }
472         return CONF_METHOD_UNKNOWN;
473 }
474
475 static int handle_conf_user_event(struct device_handler *handler,
476                 struct event *event)
477 {
478         struct discover_context *ctx;
479         struct discover_device *dev;
480         enum conf_method method;
481         const char *val;
482
483         ctx = talloc(handler, struct discover_context);
484         ctx->event = event;
485         list_init(&ctx->boot_options);
486
487         val = event_get_param(event, "url");
488         if (!val) {
489                 talloc_free(ctx);
490                 return 0;
491         }
492
493         ctx->conf_url = pb_url_parse(ctx, val);
494         if (!ctx->conf_url) {
495                 talloc_free(ctx);
496                 return 0;
497         }
498
499         val = event_get_param(event, "method");
500         if (!val) {
501                 talloc_free(ctx);
502                 return 0;
503         }
504
505         method = parse_conf_method(val);
506         if (method == CONF_METHOD_UNKNOWN) {
507                 talloc_free(ctx);
508                 return 0;
509         }
510
511         dev = discover_device_create(handler, ctx, event);
512         ctx->device = dev;
513
514         iterate_parsers(ctx, method);
515
516         context_commit(handler, ctx);
517
518         return 0;
519 }
520
521 typedef int (*event_handler)(struct device_handler *, struct event *);
522
523 static event_handler handlers[EVENT_TYPE_MAX][EVENT_ACTION_MAX] = {
524         [EVENT_TYPE_UDEV] = {
525                 [EVENT_ACTION_ADD]      = handle_add_udev_event,
526                 [EVENT_ACTION_REMOVE]   = handle_remove_udev_event,
527         },
528         [EVENT_TYPE_USER] = {
529                 [EVENT_ACTION_ADD]      = handle_add_user_event,
530                 [EVENT_ACTION_REMOVE]   = handle_remove_user_event,
531                 [EVENT_ACTION_CONF]     = handle_conf_user_event,
532         }
533 };
534
535 int device_handler_event(struct device_handler *handler,
536                 struct event *event)
537 {
538         if (event->type >= EVENT_TYPE_MAX ||
539                         event->action >= EVENT_ACTION_MAX ||
540                         !handlers[event->type][event->action]) {
541                 pb_log("%s unknown type/action: %d/%d\n", __func__,
542                                 event->type, event->action);
543                 return 0;
544         }
545
546         return handlers[event->type][event->action](handler, event);
547 }
548
549 struct device_handler *device_handler_init(struct discover_server *server,
550                 int dry_run)
551 {
552         struct device_handler *handler;
553
554         handler = talloc(NULL, struct device_handler);
555         handler->devices = NULL;
556         handler->n_devices = 0;
557         handler->server = server;
558         handler->dry_run = dry_run;
559         list_init(&handler->unresolved_boot_options);
560
561         /* set up our mount point base */
562         pb_mkdir_recursive(mount_base());
563
564         parser_init();
565
566         return handler;
567 }
568
569 void device_handler_destroy(struct device_handler *handler)
570 {
571         talloc_free(handler);
572 }
573
574 static int device_match_path(struct discover_device *dev, const char *path)
575 {
576         return !strcmp(dev->device_path, path);
577 }
578
579 static int device_match_uuid(struct discover_device *dev, const char *uuid)
580 {
581         return dev->uuid && !strcmp(dev->uuid, uuid);
582 }
583
584 static int device_match_label(struct discover_device *dev, const char *label)
585 {
586         return dev->label && !strcmp(dev->label, label);
587 }
588
589 static int device_match_id(struct discover_device *dev, const char *id)
590 {
591         return !strcmp(dev->device->id, id);
592 }
593
594 static struct discover_device *device_lookup(
595                 struct device_handler *device_handler,
596                 int (match_fn)(struct discover_device *, const char *),
597                 const char *str)
598 {
599         struct discover_device *dev;
600         unsigned int i;
601
602         if (!str)
603                 return NULL;
604
605         for (i = 0; i < device_handler->n_devices; i++) {
606                 dev = device_handler->devices[i];
607
608                 if (match_fn(dev, str))
609                         return dev;
610         }
611
612         return NULL;
613 }
614
615 struct discover_device *device_lookup_by_name(struct device_handler *handler,
616                 const char *name)
617 {
618         struct discover_device *dev;
619         char *path;
620
621         if (strncmp(name, "/dev/", strlen("/dev/")))
622                 path = talloc_asprintf(NULL, "/dev/%s", name);
623         else
624                 path = talloc_strdup(NULL, name);
625
626         dev = device_lookup_by_path(handler, path);
627
628         talloc_free(path);
629
630         return dev;
631 }
632
633 struct discover_device *device_lookup_by_path(
634                 struct device_handler *device_handler,
635                 const char *path)
636 {
637         return device_lookup(device_handler, device_match_path, path);
638 }
639
640 struct discover_device *device_lookup_by_uuid(
641                 struct device_handler *device_handler,
642                 const char *uuid)
643 {
644         return device_lookup(device_handler, device_match_uuid, uuid);
645 }
646
647 struct discover_device *device_lookup_by_label(
648                 struct device_handler *device_handler,
649                 const char *label)
650 {
651         return device_lookup(device_handler, device_match_label, label);
652 }
653
654 struct discover_device *device_lookup_by_id(
655                 struct device_handler *device_handler,
656                 const char *id)
657 {
658         return device_lookup(device_handler, device_match_id, id);
659 }
660
661 static struct discover_boot_option *find_boot_option_by_id(
662                 struct device_handler *handler, const char *id)
663 {
664         unsigned int i;
665
666         for (i = 0; i < handler->n_devices; i++) {
667                 struct discover_device *dev = handler->devices[i];
668                 struct discover_boot_option *opt;
669
670                 list_for_each_entry(&dev->boot_options, opt, list)
671                         if (!strcmp(opt->option->id, id))
672                                 return opt;
673         }
674
675         return NULL;
676 }
677
678 static void boot_status(void *arg, struct boot_status *status)
679 {
680         struct device_handler *handler = arg;
681
682         discover_server_notify_boot_status(handler->server, status);
683 }
684
685 void device_handler_boot(struct device_handler *handler,
686                 struct boot_command *cmd)
687 {
688         struct discover_boot_option *opt;
689
690         opt = find_boot_option_by_id(handler, cmd->option_id);
691
692         boot(handler, opt, cmd, handler->dry_run, boot_status, handler);
693 }