discover: detect unresolvable resources
[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                                 list_add(&handler->unresolved_boot_options,
160                                                 &opt->list);
161                                 talloc_steal(handler, opt);
162                         }
163                 }
164         }
165 }
166
167 void discover_context_add_boot_option(struct discover_context *ctx,
168                 struct discover_boot_option *boot_option)
169 {
170         boot_option->source = ctx->parser;
171         list_add(&ctx->boot_options, &boot_option->list);
172         talloc_steal(ctx, boot_option);
173 }
174
175 /**
176  * device_handler_remove - Remove a device from the handler device array.
177  */
178
179 static void device_handler_remove(struct device_handler *handler,
180         struct discover_device *device)
181 {
182         unsigned int i;
183
184         for (i = 0; i < handler->n_devices; i++)
185                 if (handler->devices[i] == device)
186                         break;
187
188         if (i == handler->n_devices) {
189                 assert(0 && "unknown device");
190                 return;
191         }
192
193         handler->n_devices--;
194         memmove(&handler->devices[i], &handler->devices[i + 1],
195                 (handler->n_devices - i) * sizeof(handler->devices[0]));
196         handler->devices = talloc_realloc(handler, handler->devices,
197                 struct discover_device *, handler->n_devices);
198
199         discover_server_notify_device_remove(handler->server, device->device);
200
201         talloc_free(device);
202 }
203
204 /**
205  * device_handler_get_device_count - Get the count of current handler devices.
206  */
207
208 int device_handler_get_device_count(const struct device_handler *handler)
209 {
210         return handler->n_devices;
211 }
212
213 /**
214  * device_handler_get_device - Get a handler device by index.
215  */
216
217 const struct discover_device *device_handler_get_device(
218         const struct device_handler *handler, unsigned int index)
219 {
220         if (index >= handler->n_devices) {
221                 assert(0 && "bad index");
222                 return NULL;
223         }
224
225         return handler->devices[index];
226 }
227
228 static int mount_device(struct discover_device *dev)
229 {
230         const char *argv[6];
231
232         if (!dev->mount_path)
233                 dev->mount_path = join_paths(dev, mount_base(),
234                                                 dev->device_path);
235
236         if (pb_mkdir_recursive(dev->mount_path))
237                 pb_log("couldn't create mount directory %s: %s\n",
238                                 dev->mount_path, strerror(errno));
239
240         argv[0] = pb_system_apps.mount;
241         argv[1] = dev->device_path;
242         argv[2] = dev->mount_path;
243         argv[3] = "-o";
244         argv[4] = "ro";
245         argv[5] = NULL;
246
247         if (pb_run_cmd(argv, 1, 0)) {
248
249                 /* Retry mount without ro option. */
250
251                 argv[0] = pb_system_apps.mount;
252                 argv[1] = dev->device_path;
253                 argv[2] = dev->mount_path;
254                 argv[3] = NULL;
255
256                 if (pb_run_cmd(argv, 1, 0))
257                         goto out_rmdir;
258         }
259
260         return 0;
261
262 out_rmdir:
263         pb_rmdir_recursive(mount_base(), dev->mount_path);
264         return -1;
265 }
266
267 static int umount_device(struct discover_device *dev)
268 {
269         int status;
270         pid_t pid;
271
272         if (!dev->mount_path)
273                 return 0;
274
275         pid = fork();
276         if (pid == -1) {
277                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
278                 return -1;
279         }
280
281         if (pid == 0) {
282                 execl(pb_system_apps.umount, pb_system_apps.umount,
283                                                 dev->mount_path, NULL);
284                 exit(EXIT_FAILURE);
285         }
286
287         if (waitpid(pid, &status, 0) == -1) {
288                 pb_log("%s: waitpid failed: %s\n", __func__,
289                                 strerror(errno));
290                 return -1;
291         }
292
293         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
294                 return -1;
295
296         pb_rmdir_recursive(mount_base(), dev->mount_path);
297
298         return 0;
299 }
300
301 static struct discover_device *find_device(struct device_handler *handler,
302                 const char *id)
303 {
304         struct discover_device *dev;
305         unsigned int i;
306
307         for (i = 0; i < handler->n_devices; i++) {
308                 dev = handler->devices[i];
309                 if (!strcmp(dev->device->id, id))
310                         return dev;
311         }
312
313         return NULL;
314 }
315
316 static int destroy_device(void *arg)
317 {
318         struct discover_device *dev = arg;
319
320         umount_device(dev);
321
322         return 0;
323 }
324
325 static struct discover_device *discover_device_create(
326                 struct device_handler *handler,
327                 struct discover_context *ctx,
328                 struct event *event)
329 {
330         struct discover_device *dev;
331         const char *devname;
332
333         dev = find_device(handler, event->device);
334         if (dev)
335                 return dev;
336
337         dev = talloc_zero(ctx, struct discover_device);
338         dev->device = talloc_zero(dev, struct device);
339         list_init(&dev->boot_options);
340
341         devname = event_get_param(ctx->event, "DEVNAME");
342         if (devname)
343                 dev->device_path = talloc_strdup(dev, devname);
344
345         dev->device->id = talloc_strdup(dev, event->device);
346
347         talloc_set_destructor(dev, destroy_device);
348
349         return dev;
350 }
351
352 struct discover_boot_option *discover_boot_option_create(
353                 struct discover_context *ctx,
354                 struct discover_device *device)
355 {
356         struct discover_boot_option *opt;
357
358         opt = talloc_zero(ctx, struct discover_boot_option);
359         opt->option = talloc_zero(opt, struct boot_option);
360         opt->device = device;
361
362         return opt;
363 }
364
365
366 static int handle_add_udev_event(struct device_handler *handler,
367                 struct event *event)
368 {
369         struct discover_context *ctx;
370         struct discover_device *dev;
371         const char *param;
372         int rc;
373
374         /* create our context */
375         ctx = talloc(handler, struct discover_context);
376         ctx->event = event;
377         list_init(&ctx->boot_options);
378
379         /* create our top-level device */
380         dev = discover_device_create(handler, ctx, event);
381
382         ctx->device = dev;
383
384         /* try to parse UUID and labels */
385         param = event_get_param(ctx->event, "ID_FS_UUID");
386         if (param)
387                 dev->uuid = talloc_strdup(dev, param);
388
389         param = event_get_param(ctx->event, "ID_FS_LABEL");
390         if (param)
391                 dev->label = talloc_strdup(dev, param);
392
393         rc = mount_device(dev);
394         if (rc) {
395                 talloc_free(ctx);
396                 return 0;
397         }
398
399         /* run the parsers. This will populate the ctx's boot_option list. */
400         iterate_parsers(ctx, CONF_METHOD_LOCAL_FILE);
401
402         /* add discovered stuff to the handler */
403         context_commit(handler, ctx);
404
405         talloc_free(ctx);
406
407         return 0;
408 }
409
410 static int handle_remove_udev_event(struct device_handler *handler,
411                 struct event *event)
412 {
413         struct discover_device *dev;
414
415         dev = find_device(handler, event->device);
416         if (!dev)
417                 return 0;
418
419         /* remove device from handler device array */
420         device_handler_remove(handler, dev);
421
422         return 0;
423 }
424
425 static int handle_add_user_event(struct device_handler *handler,
426                 struct event *event)
427 {
428         struct discover_context *ctx;
429         struct discover_device *dev;
430         int rc;
431
432         assert(event->device);
433
434         ctx = talloc(handler, struct discover_context);
435         ctx->event = event;
436         list_init(&ctx->boot_options);
437
438         dev = discover_device_create(handler, ctx, event);
439         ctx->device = dev;
440
441         rc = parse_user_event(ctx, event);
442
443         if (!rc)
444                 context_commit(handler, ctx);
445
446         return rc;
447 }
448
449 static int handle_remove_user_event(struct device_handler *handler,
450                 struct event *event)
451 {
452         struct discover_device *dev = find_device(handler, event->device);
453
454         if (!dev)
455                 return 0;
456
457         /* remove device from handler device array */
458         device_handler_remove(handler, dev);
459
460         return 0;
461 }
462
463 static enum conf_method parse_conf_method(const char *str)
464 {
465
466         if (!strcasecmp(str, "dhcp")) {
467                 return CONF_METHOD_DHCP;
468         }
469         return CONF_METHOD_UNKNOWN;
470 }
471
472 static int handle_conf_user_event(struct device_handler *handler,
473                 struct event *event)
474 {
475         struct discover_context *ctx;
476         struct discover_device *dev;
477         enum conf_method method;
478         const char *val;
479
480         ctx = talloc(handler, struct discover_context);
481         ctx->event = event;
482         list_init(&ctx->boot_options);
483
484         val = event_get_param(event, "url");
485         if (!val) {
486                 talloc_free(ctx);
487                 return 0;
488         }
489
490         ctx->conf_url = pb_url_parse(ctx, val);
491         if (!ctx->conf_url) {
492                 talloc_free(ctx);
493                 return 0;
494         }
495
496         val = event_get_param(event, "method");
497         if (!val) {
498                 talloc_free(ctx);
499                 return 0;
500         }
501
502         method = parse_conf_method(val);
503         if (method == CONF_METHOD_UNKNOWN) {
504                 talloc_free(ctx);
505                 return 0;
506         }
507
508         dev = discover_device_create(handler, ctx, event);
509         ctx->device = dev;
510
511         iterate_parsers(ctx, method);
512
513         context_commit(handler, ctx);
514
515         return 0;
516 }
517
518 typedef int (*event_handler)(struct device_handler *, struct event *);
519
520 static event_handler handlers[EVENT_TYPE_MAX][EVENT_ACTION_MAX] = {
521         [EVENT_TYPE_UDEV] = {
522                 [EVENT_ACTION_ADD]      = handle_add_udev_event,
523                 [EVENT_ACTION_REMOVE]   = handle_remove_udev_event,
524         },
525         [EVENT_TYPE_USER] = {
526                 [EVENT_ACTION_ADD]      = handle_add_user_event,
527                 [EVENT_ACTION_REMOVE]   = handle_remove_user_event,
528                 [EVENT_ACTION_CONF]     = handle_conf_user_event,
529         }
530 };
531
532 int device_handler_event(struct device_handler *handler,
533                 struct event *event)
534 {
535         if (event->type >= EVENT_TYPE_MAX ||
536                         event->action >= EVENT_ACTION_MAX ||
537                         !handlers[event->type][event->action]) {
538                 pb_log("%s unknown type/action: %d/%d\n", __func__,
539                                 event->type, event->action);
540                 return 0;
541         }
542
543         return handlers[event->type][event->action](handler, event);
544 }
545
546 struct device_handler *device_handler_init(struct discover_server *server,
547                 int dry_run)
548 {
549         struct device_handler *handler;
550
551         handler = talloc(NULL, struct device_handler);
552         handler->devices = NULL;
553         handler->n_devices = 0;
554         handler->server = server;
555         handler->dry_run = dry_run;
556         list_init(&handler->unresolved_boot_options);
557
558         /* set up our mount point base */
559         pb_mkdir_recursive(mount_base());
560
561         parser_init();
562
563         return handler;
564 }
565
566 void device_handler_destroy(struct device_handler *handler)
567 {
568         talloc_free(handler);
569 }
570
571 static int device_match_path(struct discover_device *dev, const char *path)
572 {
573         return !strcmp(dev->device_path, path);
574 }
575
576 static int device_match_uuid(struct discover_device *dev, const char *uuid)
577 {
578         return dev->uuid && !strcmp(dev->uuid, uuid);
579 }
580
581 static int device_match_label(struct discover_device *dev, const char *label)
582 {
583         return dev->label && !strcmp(dev->label, label);
584 }
585
586 static int device_match_id(struct discover_device *dev, const char *id)
587 {
588         return !strcmp(dev->device->id, id);
589 }
590
591 static struct discover_device *device_lookup(
592                 struct device_handler *device_handler,
593                 int (match_fn)(struct discover_device *, const char *),
594                 const char *str)
595 {
596         struct discover_device *dev;
597         unsigned int i;
598
599         if (!str)
600                 return NULL;
601
602         for (i = 0; i < device_handler->n_devices; i++) {
603                 dev = device_handler->devices[i];
604
605                 if (match_fn(dev, str))
606                         return dev;
607         }
608
609         return NULL;
610 }
611
612 struct discover_device *device_lookup_by_name(struct device_handler *handler,
613                 const char *name)
614 {
615         struct discover_device *dev;
616         char *path;
617
618         if (strncmp(name, "/dev/", strlen("/dev/")))
619                 path = talloc_asprintf(NULL, "/dev/%s", name);
620         else
621                 path = talloc_strdup(NULL, name);
622
623         dev = device_lookup_by_path(handler, path);
624
625         talloc_free(path);
626
627         return dev;
628 }
629
630 struct discover_device *device_lookup_by_path(
631                 struct device_handler *device_handler,
632                 const char *path)
633 {
634         return device_lookup(device_handler, device_match_path, path);
635 }
636
637 struct discover_device *device_lookup_by_uuid(
638                 struct device_handler *device_handler,
639                 const char *uuid)
640 {
641         return device_lookup(device_handler, device_match_uuid, uuid);
642 }
643
644 struct discover_device *device_lookup_by_label(
645                 struct device_handler *device_handler,
646                 const char *label)
647 {
648         return device_lookup(device_handler, device_match_label, label);
649 }
650
651 struct discover_device *device_lookup_by_id(
652                 struct device_handler *device_handler,
653                 const char *id)
654 {
655         return device_lookup(device_handler, device_match_id, id);
656 }
657
658 static struct discover_boot_option *find_boot_option_by_id(
659                 struct device_handler *handler, const char *id)
660 {
661         unsigned int i;
662
663         for (i = 0; i < handler->n_devices; i++) {
664                 struct discover_device *dev = handler->devices[i];
665                 struct discover_boot_option *opt;
666
667                 list_for_each_entry(&dev->boot_options, opt, list)
668                         if (!strcmp(opt->option->id, id))
669                                 return opt;
670         }
671
672         return NULL;
673 }
674
675 static void boot_status(void *arg, struct boot_status *status)
676 {
677         struct device_handler *handler = arg;
678
679         discover_server_notify_boot_status(handler->server, status);
680 }
681
682 void device_handler_boot(struct device_handler *handler,
683                 struct boot_command *cmd)
684 {
685         struct discover_boot_option *opt;
686
687         opt = find_boot_option_by_id(handler, cmd->option_id);
688
689         boot(handler, opt, cmd, handler->dry_run, boot_status, handler);
690 }