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