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