908409c6540ff52c71b3962d700486a38d309f76
[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                 goto out_rmdir;
206
207         setup_device_links(ctx);
208         return 0;
209
210 out_rmdir:
211         pb_rmdir_recursive(mount_base(), ctx->mount_path);
212         return -1;
213 }
214
215 static int umount_device(struct discover_context *ctx)
216 {
217         int status;
218         pid_t pid;
219
220         remove_device_links(ctx);
221
222         pid = fork();
223         if (pid == -1) {
224                 pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
225                 return -1;
226         }
227
228         if (pid == 0) {
229                 execl(UMOUNT_BIN, UMOUNT_BIN, ctx->mount_path, NULL);
230                 exit(EXIT_FAILURE);
231         }
232
233         if (waitpid(pid, &status, 0) == -1) {
234                 pb_log("%s: waitpid failed: %s\n", __func__,
235                                 strerror(errno));
236                 return -1;
237         }
238
239         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
240                 return -1;
241
242         pb_rmdir_recursive(mount_base(), ctx->mount_path);
243
244         return 0;
245 }
246
247 static struct discover_context *find_context(struct device_handler *handler,
248                 const char *id)
249 {
250         struct discover_context *ctx;
251
252         list_for_each_entry(&handler->contexts, ctx, list) {
253                 if (!strcmp(ctx->id, id))
254                         return ctx;
255         }
256
257         return NULL;
258 }
259
260 static int destroy_context(void *arg)
261 {
262         struct discover_context *ctx = arg;
263
264         list_remove(&ctx->list);
265         umount_device(ctx);
266
267         return 0;
268 }
269
270 static int handle_add_udev_event(struct device_handler *handler,
271                 struct event *event)
272 {
273         struct discover_context *ctx;
274         const char *devname;
275         int rc;
276
277         /* create our context */
278         ctx = talloc(handler, struct discover_context);
279         ctx->event = event;
280         ctx->mount_path = NULL;
281         ctx->links = NULL;
282         ctx->n_links = 0;
283
284         ctx->id = talloc_strdup(ctx, event->device);
285
286         devname = event_get_param(ctx->event, "DEVNAME");
287         if (!devname) {
288                 pb_log("no devname for %s?\n", event->device);
289                 return 0;
290         }
291
292         ctx->device_path = talloc_strdup(ctx, devname);
293
294         rc = mount_device(ctx);
295         if (rc) {
296                 talloc_free(ctx);
297                 return 0;
298         }
299
300         list_add(&handler->contexts, &ctx->list);
301         talloc_set_destructor(ctx, destroy_context);
302
303         /* set up the top-level device */
304         ctx->device = talloc_zero(ctx, struct device);
305         ctx->device->id = talloc_strdup(ctx->device, ctx->id);
306         list_init(&ctx->device->boot_options);
307
308         /* run the parsers */
309         iterate_parsers(ctx);
310
311         /* add device to handler device array */
312         device_handler_add(handler, ctx->device);
313
314         discover_server_notify_add(handler->server, ctx->device);
315
316         return 0;
317 }
318
319 static int handle_remove_udev_event(struct device_handler *handler,
320                 struct event *event)
321 {
322         struct discover_context *ctx;
323
324         ctx = find_context(handler, event->device);
325         if (!ctx)
326                 return 0;
327
328         discover_server_notify_remove(handler->server, ctx->device);
329
330         /* remove device from handler device array */
331         device_handler_remove(handler, ctx->device);
332
333         talloc_free(ctx);
334
335         return 0;
336 }
337
338 int device_handler_event(struct device_handler *handler,
339                 struct event *event)
340 {
341         int rc = 0;
342
343         switch (event->type) {
344         case EVENT_TYPE_UDEV:
345                 switch (event->action) {
346                 case EVENT_ACTION_ADD:
347                         rc = handle_add_udev_event(handler, event);
348                         break;
349                 case EVENT_ACTION_REMOVE:
350                         rc = handle_remove_udev_event(handler, event);
351                         break;
352                 default:
353                         pb_log("%s unknown action: %d\n", __func__,
354                                 event->action);
355                         break;
356                 }
357                 break;
358         case EVENT_TYPE_USER:
359                 break;
360         default:
361                 pb_log("%s unknown type: %d\n", __func__, event->type);
362                 break;
363         }
364
365         return rc;
366 }
367
368 struct device_handler *device_handler_init(struct discover_server *server)
369 {
370         struct device_handler *handler;
371
372         handler = talloc(NULL, struct device_handler);
373         handler->devices = NULL;
374         handler->n_devices = 0;
375         handler->server = server;
376
377         list_init(&handler->contexts);
378
379         /* set up our mount point base */
380         pb_mkdir_recursive(mount_base());
381
382         parser_init();
383
384         return handler;
385 }
386
387 void device_handler_destroy(struct device_handler *handler)
388 {
389         talloc_free(handler);
390 }