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