lib/url: Move URL-handling code to lib
[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
23 struct device_handler {
24         struct discover_server *server;
25
26         struct device **devices;
27         unsigned int n_devices;
28
29         struct list contexts;
30 };
31
32 /**
33  * device_handler_add - Add a device to the handler device array.
34  */
35
36 static void device_handler_add(struct device_handler *handler,
37         struct device *device)
38 {
39         handler->n_devices++;
40         handler->devices = talloc_realloc(handler, handler->devices,
41                 struct device *, handler->n_devices);
42         handler->devices[handler->n_devices - 1] = device;
43 }
44
45 /**
46  * device_handler_remove - Remove a device from the handler device array.
47  */
48
49 static void device_handler_remove(struct device_handler *handler,
50         struct device *device)
51 {
52         unsigned int i;
53
54         for (i = 0; i < handler->n_devices; i++)
55                 if (handler->devices[i] == device)
56                         break;
57
58         if (i == handler->n_devices) {
59                 assert(0 && "unknown device");
60                 return;
61         }
62
63         handler->n_devices--;
64         memmove(&handler->devices[i], &handler->devices[i + 1],
65                 (handler->n_devices - i) * sizeof(handler->devices[0]));
66         handler->devices = talloc_realloc(handler, handler->devices,
67                 struct device *, handler->n_devices);
68 }
69
70 /**
71  * device_handler_find - Find a handler device by id.
72  */
73
74 static struct device *device_handler_find(struct device_handler *handler,
75         const char *id)
76 {
77         unsigned int i;
78
79         assert(id);
80
81         for (i = 0; i < handler->n_devices; i++)
82                 if (handler->devices[i]->id
83                         && streq(handler->devices[i]->id, id))
84                         return handler->devices[i];
85
86         pb_log("%s: unknown device: %s\n", __func__, id);
87         return NULL;
88 }
89
90 /**
91  * device_handler_get_device_count - Get the count of current handler devices.
92  */
93
94 int device_handler_get_device_count(const struct device_handler *handler)
95 {
96         return handler->n_devices;
97 }
98
99 /**
100  * device_handler_get_device - Get a handler device by index.
101  */
102
103 const struct device *device_handler_get_device(
104         const struct device_handler *handler, unsigned int index)
105 {
106         if (index >= handler->n_devices) {
107                 assert(0 && "bad index");
108                 return NULL;
109         }
110
111         return handler->devices[index];
112 }
113
114 static void setup_device_links(struct discover_context *ctx)
115 {
116         struct link {
117                 char *env, *dir;
118         } *link, links[] = {
119                 {
120                         .env = "ID_FS_UUID",
121                         .dir = "disk/by-uuid"
122                 },
123                 {
124                         .env = "ID_FS_LABEL",
125                         .dir = "disk/by-label"
126                 },
127                 {
128                         .env = NULL
129                 }
130         };
131
132         for (link = links; link->env; link++) {
133                 char *enc, *dir, *path;
134                 const char *value;
135
136                 value = event_get_param(ctx->event, link->env);
137                 if (!value || !*value)
138                         continue;
139
140                 enc = encode_label(ctx, value);
141                 dir = join_paths(ctx, mount_base(), link->dir);
142                 path = join_paths(ctx, dir, value);
143
144                 if (!pb_mkdir_recursive(dir)) {
145                         unlink(path);
146                         if (symlink(ctx->mount_path, path)) {
147                                 pb_log("symlink(%s,%s): %s\n",
148                                                 ctx->mount_path, path,
149                                                 strerror(errno));
150                                 talloc_free(path);
151                         } else {
152                                 int i = ctx->n_links++;
153                                 ctx->links = talloc_realloc(ctx,
154                                                 ctx->links, char *,
155                                                 ctx->n_links);
156                                 ctx->links[i] = path;
157                         }
158
159                 }
160
161                 talloc_free(dir);
162                 talloc_free(enc);
163         }
164 }
165
166 static void remove_device_links(struct discover_context *ctx)
167 {
168         int i;
169
170         for (i = 0; i < ctx->n_links; i++)
171                 unlink(ctx->links[i]);
172 }
173
174 static int mount_device(struct discover_context *ctx)
175 {
176         const char *mountpoint;
177         const char *argv[6];
178
179         if (!ctx->mount_path) {
180                 mountpoint = mountpoint_for_device(ctx->device_path);
181                 ctx->mount_path = talloc_strdup(ctx, mountpoint);
182         }
183
184         if (pb_mkdir_recursive(ctx->mount_path))
185                 pb_log("couldn't create mount directory %s: %s\n",
186                                 ctx->mount_path, strerror(errno));
187
188         argv[0] = pb_system_apps.mount;
189         argv[1] = ctx->device_path;
190         argv[2] = ctx->mount_path;
191         argv[3] = "-o";
192         argv[4] = "ro";
193         argv[5] = NULL;
194
195         if (pb_run_cmd(argv, 1, 0)) {
196
197                 /* Retry mount without ro option. */
198
199                 argv[0] = pb_system_apps.mount;
200                 argv[1] = ctx->device_path;
201                 argv[2] = ctx->mount_path;
202                 argv[3] = NULL;
203
204                 if (pb_run_cmd(argv, 1, 0))
205                         goto out_rmdir;
206         }
207
208         setup_device_links(ctx);
209         return 0;
210
211 out_rmdir:
212         pb_rmdir_recursive(mount_base(), ctx->mount_path);
213         return -1;
214 }
215
216 static int umount_device(struct discover_context *ctx)
217 {
218         int status;
219         pid_t pid;
220
221         remove_device_links(ctx);
222
223         pid = fork();
224         if (pid == -1) {
225                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
226                 return -1;
227         }
228
229         if (pid == 0) {
230                 execl(pb_system_apps.umount, pb_system_apps.umount,
231                                                 ctx->mount_path, NULL);
232                 exit(EXIT_FAILURE);
233         }
234
235         if (waitpid(pid, &status, 0) == -1) {
236                 pb_log("%s: waitpid failed: %s\n", __func__,
237                                 strerror(errno));
238                 return -1;
239         }
240
241         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
242                 return -1;
243
244         pb_rmdir_recursive(mount_base(), ctx->mount_path);
245
246         return 0;
247 }
248
249 static struct discover_context *find_context(struct device_handler *handler,
250                 const char *id)
251 {
252         struct discover_context *ctx;
253
254         list_for_each_entry(&handler->contexts, ctx, list) {
255                 if (!strcmp(ctx->id, id))
256                         return ctx;
257         }
258
259         return NULL;
260 }
261
262 static int destroy_context(void *arg)
263 {
264         struct discover_context *ctx = arg;
265
266         list_remove(&ctx->list);
267         umount_device(ctx);
268
269         return 0;
270 }
271
272 static int handle_add_udev_event(struct device_handler *handler,
273                 struct event *event)
274 {
275         struct discover_context *ctx;
276         const char *devname;
277         int rc;
278
279         /* create our context */
280         ctx = talloc(handler, struct discover_context);
281         ctx->event = event;
282         ctx->mount_path = NULL;
283         ctx->links = NULL;
284         ctx->n_links = 0;
285
286         ctx->id = talloc_strdup(ctx, event->device);
287
288         devname = event_get_param(ctx->event, "DEVNAME");
289         assert(devname);
290         ctx->device_path = talloc_strdup(ctx, devname);
291
292         rc = mount_device(ctx);
293         if (rc) {
294                 talloc_free(ctx);
295                 return 0;
296         }
297
298         list_add(&handler->contexts, &ctx->list);
299         talloc_set_destructor(ctx, destroy_context);
300
301         /* set up the top-level device */
302         ctx->device = talloc_zero(ctx, struct device);
303         ctx->device->id = talloc_strdup(ctx->device, ctx->id);
304         list_init(&ctx->device->boot_options);
305
306         /* run the parsers */
307         iterate_parsers(ctx);
308
309         /* add device to handler device array */
310         device_handler_add(handler, ctx->device);
311
312         discover_server_notify_add(handler->server, ctx->device);
313
314         return 0;
315 }
316
317 static int handle_remove_udev_event(struct device_handler *handler,
318                 struct event *event)
319 {
320         struct discover_context *ctx;
321
322         ctx = find_context(handler, event->device);
323         if (!ctx)
324                 return 0;
325
326         discover_server_notify_remove(handler->server, ctx->device);
327
328         /* remove device from handler device array */
329         device_handler_remove(handler, ctx->device);
330
331         talloc_free(ctx);
332
333         return 0;
334 }
335
336 static int handle_add_user_event(struct device_handler *handler,
337                 struct event *event)
338 {
339         struct device *device;
340
341         assert(event->device);
342
343         device = talloc_zero(handler, struct device);
344
345         if (!device)
346                 goto fail;
347
348         device->id = talloc_strdup(device, event->device);
349         list_init(&device->boot_options);
350
351         parse_user_event(device, event);
352
353         discover_server_notify_add(handler->server, device);
354
355         /* add device to handler device array */
356         device_handler_add(handler, device);
357
358         return 0;
359
360 fail:
361         talloc_free(device);
362         return 0;
363 }
364
365 static int handle_remove_user_event(struct device_handler *handler,
366                 struct event *event)
367 {
368         struct device *device = device_handler_find(handler, event->device);
369
370         if (!device)
371                 return 0;
372
373         discover_server_notify_remove(handler->server, device);
374
375         /* remove device from handler device array */
376         device_handler_remove(handler, device);
377
378         talloc_free(device);
379         return 0;
380 }
381
382 typedef int (*event_handler)(struct device_handler *, struct event *);
383
384 static event_handler handlers[EVENT_TYPE_MAX][EVENT_ACTION_MAX] = {
385         [EVENT_TYPE_UDEV] = {
386                 [EVENT_ACTION_ADD]      = handle_add_udev_event,
387                 [EVENT_ACTION_REMOVE]   = handle_remove_udev_event,
388         },
389         [EVENT_TYPE_USER] = {
390                 [EVENT_ACTION_ADD]      = handle_add_user_event,
391                 [EVENT_ACTION_REMOVE]   = handle_remove_user_event,
392         }
393 };
394
395 int device_handler_event(struct device_handler *handler,
396                 struct event *event)
397 {
398         if (event->type >= EVENT_TYPE_MAX ||
399                         event->action >= EVENT_ACTION_MAX ||
400                         !handlers[event->type][event->action]) {
401                 pb_log("%s unknown type/action: %d/%d\n", __func__,
402                                 event->type, event->action);
403                 return 0;
404         }
405
406         return handlers[event->type][event->action](handler, event);
407 }
408
409 struct device_handler *device_handler_init(struct discover_server *server)
410 {
411         struct device_handler *handler;
412
413         handler = talloc(NULL, struct device_handler);
414         handler->devices = NULL;
415         handler->n_devices = 0;
416         handler->server = server;
417
418         list_init(&handler->contexts);
419
420         /* set up our mount point base */
421         pb_mkdir_recursive(mount_base());
422
423         parser_init();
424
425         return handler;
426 }
427
428 void device_handler_destroy(struct device_handler *handler)
429 {
430         talloc_free(handler);
431 }