discover: Add uuid and label parameters to persistent device data
[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         unsigned int i, existing_device;
41         struct boot_option *opt, *tmp;
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->device->boot_options, &opt->list);
67                 dev->device->n_options++;
68                 discover_server_notify_boot_option_add(handler->server, opt);
69         }
70 }
71
72 void discover_context_add_boot_option(struct discover_context *ctx,
73                 struct boot_option *boot_option)
74 {
75         list_add(&ctx->boot_options, &boot_option->list);
76         talloc_steal(ctx, boot_option);
77 }
78
79 /**
80  * device_handler_remove - Remove a device from the handler device array.
81  */
82
83 static void device_handler_remove(struct device_handler *handler,
84         struct discover_device *device)
85 {
86         unsigned int i;
87
88         for (i = 0; i < handler->n_devices; i++)
89                 if (handler->devices[i] == device)
90                         break;
91
92         if (i == handler->n_devices) {
93                 assert(0 && "unknown device");
94                 return;
95         }
96
97         handler->n_devices--;
98         memmove(&handler->devices[i], &handler->devices[i + 1],
99                 (handler->n_devices - i) * sizeof(handler->devices[0]));
100         handler->devices = talloc_realloc(handler, handler->devices,
101                 struct discover_device *, handler->n_devices);
102
103         discover_server_notify_device_remove(handler->server, device->device);
104
105         talloc_free(device);
106 }
107
108 /**
109  * device_handler_get_device_count - Get the count of current handler devices.
110  */
111
112 int device_handler_get_device_count(const struct device_handler *handler)
113 {
114         return handler->n_devices;
115 }
116
117 /**
118  * device_handler_get_device - Get a handler device by index.
119  */
120
121 const struct device *device_handler_get_device(
122         const struct device_handler *handler, unsigned int index)
123 {
124         if (index >= handler->n_devices) {
125                 assert(0 && "bad index");
126                 return NULL;
127         }
128
129         return handler->devices[index]->device;
130 }
131
132 static void setup_device_links(struct discover_device *dev)
133 {
134         struct link {
135                 const char *dir, *val;
136         } *link, links[] = {
137                 {
138                         .dir = "disk/by-uuid",
139                         .val = dev->uuid,
140                 },
141                 {
142                         .dir = "disk/by-label",
143                         .val = dev->label,
144                 },
145                 {
146                         .dir = NULL
147                 }
148         };
149
150         for (link = links; link->dir; link++) {
151                 char *enc, *dir, *path;
152
153                 if (!link->val || !*link->val)
154                         continue;
155
156                 enc = encode_label(dev, link->val);
157                 dir = join_paths(dev, mount_base(), link->dir);
158                 path = join_paths(dev, dir, enc);
159
160                 if (!pb_mkdir_recursive(dir)) {
161                         unlink(path);
162                         if (symlink(dev->mount_path, path)) {
163                                 pb_log("symlink(%s,%s): %s\n",
164                                                 dev->mount_path, path,
165                                                 strerror(errno));
166                                 talloc_free(path);
167                         } else {
168                                 int i = dev->n_links++;
169                                 dev->links = talloc_realloc(dev,
170                                                 dev->links, char *,
171                                                 dev->n_links);
172                                 dev->links[i] = path;
173                         }
174
175                 }
176
177                 talloc_free(dir);
178                 talloc_free(enc);
179         }
180 }
181
182 static void remove_device_links(struct discover_device *dev)
183 {
184         int i;
185
186         for (i = 0; i < dev->n_links; i++)
187                 unlink(dev->links[i]);
188 }
189
190 static int mount_device(struct discover_device *dev)
191 {
192         const char *mountpoint;
193         const char *argv[6];
194
195         if (!dev->mount_path) {
196                 mountpoint = mountpoint_for_device(dev->device_path);
197                 dev->mount_path = talloc_strdup(dev, mountpoint);
198         }
199
200         if (pb_mkdir_recursive(dev->mount_path))
201                 pb_log("couldn't create mount directory %s: %s\n",
202                                 dev->mount_path, strerror(errno));
203
204         argv[0] = pb_system_apps.mount;
205         argv[1] = dev->device_path;
206         argv[2] = dev->mount_path;
207         argv[3] = "-o";
208         argv[4] = "ro";
209         argv[5] = NULL;
210
211         if (pb_run_cmd(argv, 1, 0)) {
212
213                 /* Retry mount without ro option. */
214
215                 argv[0] = pb_system_apps.mount;
216                 argv[1] = dev->device_path;
217                 argv[2] = dev->mount_path;
218                 argv[3] = NULL;
219
220                 if (pb_run_cmd(argv, 1, 0))
221                         goto out_rmdir;
222         }
223
224         setup_device_links(dev);
225         return 0;
226
227 out_rmdir:
228         pb_rmdir_recursive(mount_base(), dev->mount_path);
229         return -1;
230 }
231
232 static int umount_device(struct discover_device *dev)
233 {
234         int status;
235         pid_t pid;
236
237         remove_device_links(dev);
238
239         if (!dev->mount_path)
240                 return 0;
241
242         pid = fork();
243         if (pid == -1) {
244                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
245                 return -1;
246         }
247
248         if (pid == 0) {
249                 execl(pb_system_apps.umount, pb_system_apps.umount,
250                                                 dev->mount_path, NULL);
251                 exit(EXIT_FAILURE);
252         }
253
254         if (waitpid(pid, &status, 0) == -1) {
255                 pb_log("%s: waitpid failed: %s\n", __func__,
256                                 strerror(errno));
257                 return -1;
258         }
259
260         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
261                 return -1;
262
263         pb_rmdir_recursive(mount_base(), dev->mount_path);
264
265         return 0;
266 }
267
268 static struct discover_device *find_device(struct device_handler *handler,
269                 const char *id)
270 {
271         struct discover_device *dev;
272         unsigned int i;
273
274         for (i = 0; i < handler->n_devices; i++) {
275                 dev = handler->devices[i];
276                 if (!strcmp(dev->device->id, id))
277                         return dev;
278         }
279
280         return NULL;
281 }
282
283 static int destroy_device(void *arg)
284 {
285         struct discover_device *dev = arg;
286
287         umount_device(dev);
288
289         return 0;
290 }
291
292 static struct discover_device *discover_device_create(
293                 struct device_handler *handler,
294                 struct discover_context *ctx,
295                 struct event *event)
296 {
297         struct discover_device *dev;
298         const char *devname;
299
300         dev = find_device(handler, event->device);
301         if (dev)
302                 return dev;
303
304         dev = talloc_zero(ctx, struct discover_device);
305         dev->device = talloc_zero(dev, struct device);
306         list_init(&dev->device->boot_options);
307
308         devname = event_get_param(ctx->event, "DEVNAME");
309         if (devname)
310                 dev->device_path = talloc_strdup(dev, devname);
311
312         dev->device->id = talloc_strdup(dev, event->device);
313
314         talloc_set_destructor(dev, destroy_device);
315
316         return dev;
317 }
318
319 static int handle_add_udev_event(struct device_handler *handler,
320                 struct event *event)
321 {
322         struct discover_context *ctx;
323         struct discover_device *dev;
324         const char *param;
325         int rc;
326
327         /* create our context */
328         ctx = talloc(handler, struct discover_context);
329         ctx->event = event;
330         list_init(&ctx->boot_options);
331
332         /* create our top-level device */
333         dev = discover_device_create(handler, ctx, event);
334
335         ctx->device = dev;
336
337         /* try to parse UUID and labels */
338         param = event_get_param(ctx->event, "ID_FS_UUID");
339         if (param)
340                 dev->uuid = talloc_strdup(dev, param);
341
342         param = event_get_param(ctx->event, "ID_FS_LABEL");
343         if (param)
344                 dev->label = talloc_strdup(dev, param);
345
346         rc = mount_device(dev);
347         if (rc) {
348                 talloc_free(ctx);
349                 return 0;
350         }
351
352         /* run the parsers. This will populate the ctx's boot_option list. */
353         iterate_parsers(ctx);
354
355         /* add discovered stuff to the handler */
356         context_commit(handler, ctx);
357
358         talloc_free(ctx);
359
360         return 0;
361 }
362
363 static int handle_remove_udev_event(struct device_handler *handler,
364                 struct event *event)
365 {
366         struct discover_device *dev;
367
368         dev = find_device(handler, event->device);
369         if (!dev)
370                 return 0;
371
372         /* remove device from handler device array */
373         device_handler_remove(handler, dev);
374
375         return 0;
376 }
377
378 static int handle_add_user_event(struct device_handler *handler,
379                 struct event *event)
380 {
381         struct discover_context *ctx;
382         struct discover_device *dev;
383         int rc;
384
385         assert(event->device);
386
387         ctx = talloc(handler, struct discover_context);
388         ctx->event = event;
389         list_init(&ctx->boot_options);
390
391         dev = discover_device_create(handler, ctx, event);
392         ctx->device = dev;
393
394         rc = parse_user_event(ctx, event);
395
396         if (!rc)
397                 context_commit(handler, ctx);
398
399         return rc;
400 }
401
402 static int handle_remove_user_event(struct device_handler *handler,
403                 struct event *event)
404 {
405         struct discover_device *dev = find_device(handler, event->device);
406
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 typedef int (*event_handler)(struct device_handler *, struct event *);
417
418 static event_handler handlers[EVENT_TYPE_MAX][EVENT_ACTION_MAX] = {
419         [EVENT_TYPE_UDEV] = {
420                 [EVENT_ACTION_ADD]      = handle_add_udev_event,
421                 [EVENT_ACTION_REMOVE]   = handle_remove_udev_event,
422         },
423         [EVENT_TYPE_USER] = {
424                 [EVENT_ACTION_ADD]      = handle_add_user_event,
425                 [EVENT_ACTION_REMOVE]   = handle_remove_user_event,
426         }
427 };
428
429 int device_handler_event(struct device_handler *handler,
430                 struct event *event)
431 {
432         if (event->type >= EVENT_TYPE_MAX ||
433                         event->action >= EVENT_ACTION_MAX ||
434                         !handlers[event->type][event->action]) {
435                 pb_log("%s unknown type/action: %d/%d\n", __func__,
436                                 event->type, event->action);
437                 return 0;
438         }
439
440         return handlers[event->type][event->action](handler, event);
441 }
442
443 struct device_handler *device_handler_init(struct discover_server *server,
444                 int dry_run)
445 {
446         struct device_handler *handler;
447
448         handler = talloc(NULL, struct device_handler);
449         handler->devices = NULL;
450         handler->n_devices = 0;
451         handler->server = server;
452         handler->dry_run = dry_run;
453
454         /* set up our mount point base */
455         pb_mkdir_recursive(mount_base());
456
457         parser_init();
458
459         return handler;
460 }
461
462 void device_handler_destroy(struct device_handler *handler)
463 {
464         talloc_free(handler);
465 }
466
467 static struct boot_option *find_boot_option_by_id(
468                 struct device_handler *handler, const char *id)
469 {
470         unsigned int i;
471
472         for (i = 0; i < handler->n_devices; i++) {
473                 struct discover_device *dev = handler->devices[i];
474                 struct boot_option *opt;
475
476                 list_for_each_entry(&dev->device->boot_options, opt, list)
477                         if (!strcmp(opt->id, id))
478                                 return opt;
479         }
480
481         return NULL;
482 }
483
484 void device_handler_boot(struct device_handler *handler,
485                 struct boot_command *cmd)
486 {
487         struct boot_option *opt;
488
489         opt = find_boot_option_by_id(handler, cmd->option_id);
490
491         boot(handler, opt, cmd, handler->dry_run);
492 }