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