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