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