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