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