discover: Change parsers to emit resources rather than filenames
[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
16 #include "device-handler.h"
17 #include "discover-server.h"
18 #include "event.h"
19 #include "parser.h"
20 #include "udev.h"
21 #include "paths.h"
22 #include "boot.h"
23
24 struct device_handler {
25         struct discover_server  *server;
26         int                     dry_run;
27
28         struct discover_device  **devices;
29         unsigned int            n_devices;
30 };
31
32 /**
33  * context_commit - Commit a temporary discovery context to the handler,
34  * and notify the clients about any new options / devices
35  */
36 static void context_commit(struct device_handler *handler,
37                 struct discover_context *ctx)
38 {
39         struct discover_device *dev = ctx->device;
40         struct discover_boot_option *opt, *tmp;
41         unsigned int i, existing_device;
42
43         /* do we already have this device? */
44         for (i = 0; i < handler->n_devices; i++) {
45                 if (ctx->device == handler->devices[i]) {
46                         existing_device = 1;
47                         break;
48                 }
49         }
50
51         /* if not already present, add the device to the handler's array */
52         if (!existing_device) {
53                 handler->n_devices++;
54                 handler->devices = talloc_realloc(handler, handler->devices,
55                         struct discover_device *, handler->n_devices);
56                 handler->devices[handler->n_devices - 1] = dev;
57                 talloc_steal(handler, dev);
58
59                 discover_server_notify_device_add(handler->server, dev->device);
60         }
61
62
63         /* move boot options from the context to the device */
64         list_for_each_entry_safe(&ctx->boot_options, opt, tmp, list) {
65                 list_remove(&opt->list);
66                 list_add(&dev->boot_options, &opt->list);
67                 talloc_steal(dev, opt);
68                 discover_server_notify_boot_option_add(handler->server,
69                                                         opt->option);
70         }
71 }
72
73 void discover_context_add_boot_option(struct discover_context *ctx,
74                 struct discover_boot_option *boot_option)
75 {
76         list_add(&ctx->boot_options, &boot_option->list);
77         talloc_steal(ctx, boot_option);
78 }
79
80 /**
81  * device_handler_remove - Remove a device from the handler device array.
82  */
83
84 static void device_handler_remove(struct device_handler *handler,
85         struct discover_device *device)
86 {
87         unsigned int i;
88
89         for (i = 0; i < handler->n_devices; i++)
90                 if (handler->devices[i] == device)
91                         break;
92
93         if (i == handler->n_devices) {
94                 assert(0 && "unknown device");
95                 return;
96         }
97
98         handler->n_devices--;
99         memmove(&handler->devices[i], &handler->devices[i + 1],
100                 (handler->n_devices - i) * sizeof(handler->devices[0]));
101         handler->devices = talloc_realloc(handler, handler->devices,
102                 struct discover_device *, handler->n_devices);
103
104         discover_server_notify_device_remove(handler->server, device->device);
105
106         talloc_free(device);
107 }
108
109 /**
110  * device_handler_get_device_count - Get the count of current handler devices.
111  */
112
113 int device_handler_get_device_count(const struct device_handler *handler)
114 {
115         return handler->n_devices;
116 }
117
118 /**
119  * device_handler_get_device - Get a handler device by index.
120  */
121
122 const struct discover_device *device_handler_get_device(
123         const struct device_handler *handler, unsigned int index)
124 {
125         if (index >= handler->n_devices) {
126                 assert(0 && "bad index");
127                 return NULL;
128         }
129
130         return handler->devices[index];
131 }
132
133 static int mount_device(struct discover_device *dev)
134 {
135         const char *argv[6];
136
137         if (!dev->mount_path)
138                 dev->mount_path = join_paths(dev, mount_base(),
139                                                 dev->device_path);
140
141         if (pb_mkdir_recursive(dev->mount_path))
142                 pb_log("couldn't create mount directory %s: %s\n",
143                                 dev->mount_path, strerror(errno));
144
145         argv[0] = pb_system_apps.mount;
146         argv[1] = dev->device_path;
147         argv[2] = dev->mount_path;
148         argv[3] = "-o";
149         argv[4] = "ro";
150         argv[5] = NULL;
151
152         if (pb_run_cmd(argv, 1, 0)) {
153
154                 /* Retry mount without ro option. */
155
156                 argv[0] = pb_system_apps.mount;
157                 argv[1] = dev->device_path;
158                 argv[2] = dev->mount_path;
159                 argv[3] = NULL;
160
161                 if (pb_run_cmd(argv, 1, 0))
162                         goto out_rmdir;
163         }
164
165         return 0;
166
167 out_rmdir:
168         pb_rmdir_recursive(mount_base(), dev->mount_path);
169         return -1;
170 }
171
172 static int umount_device(struct discover_device *dev)
173 {
174         int status;
175         pid_t pid;
176
177         if (!dev->mount_path)
178                 return 0;
179
180         pid = fork();
181         if (pid == -1) {
182                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
183                 return -1;
184         }
185
186         if (pid == 0) {
187                 execl(pb_system_apps.umount, pb_system_apps.umount,
188                                                 dev->mount_path, NULL);
189                 exit(EXIT_FAILURE);
190         }
191
192         if (waitpid(pid, &status, 0) == -1) {
193                 pb_log("%s: waitpid failed: %s\n", __func__,
194                                 strerror(errno));
195                 return -1;
196         }
197
198         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
199                 return -1;
200
201         pb_rmdir_recursive(mount_base(), dev->mount_path);
202
203         return 0;
204 }
205
206 static struct discover_device *find_device(struct device_handler *handler,
207                 const char *id)
208 {
209         struct discover_device *dev;
210         unsigned int i;
211
212         for (i = 0; i < handler->n_devices; i++) {
213                 dev = handler->devices[i];
214                 if (!strcmp(dev->device->id, id))
215                         return dev;
216         }
217
218         return NULL;
219 }
220
221 static int destroy_device(void *arg)
222 {
223         struct discover_device *dev = arg;
224
225         umount_device(dev);
226
227         return 0;
228 }
229
230 static struct discover_device *discover_device_create(
231                 struct device_handler *handler,
232                 struct discover_context *ctx,
233                 struct event *event)
234 {
235         struct discover_device *dev;
236         const char *devname;
237
238         dev = find_device(handler, event->device);
239         if (dev)
240                 return dev;
241
242         dev = talloc_zero(ctx, struct discover_device);
243         dev->device = talloc_zero(dev, struct device);
244         list_init(&dev->boot_options);
245
246         devname = event_get_param(ctx->event, "DEVNAME");
247         if (devname)
248                 dev->device_path = talloc_strdup(dev, devname);
249
250         dev->device->id = talloc_strdup(dev, event->device);
251
252         talloc_set_destructor(dev, destroy_device);
253
254         return dev;
255 }
256
257 struct discover_boot_option *discover_boot_option_create(
258                 struct discover_context *ctx,
259                 struct discover_device *device)
260 {
261         struct discover_boot_option *opt;
262
263         opt = talloc_zero(ctx, struct discover_boot_option);
264         opt->option = talloc_zero(opt, struct boot_option);
265         opt->device = device;
266
267         return opt;
268 }
269
270
271 static int handle_add_udev_event(struct device_handler *handler,
272                 struct event *event)
273 {
274         struct discover_context *ctx;
275         struct discover_device *dev;
276         const char *param;
277         int rc;
278
279         /* create our context */
280         ctx = talloc(handler, struct discover_context);
281         ctx->event = event;
282         list_init(&ctx->boot_options);
283
284         /* create our top-level device */
285         dev = discover_device_create(handler, ctx, event);
286
287         ctx->device = dev;
288
289         /* try to parse UUID and labels */
290         param = event_get_param(ctx->event, "ID_FS_UUID");
291         if (param)
292                 dev->uuid = talloc_strdup(dev, param);
293
294         param = event_get_param(ctx->event, "ID_FS_LABEL");
295         if (param)
296                 dev->label = talloc_strdup(dev, param);
297
298         rc = mount_device(dev);
299         if (rc) {
300                 talloc_free(ctx);
301                 return 0;
302         }
303
304         /* run the parsers. This will populate the ctx's boot_option list. */
305         iterate_parsers(ctx);
306
307         /* add discovered stuff to the handler */
308         context_commit(handler, ctx);
309
310         talloc_free(ctx);
311
312         return 0;
313 }
314
315 static int handle_remove_udev_event(struct device_handler *handler,
316                 struct event *event)
317 {
318         struct discover_device *dev;
319
320         dev = find_device(handler, event->device);
321         if (!dev)
322                 return 0;
323
324         /* remove device from handler device array */
325         device_handler_remove(handler, dev);
326
327         return 0;
328 }
329
330 static int handle_add_user_event(struct device_handler *handler,
331                 struct event *event)
332 {
333         struct discover_context *ctx;
334         struct discover_device *dev;
335         int rc;
336
337         assert(event->device);
338
339         ctx = talloc(handler, struct discover_context);
340         ctx->event = event;
341         list_init(&ctx->boot_options);
342
343         dev = discover_device_create(handler, ctx, event);
344         ctx->device = dev;
345
346         rc = parse_user_event(ctx, event);
347
348         if (!rc)
349                 context_commit(handler, ctx);
350
351         return rc;
352 }
353
354 static int handle_remove_user_event(struct device_handler *handler,
355                 struct event *event)
356 {
357         struct discover_device *dev = find_device(handler, event->device);
358
359         if (!dev)
360                 return 0;
361
362         /* remove device from handler device array */
363         device_handler_remove(handler, dev);
364
365         return 0;
366 }
367
368 typedef int (*event_handler)(struct device_handler *, struct event *);
369
370 static event_handler handlers[EVENT_TYPE_MAX][EVENT_ACTION_MAX] = {
371         [EVENT_TYPE_UDEV] = {
372                 [EVENT_ACTION_ADD]      = handle_add_udev_event,
373                 [EVENT_ACTION_REMOVE]   = handle_remove_udev_event,
374         },
375         [EVENT_TYPE_USER] = {
376                 [EVENT_ACTION_ADD]      = handle_add_user_event,
377                 [EVENT_ACTION_REMOVE]   = handle_remove_user_event,
378         }
379 };
380
381 int device_handler_event(struct device_handler *handler,
382                 struct event *event)
383 {
384         if (event->type >= EVENT_TYPE_MAX ||
385                         event->action >= EVENT_ACTION_MAX ||
386                         !handlers[event->type][event->action]) {
387                 pb_log("%s unknown type/action: %d/%d\n", __func__,
388                                 event->type, event->action);
389                 return 0;
390         }
391
392         return handlers[event->type][event->action](handler, event);
393 }
394
395 struct device_handler *device_handler_init(struct discover_server *server,
396                 int dry_run)
397 {
398         struct device_handler *handler;
399
400         handler = talloc(NULL, struct device_handler);
401         handler->devices = NULL;
402         handler->n_devices = 0;
403         handler->server = server;
404         handler->dry_run = dry_run;
405
406         /* set up our mount point base */
407         pb_mkdir_recursive(mount_base());
408
409         parser_init();
410
411         return handler;
412 }
413
414 void device_handler_destroy(struct device_handler *handler)
415 {
416         talloc_free(handler);
417 }
418
419 static int device_match_path(struct discover_device *dev, const char *path)
420 {
421         return !strcmp(dev->device_path, path);
422 }
423
424 static int device_match_uuid(struct discover_device *dev, const char *uuid)
425 {
426         return dev->uuid && !strcmp(dev->uuid, uuid);
427 }
428
429 static int device_match_label(struct discover_device *dev, const char *label)
430 {
431         return dev->label && !strcmp(dev->label, label);
432 }
433
434 static int device_match_id(struct discover_device *dev, const char *id)
435 {
436         return !strcmp(dev->device->id, id);
437 }
438
439 static struct discover_device *device_lookup(
440                 struct device_handler *device_handler,
441                 int (match_fn)(struct discover_device *, const char *),
442                 const char *str)
443 {
444         struct discover_device *dev;
445         unsigned int i;
446
447         if (!str)
448                 return NULL;
449
450         for (i = 0; i < device_handler->n_devices; i++) {
451                 dev = device_handler->devices[i];
452
453                 if (match_fn(dev, str))
454                         return dev;
455         }
456
457         return NULL;
458 }
459
460 struct discover_device *device_lookup_by_name(struct device_handler *handler,
461                 const char *name)
462 {
463         struct discover_device *dev;
464         char *path;
465
466         if (strncmp(name, "/dev/", strlen("/dev/")))
467                 path = talloc_asprintf(NULL, "/dev/%s", name);
468         else
469                 path = talloc_strdup(NULL, name);
470
471         dev = device_lookup_by_path(handler, path);
472
473         talloc_free(path);
474
475         return dev;
476 }
477
478 struct discover_device *device_lookup_by_path(
479                 struct device_handler *device_handler,
480                 const char *path)
481 {
482         return device_lookup(device_handler, device_match_path, path);
483 }
484
485 struct discover_device *device_lookup_by_uuid(
486                 struct device_handler *device_handler,
487                 const char *uuid)
488 {
489         return device_lookup(device_handler, device_match_uuid, uuid);
490 }
491
492 struct discover_device *device_lookup_by_label(
493                 struct device_handler *device_handler,
494                 const char *label)
495 {
496         return device_lookup(device_handler, device_match_label, label);
497 }
498
499 struct discover_device *device_lookup_by_id(
500                 struct device_handler *device_handler,
501                 const char *id)
502 {
503         return device_lookup(device_handler, device_match_id, id);
504 }
505
506 static struct discover_boot_option *find_boot_option_by_id(
507                 struct device_handler *handler, const char *id)
508 {
509         unsigned int i;
510
511         for (i = 0; i < handler->n_devices; i++) {
512                 struct discover_device *dev = handler->devices[i];
513                 struct discover_boot_option *opt;
514
515                 list_for_each_entry(&dev->boot_options, opt, list)
516                         if (!strcmp(opt->option->id, id))
517                                 return opt;
518         }
519
520         return NULL;
521 }
522
523 void device_handler_boot(struct device_handler *handler,
524                 struct boot_command *cmd)
525 {
526         struct discover_boot_option *opt;
527
528         opt = find_boot_option_by_id(handler, cmd->option_id);
529
530         boot(handler, opt, cmd, handler->dry_run);
531 }