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