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