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