0783181147edab9f0f8c6b7de9fa2ea35c516cb8
[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, 1)) {
205
206                 /* Retry mount without ro option. */
207
208                 argv[0] = MOUNT_BIN;
209                 argv[1] = ctx->device_path;
210                 argv[2] = ctx->mount_path;
211                 argv[3] = NULL;
212
213                 if (pb_run_cmd(argv, 1))
214                         goto out_rmdir;
215         }
216
217         setup_device_links(ctx);
218         return 0;
219
220 out_rmdir:
221         pb_rmdir_recursive(mount_base(), ctx->mount_path);
222         return -1;
223 }
224
225 static int umount_device(struct discover_context *ctx)
226 {
227         int status;
228         pid_t pid;
229
230         remove_device_links(ctx);
231
232         pid = fork();
233         if (pid == -1) {
234                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
235                 return -1;
236         }
237
238         if (pid == 0) {
239                 execl(UMOUNT_BIN, UMOUNT_BIN, ctx->mount_path, NULL);
240                 exit(EXIT_FAILURE);
241         }
242
243         if (waitpid(pid, &status, 0) == -1) {
244                 pb_log("%s: waitpid failed: %s\n", __func__,
245                                 strerror(errno));
246                 return -1;
247         }
248
249         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
250                 return -1;
251
252         pb_rmdir_recursive(mount_base(), ctx->mount_path);
253
254         return 0;
255 }
256
257 static struct discover_context *find_context(struct device_handler *handler,
258                 const char *id)
259 {
260         struct discover_context *ctx;
261
262         list_for_each_entry(&handler->contexts, ctx, list) {
263                 if (!strcmp(ctx->id, id))
264                         return ctx;
265         }
266
267         return NULL;
268 }
269
270 static int destroy_context(void *arg)
271 {
272         struct discover_context *ctx = arg;
273
274         list_remove(&ctx->list);
275         umount_device(ctx);
276
277         return 0;
278 }
279
280 static int handle_add_udev_event(struct device_handler *handler,
281                 struct event *event)
282 {
283         struct discover_context *ctx;
284         const char *devname;
285         int rc;
286
287         /* create our context */
288         ctx = talloc(handler, struct discover_context);
289         ctx->event = event;
290         ctx->mount_path = NULL;
291         ctx->links = NULL;
292         ctx->n_links = 0;
293
294         ctx->id = talloc_strdup(ctx, event->device);
295
296         devname = event_get_param(ctx->event, "DEVNAME");
297         if (!devname) {
298                 pb_log("no devname for %s?\n", event->device);
299                 return 0;
300         }
301
302         ctx->device_path = talloc_strdup(ctx, devname);
303
304         rc = mount_device(ctx);
305         if (rc) {
306                 talloc_free(ctx);
307                 return 0;
308         }
309
310         list_add(&handler->contexts, &ctx->list);
311         talloc_set_destructor(ctx, destroy_context);
312
313         /* set up the top-level device */
314         ctx->device = talloc_zero(ctx, struct device);
315         ctx->device->id = talloc_strdup(ctx->device, ctx->id);
316         list_init(&ctx->device->boot_options);
317
318         /* run the parsers */
319         iterate_parsers(ctx);
320
321         /* add device to handler device array */
322         device_handler_add(handler, ctx->device);
323
324         discover_server_notify_add(handler->server, ctx->device);
325
326         return 0;
327 }
328
329 static int handle_remove_udev_event(struct device_handler *handler,
330                 struct event *event)
331 {
332         struct discover_context *ctx;
333
334         ctx = find_context(handler, event->device);
335         if (!ctx)
336                 return 0;
337
338         discover_server_notify_remove(handler->server, ctx->device);
339
340         /* remove device from handler device array */
341         device_handler_remove(handler, ctx->device);
342
343         talloc_free(ctx);
344
345         return 0;
346 }
347
348 static int handle_add_user_event(struct device_handler *handler,
349                 struct event *event)
350 {
351         struct device *device;
352
353         assert(event->device);
354
355         device = talloc_zero(handler, struct device);
356
357         if (!device)
358                 goto fail;
359
360         device->id = talloc_strdup(device, event->device);
361         list_init(&device->boot_options);
362
363         parse_user_event(device, event);
364
365         discover_server_notify_add(handler->server, device);
366
367         /* add device to handler device array */
368         device_handler_add(handler, device);
369
370         return 0;
371
372 fail:
373         talloc_free(device);
374         return 0;
375 }
376
377 static int handle_remove_user_event(struct device_handler *handler,
378                 struct event *event)
379 {
380         struct device *device = device_handler_find(handler, event->device);
381
382         if (!device)
383                 return 0;
384
385         discover_server_notify_remove(handler->server, device);
386
387         /* remove device from handler device array */
388         device_handler_remove(handler, device);
389
390         talloc_free(device);
391         return 0;
392 }
393
394 int device_handler_event(struct device_handler *handler,
395                 struct event *event)
396 {
397         int rc = 0;
398
399         switch (event->type) {
400         case EVENT_TYPE_UDEV:
401                 switch (event->action) {
402                 case EVENT_ACTION_ADD:
403                         rc = handle_add_udev_event(handler, event);
404                         break;
405                 case EVENT_ACTION_REMOVE:
406                         rc = handle_remove_udev_event(handler, event);
407                         break;
408                 default:
409                         pb_log("%s unknown action: %d\n", __func__,
410                                 event->action);
411                         break;
412                 }
413                 break;
414         case EVENT_TYPE_USER:
415                 switch (event->action) {
416                 case EVENT_ACTION_ADD:
417                         rc = handle_add_user_event(handler, event);
418                         break;
419                 case EVENT_ACTION_REMOVE:
420                         rc = handle_remove_user_event(handler, event);
421                         break;
422                 default:
423                         pb_log("%s unknown action: %d\n", __func__,
424                                 event->action);
425                         break;
426                 }
427                 break;
428         default:
429                 pb_log("%s unknown type: %d\n", __func__, event->type);
430                 break;
431         }
432
433         return rc;
434 }
435
436 struct device_handler *device_handler_init(struct discover_server *server)
437 {
438         struct device_handler *handler;
439
440         handler = talloc(NULL, struct device_handler);
441         handler->devices = NULL;
442         handler->n_devices = 0;
443         handler->server = server;
444
445         list_init(&handler->contexts);
446
447         /* set up our mount point base */
448         pb_mkdir_recursive(mount_base());
449
450         parser_init();
451
452         return handler;
453 }
454
455 void device_handler_destroy(struct device_handler *handler)
456 {
457         talloc_free(handler);
458 }