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