discover: Add configuration methods
[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 typedef int (*event_handler)(struct device_handler *, struct event *);
455
456 static event_handler handlers[EVENT_TYPE_MAX][EVENT_ACTION_MAX] = {
457         [EVENT_TYPE_UDEV] = {
458                 [EVENT_ACTION_ADD]      = handle_add_udev_event,
459                 [EVENT_ACTION_REMOVE]   = handle_remove_udev_event,
460         },
461         [EVENT_TYPE_USER] = {
462                 [EVENT_ACTION_ADD]      = handle_add_user_event,
463                 [EVENT_ACTION_REMOVE]   = handle_remove_user_event,
464         }
465 };
466
467 int device_handler_event(struct device_handler *handler,
468                 struct event *event)
469 {
470         if (event->type >= EVENT_TYPE_MAX ||
471                         event->action >= EVENT_ACTION_MAX ||
472                         !handlers[event->type][event->action]) {
473                 pb_log("%s unknown type/action: %d/%d\n", __func__,
474                                 event->type, event->action);
475                 return 0;
476         }
477
478         return handlers[event->type][event->action](handler, event);
479 }
480
481 struct device_handler *device_handler_init(struct discover_server *server,
482                 int dry_run)
483 {
484         struct device_handler *handler;
485
486         handler = talloc(NULL, struct device_handler);
487         handler->devices = NULL;
488         handler->n_devices = 0;
489         handler->server = server;
490         handler->dry_run = dry_run;
491         list_init(&handler->unresolved_boot_options);
492
493         /* set up our mount point base */
494         pb_mkdir_recursive(mount_base());
495
496         parser_init();
497
498         return handler;
499 }
500
501 void device_handler_destroy(struct device_handler *handler)
502 {
503         talloc_free(handler);
504 }
505
506 static int device_match_path(struct discover_device *dev, const char *path)
507 {
508         return !strcmp(dev->device_path, path);
509 }
510
511 static int device_match_uuid(struct discover_device *dev, const char *uuid)
512 {
513         return dev->uuid && !strcmp(dev->uuid, uuid);
514 }
515
516 static int device_match_label(struct discover_device *dev, const char *label)
517 {
518         return dev->label && !strcmp(dev->label, label);
519 }
520
521 static int device_match_id(struct discover_device *dev, const char *id)
522 {
523         return !strcmp(dev->device->id, id);
524 }
525
526 static struct discover_device *device_lookup(
527                 struct device_handler *device_handler,
528                 int (match_fn)(struct discover_device *, const char *),
529                 const char *str)
530 {
531         struct discover_device *dev;
532         unsigned int i;
533
534         if (!str)
535                 return NULL;
536
537         for (i = 0; i < device_handler->n_devices; i++) {
538                 dev = device_handler->devices[i];
539
540                 if (match_fn(dev, str))
541                         return dev;
542         }
543
544         return NULL;
545 }
546
547 struct discover_device *device_lookup_by_name(struct device_handler *handler,
548                 const char *name)
549 {
550         struct discover_device *dev;
551         char *path;
552
553         if (strncmp(name, "/dev/", strlen("/dev/")))
554                 path = talloc_asprintf(NULL, "/dev/%s", name);
555         else
556                 path = talloc_strdup(NULL, name);
557
558         dev = device_lookup_by_path(handler, path);
559
560         talloc_free(path);
561
562         return dev;
563 }
564
565 struct discover_device *device_lookup_by_path(
566                 struct device_handler *device_handler,
567                 const char *path)
568 {
569         return device_lookup(device_handler, device_match_path, path);
570 }
571
572 struct discover_device *device_lookup_by_uuid(
573                 struct device_handler *device_handler,
574                 const char *uuid)
575 {
576         return device_lookup(device_handler, device_match_uuid, uuid);
577 }
578
579 struct discover_device *device_lookup_by_label(
580                 struct device_handler *device_handler,
581                 const char *label)
582 {
583         return device_lookup(device_handler, device_match_label, label);
584 }
585
586 struct discover_device *device_lookup_by_id(
587                 struct device_handler *device_handler,
588                 const char *id)
589 {
590         return device_lookup(device_handler, device_match_id, id);
591 }
592
593 static struct discover_boot_option *find_boot_option_by_id(
594                 struct device_handler *handler, const char *id)
595 {
596         unsigned int i;
597
598         for (i = 0; i < handler->n_devices; i++) {
599                 struct discover_device *dev = handler->devices[i];
600                 struct discover_boot_option *opt;
601
602                 list_for_each_entry(&dev->boot_options, opt, list)
603                         if (!strcmp(opt->option->id, id))
604                                 return opt;
605         }
606
607         return NULL;
608 }
609
610 void device_handler_boot(struct device_handler *handler,
611                 struct boot_command *cmd)
612 {
613         struct discover_boot_option *opt;
614
615         opt = find_boot_option_by_id(handler, cmd->option_id);
616
617         boot(handler, opt, cmd, handler->dry_run);
618 }