]> git.ozlabs.org Git - petitboot/blob - discover/user-event.c
b201de8c669e84230efd6e13cc9254ecc68fbd96
[petitboot] / discover / user-event.c
1 /*
2  *  Copyright (C) 2009 Sony Computer Entertainment Inc.
3  *  Copyright 2009 Sony Corp.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; version 2 of the License.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 #if defined(HAVE_CONFIG_H)
20 #include "config.h"
21 #endif
22
23 #include <assert.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <sys/socket.h>
27 #include <sys/types.h>
28 #include <sys/un.h>
29
30 #include <log/log.h>
31 #include <url/url.h>
32 #include <types/types.h>
33 #include <talloc/talloc.h>
34 #include <waiter/waiter.h>
35
36 #include "device-handler.h"
37 #include "resource.h"
38 #include "event.h"
39 #include "user-event.h"
40
41
42 #define MAC_ADDR_SIZE   6
43 #define IP_ADDR_SIZE    4
44
45 struct user_event {
46         struct device_handler *handler;
47         int socket;
48 };
49
50 static const char *event_action_name(enum event_action action)
51 {
52         switch (action) {
53         case EVENT_ACTION_ADD:
54                 return "add";
55         case EVENT_ACTION_REMOVE:
56                 return "remove";
57         case EVENT_ACTION_URL:
58                 return "url";
59         case EVENT_ACTION_DHCP:
60                 return "dhcp";
61         case EVENT_ACTION_BOOT:
62                 return "boot";
63         case EVENT_ACTION_SYNC:
64                 return "sync";
65         default:
66                 break;
67         }
68
69         return "unknown";
70 }
71
72 static void user_event_print_event(struct event __attribute__((unused)) *event)
73 {
74         int i;
75
76         pb_debug("user_event %s event:\n", event_action_name(event->action));
77         pb_debug("\tdevice: %s\n", event->device);
78
79         for (i = 0; i < event->n_params; i++)
80                 pb_debug("\t%-12s => %s\n",
81                         event->params[i].name, event->params[i].value);
82 }
83
84 static struct resource *user_event_resource(struct discover_boot_option *opt,
85                 struct event *event)
86 {
87         const char *siaddr, *boot_file;
88         struct resource *res;
89         struct pb_url *url;
90         char *url_str;
91
92         siaddr = event_get_param(event, "siaddr");
93         if (!siaddr) {
94                 pb_log("%s: next server option not found\n", __func__);
95                 return NULL;
96         }
97
98         boot_file = event_get_param(event, "bootfile");
99         if (!boot_file) {
100                 pb_log("%s: bootfile not found\n", __func__);
101                 return NULL;
102         }
103
104         url_str = talloc_asprintf(opt, "%s%s/%s", "tftp://", siaddr, boot_file);
105         url = pb_url_parse(opt, url_str);
106         talloc_free(url_str);
107
108         if (!url)
109                 return NULL;
110
111         res = create_url_resource(opt, url);
112         if (!res) {
113                 talloc_free(url);
114                 return NULL;
115         }
116
117         return res;
118 }
119
120 static int parse_user_event(struct discover_context *ctx, struct event *event)
121 {
122         struct discover_boot_option *d_opt;
123         char *server_ip, *root_dir, *p;
124         struct boot_option *opt;
125         struct device *dev;
126         const char *val;
127
128         dev = ctx->device->device;
129
130         d_opt = discover_boot_option_create(ctx, ctx->device);
131         if (!d_opt)
132                 goto fail;
133
134         opt = d_opt->option;
135
136         val = event_get_param(event, "name");
137
138         if (!val) {
139                 pb_log("%s: no name found\n", __func__);
140                 goto fail_opt;
141         }
142
143         opt->id = talloc_asprintf(opt, "%s#%s", dev->id, val);
144         opt->name = talloc_strdup(opt, val);
145
146         d_opt->boot_image = user_event_resource(d_opt, event);
147         if (!d_opt->boot_image) {
148                 pb_log("%s: no boot image found for %s!\n", __func__,
149                                 opt->name);
150                 goto fail_opt;
151         }
152
153         val = event_get_param(event, "rootpath");
154         if (val) {
155                 server_ip = talloc_strdup(opt, val);
156                 p = strchr(server_ip, ':');
157                 if (p) {
158                         root_dir = talloc_strdup(opt, p + 1);
159                         *p = '\0';
160                         opt->boot_args = talloc_asprintf(opt, "%s%s:%s",
161                                         "root=/dev/nfs ip=any nfsroot=",
162                                         server_ip, root_dir);
163
164                         talloc_free(root_dir);
165                 } else {
166                         opt->boot_args = talloc_asprintf(opt, "%s",
167                                         "root=/dev/nfs ip=any nfsroot=");
168                 }
169
170                 talloc_free(server_ip);
171         }
172
173         opt->description = talloc_asprintf(opt, "%s %s", opt->boot_image_file,
174                 opt->boot_args ? : "");
175
176         if (event_get_param(event, "default"))
177                 opt->is_default = true;
178
179         discover_context_add_boot_option(ctx, d_opt);
180
181         return 0;
182
183 fail_opt:
184         talloc_free(d_opt);
185 fail:
186         return -1;
187 }
188
189 static const char *parse_host_addr(struct event *event)
190 {
191         const char *val;
192
193         val = event_get_param(event, "tftp");
194         if (val)
195                 return val;
196
197         val = event_get_param(event, "siaddr");
198         if (val)
199                 return val;
200
201         val = event_get_param(event, "serverid");
202         if (val)
203                 return val;
204
205         return NULL;
206 }
207
208 static char *parse_mac_addr(struct discover_context *ctx, const char *mac)
209 {
210         unsigned int mac_addr_arr[MAC_ADDR_SIZE];
211         char *mac_addr;
212
213         sscanf(mac, "%X:%X:%X:%X:%X:%X", mac_addr_arr, mac_addr_arr + 1,
214                         mac_addr_arr + 2, mac_addr_arr + 3, mac_addr_arr + 4,
215                         mac_addr_arr + 5);
216
217         mac_addr = talloc_asprintf(ctx, "01-%02x-%02x-%02x-%02x-%02x-%02x",
218                         mac_addr_arr[0], mac_addr_arr[1], mac_addr_arr[2],
219                         mac_addr_arr[3], mac_addr_arr[4], mac_addr_arr[5]);
220
221         return mac_addr;
222 }
223
224 static char *parse_ip_addr(struct discover_context *ctx, const char *ip)
225 {
226         unsigned int ip_addr_arr[IP_ADDR_SIZE];
227         char *ip_hex;
228
229         sscanf(ip, "%u.%u.%u.%u", ip_addr_arr, ip_addr_arr + 1,
230                         ip_addr_arr + 2, ip_addr_arr + 3);
231
232         ip_hex = talloc_asprintf(ctx, "%02X%02X%02X%02X", ip_addr_arr[0],
233                         ip_addr_arr[1], ip_addr_arr[2], ip_addr_arr[3]);
234
235         return ip_hex;
236 }
237
238 struct pb_url *user_event_parse_conf_url(struct discover_context *ctx,
239                 struct event *event, bool *is_complete)
240 {
241         const char *conffile, *pathprefix, *host, *bootfile;
242         char *p, *basedir, *url_str;
243         struct pb_url *url;
244
245         conffile = event_get_param(event, "pxeconffile");
246         pathprefix = event_get_param(event, "pxepathprefix");
247         bootfile = event_get_param(event, "bootfile");
248
249         /* If we're given a conf file, we're able to generate a complete URL to
250          * the configuration file, and the parser doesn't need to do any
251          * further autodiscovery */
252         *is_complete = !!conffile;
253
254         /* if conffile is a URL, that's all we need */
255         if (conffile && is_url(conffile)) {
256                 url = pb_url_parse(ctx, conffile);
257                 return url;
258         }
259
260         /* If we can create a URL from pathprefix (optionally with
261          * conffile appended to create a complete URL), use that */
262         if (pathprefix && is_url(pathprefix)) {
263                 if (conffile) {
264                         url_str = talloc_asprintf(ctx, "%s%s",
265                                         pathprefix, conffile);
266                         url = pb_url_parse(ctx, url_str);
267                         talloc_free(url_str);
268                 } else {
269                         url = pb_url_parse(ctx, pathprefix);
270                 }
271
272                 return url;
273         }
274
275         host = parse_host_addr(event);
276         if (!host) {
277                 pb_log("%s: host address not found\n", __func__);
278                 return NULL;
279         }
280
281         url_str = talloc_asprintf(ctx, "tftp://%s/", host);
282
283         /* if we have a pathprefix, use that directly.. */
284         if (pathprefix) {
285                 /* strip leading slashes */
286                 while (pathprefix[0] == '/')
287                         pathprefix++;
288                 url_str = talloc_asprintf_append(url_str, "%s", pathprefix);
289
290         /* ... otherwise, add a path based on the bootfile name, but only
291          * if conffile isn't an absolute path itself */
292         } else if (bootfile && !(conffile && conffile[0] == '/')) {
293
294                 basedir = talloc_strdup(ctx, bootfile);
295
296                 /* strip filename from the bootfile path, leaving only a
297                  * directory */
298                 p = strrchr(basedir, '/');
299                 if (!p)
300                         p = basedir;
301                 *p = '\0';
302
303                 if (strlen(basedir))
304                         url_str = talloc_asprintf_append(url_str, "%s/",
305                                         basedir);
306
307                 talloc_free(basedir);
308         }
309
310         /* finally, append conffile */
311         if (conffile)
312                 url_str = talloc_asprintf_append(url_str, "%s", conffile);
313
314         url = pb_url_parse(ctx, url_str);
315
316         talloc_free(url_str);
317
318         return url;
319 }
320
321 char **user_event_parse_conf_filenames(
322                 struct discover_context *ctx, struct event *event)
323 {
324         char *mac_addr, *ip_hex;
325         const char *mac, *ip;
326         char **filenames;
327         int index, len;
328
329         mac = event_get_param(event, "mac");
330         if (mac)
331                 mac_addr = parse_mac_addr(ctx, mac);
332         else
333                 mac_addr = NULL;
334
335         ip = event_get_param(event, "ip");
336         if (ip) {
337                 ip_hex = parse_ip_addr(ctx, ip);
338                 len = strlen(ip_hex);
339         } else {
340                 ip_hex = NULL;
341                 len = 0;
342         }
343
344         if (!mac_addr && !ip_hex) {
345                 pb_log("%s: neither mac nor ip parameter found\n", __func__);
346                 return NULL;
347         }
348
349         /* Filenames as fallback IP's + mac + default */
350         filenames = talloc_array(ctx, char *, len + 3);
351
352         index = 0;
353         if (mac_addr)
354                 filenames[index++] = talloc_strdup(filenames, mac_addr);
355
356         while (len) {
357                 filenames[index++] = talloc_strdup(filenames, ip_hex);
358                 ip_hex[--len] = '\0';
359         }
360
361         filenames[index++] = talloc_strdup(filenames, "default");
362         filenames[index++] = NULL;
363
364         if (mac_addr)
365                 talloc_free(mac_addr);
366
367         if (ip_hex)
368                 talloc_free(ip_hex);
369
370         return filenames;
371 }
372
373 static int user_event_dhcp(struct user_event *uev, struct event *event)
374 {
375         struct device_handler *handler = uev->handler;
376         struct discover_device *dev;
377
378         dev = discover_device_create(handler, event->device);
379
380         device_handler_dhcp(handler, dev, event);
381
382         return 0;
383 }
384
385 static int user_event_add(struct user_event *uev, struct event *event)
386 {
387         struct device_handler *handler = uev->handler;
388         struct discover_context *ctx;
389         struct discover_device *dev;
390
391         dev = discover_device_create(handler, event->device);
392         ctx = device_handler_discover_context_create(handler, dev);
393
394         parse_user_event(ctx, event);
395
396         device_handler_discover_context_commit(handler, ctx);
397
398         talloc_free(ctx);
399
400         return 0;
401 }
402
403 static int user_event_remove(struct user_event *uev, struct event *event)
404 {
405         struct device_handler *handler = uev->handler;
406         struct discover_device *dev;
407
408         dev = device_lookup_by_id(handler, event->device);
409         if (!dev)
410                 return 0;
411
412         device_handler_remove(handler, dev);
413
414         return 0;
415 }
416
417 static int user_event_url(struct user_event *uev, struct event *event)
418 {
419         struct device_handler *handler = uev->handler;
420         const char *url;
421
422         url = event_get_param(event, "url");
423         if (url)
424                 device_handler_process_url(handler, url, NULL, NULL);
425
426         return 0;
427 }
428
429 static int user_event_boot(struct user_event *uev, struct event *event)
430 {
431         struct device_handler *handler = uev->handler;
432         struct boot_command *cmd = talloc(handler, struct boot_command);
433
434         cmd->option_id = talloc_strdup(cmd, event_get_param(event, "id"));
435         cmd->boot_image_file = talloc_strdup(cmd, event_get_param(event, "image"));
436         cmd->initrd_file = talloc_strdup(cmd, event_get_param(event, "initrd"));
437         cmd->dtb_file = talloc_strdup(cmd, event_get_param(event, "dtb"));
438         cmd->boot_args = talloc_strdup(cmd, event_get_param(event, "args"));
439
440         device_handler_boot(handler, cmd);
441
442         talloc_free(cmd);
443
444         return 0;
445 }
446
447 static int user_event_sync(struct user_event *uev, struct event *event)
448 {
449         struct device_handler *handler = uev->handler;
450
451         if (strncasecmp(event->device, "all", strlen("all")) != 0)
452                 device_sync_snapshots(handler, event->device);
453         else
454                 device_sync_snapshots(handler, NULL);
455
456         return 0;
457 }
458
459 static void user_event_handle_message(struct user_event *uev, char *buf,
460         int len)
461 {
462         int result;
463         struct event *event;
464
465         event = talloc(uev, struct event);
466         event->type = EVENT_TYPE_USER;
467
468         result = event_parse_ad_message(event, buf, len);
469
470         if (result)
471                 return;
472
473         user_event_print_event(event);
474
475         switch (event->action) {
476         case EVENT_ACTION_ADD:
477                 result = user_event_add(uev, event);
478                 break;
479         case EVENT_ACTION_REMOVE:
480                 result = user_event_remove(uev, event);
481                 break;
482         case EVENT_ACTION_URL:
483                 result = user_event_url(uev, event);
484                 goto out;
485         case EVENT_ACTION_DHCP:
486                 result = user_event_dhcp(uev, event);
487                 goto out;
488         case EVENT_ACTION_BOOT:
489                 result = user_event_boot(uev, event);
490                 break;
491         case EVENT_ACTION_SYNC:
492                 result = user_event_sync(uev, event);
493                 break;
494         default:
495                 break;
496         }
497
498         /* user_event_url() and user_event_dhcp() will steal the event context,
499          * but all others still need to free */
500         talloc_free(event);
501 out:
502         return;
503 }
504
505 static int user_event_process(void *arg)
506 {
507         struct user_event *uev = arg;
508         char buf[PBOOT_USER_EVENT_SIZE + 1];
509         int len;
510
511         len = recvfrom(uev->socket, buf, PBOOT_USER_EVENT_SIZE, 0, NULL, NULL);
512
513         if (len < 0) {
514                 pb_log("%s: socket read failed: %s", __func__, strerror(errno));
515                 return 0;
516         }
517
518         if (len == 0) {
519                 pb_log("%s: empty", __func__);
520                 return 0;
521         }
522
523         buf[len] = '\0';
524
525         pb_debug("%s: %u bytes\n", __func__, len);
526
527         user_event_handle_message(uev, buf, len);
528
529         return 0;
530 }
531
532 static int user_event_destructor(void *arg)
533 {
534         struct user_event *uev = arg;
535
536         pb_debug("%s\n", __func__);
537
538         if (uev->socket >= 0)
539                 close(uev->socket);
540
541         return 0;
542 }
543
544 struct user_event *user_event_init(struct device_handler *handler,
545                 struct waitset *waitset)
546 {
547         struct sockaddr_un addr;
548         struct user_event *uev;
549
550         unlink(PBOOT_USER_EVENT_SOCKET);
551
552         uev = talloc(handler, struct user_event);
553
554         uev->handler = handler;
555
556         uev->socket = socket(AF_UNIX, SOCK_DGRAM, 0);
557         if (uev->socket < 0) {
558                 pb_log("%s: Error creating event handler socket: %s\n",
559                         __func__, strerror(errno));
560                 goto out_err;
561         }
562
563         talloc_set_destructor(uev, user_event_destructor);
564
565         memset(&addr, 0, sizeof(addr));
566         addr.sun_family = AF_UNIX;
567         strcpy(addr.sun_path, PBOOT_USER_EVENT_SOCKET);
568
569         if (bind(uev->socket, (struct sockaddr *)&addr, sizeof(addr))) {
570                 pb_log("Error binding event handler socket: %s\n",
571                         strerror(errno));
572         }
573
574         waiter_register_io(waitset, uev->socket, WAIT_IN,
575                         user_event_process, uev);
576
577         pb_debug("%s: waiting on %s\n", __func__, PBOOT_USER_EVENT_SOCKET);
578
579         return uev;
580
581 out_err:
582         talloc_free(uev);
583         return NULL;
584 }