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