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