]> git.ozlabs.org Git - petitboot/blob - discover/user-event.c
discover/event: Ensure event struct exists for async callers
[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, bool gen_boot_args_sigfile)
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         if (gen_boot_args_sigfile) {
105                 char* args_sigfile_default = talloc_asprintf(opt,
106                         "%s.cmdline.sig", boot_file);
107                 url_str = talloc_asprintf(opt, "%s%s/%s", "tftp://", siaddr,
108                         args_sigfile_default);
109                 talloc_free(args_sigfile_default);
110         }
111         else
112                 url_str = talloc_asprintf(opt, "%s%s/%s", "tftp://", siaddr,
113                         boot_file);
114         url = pb_url_parse(opt, url_str);
115         talloc_free(url_str);
116
117         if (!url)
118                 return NULL;
119
120         res = create_url_resource(opt, url);
121         if (!res) {
122                 talloc_free(url);
123                 return NULL;
124         }
125
126         return res;
127 }
128
129 static int parse_user_event(struct discover_context *ctx, struct event *event)
130 {
131         struct discover_boot_option *d_opt;
132         char *server_ip, *root_dir, *p;
133         struct boot_option *opt;
134         struct device *dev;
135         const char *val;
136
137         dev = ctx->device->device;
138
139         d_opt = discover_boot_option_create(ctx, ctx->device);
140         if (!d_opt)
141                 goto fail;
142
143         opt = d_opt->option;
144
145         val = event_get_param(event, "name");
146
147         if (!val) {
148                 pb_log("%s: no name found\n", __func__);
149                 goto fail_opt;
150         }
151
152         opt->id = talloc_asprintf(opt, "%s#%s", dev->id, val);
153         opt->name = talloc_strdup(opt, val);
154
155         d_opt->boot_image = user_event_resource(d_opt, event, false);
156         if (!d_opt->boot_image) {
157                 pb_log("%s: no boot image found for %s!\n", __func__,
158                                 opt->name);
159                 goto fail_opt;
160         }
161         d_opt->args_sig_file = user_event_resource(d_opt, event, true);
162
163         val = event_get_param(event, "rootpath");
164         if (val) {
165                 server_ip = talloc_strdup(opt, val);
166                 p = strchr(server_ip, ':');
167                 if (p) {
168                         root_dir = talloc_strdup(opt, p + 1);
169                         *p = '\0';
170                         opt->boot_args = talloc_asprintf(opt, "%s%s:%s",
171                                         "root=/dev/nfs ip=any nfsroot=",
172                                         server_ip, root_dir);
173
174                         talloc_free(root_dir);
175                 } else {
176                         opt->boot_args = talloc_asprintf(opt, "%s",
177                                         "root=/dev/nfs ip=any nfsroot=");
178                 }
179
180                 talloc_free(server_ip);
181         }
182
183         opt->description = talloc_asprintf(opt, "%s %s", opt->boot_image_file,
184                 opt->boot_args ? : "");
185
186         if (event_get_param(event, "default"))
187                 opt->is_default = true;
188
189         discover_context_add_boot_option(ctx, d_opt);
190
191         return 0;
192
193 fail_opt:
194         talloc_free(d_opt);
195 fail:
196         return -1;
197 }
198
199 static const char *parse_host_addr(struct event *event)
200 {
201         const char *val;
202
203         val = event_get_param(event, "tftp");
204         if (val)
205                 return val;
206
207         val = event_get_param(event, "siaddr");
208         if (val)
209                 return val;
210
211         val = event_get_param(event, "serverid");
212         if (val)
213                 return val;
214
215         return NULL;
216 }
217
218 static char *parse_mac_addr(struct discover_context *ctx, const char *mac)
219 {
220         unsigned int mac_addr_arr[MAC_ADDR_SIZE];
221         char *mac_addr;
222
223         sscanf(mac, "%X:%X:%X:%X:%X:%X", mac_addr_arr, mac_addr_arr + 1,
224                         mac_addr_arr + 2, mac_addr_arr + 3, mac_addr_arr + 4,
225                         mac_addr_arr + 5);
226
227         mac_addr = talloc_asprintf(ctx, "01-%02x-%02x-%02x-%02x-%02x-%02x",
228                         mac_addr_arr[0], mac_addr_arr[1], mac_addr_arr[2],
229                         mac_addr_arr[3], mac_addr_arr[4], mac_addr_arr[5]);
230
231         return mac_addr;
232 }
233
234 static char *parse_ip_addr(struct discover_context *ctx, const char *ip)
235 {
236         unsigned int ip_addr_arr[IP_ADDR_SIZE];
237         char *ip_hex;
238
239         sscanf(ip, "%u.%u.%u.%u", ip_addr_arr, ip_addr_arr + 1,
240                         ip_addr_arr + 2, ip_addr_arr + 3);
241
242         ip_hex = talloc_asprintf(ctx, "%02X%02X%02X%02X", ip_addr_arr[0],
243                         ip_addr_arr[1], ip_addr_arr[2], ip_addr_arr[3]);
244
245         return ip_hex;
246 }
247
248 struct pb_url *user_event_parse_conf_url(struct discover_context *ctx,
249                 struct event *event, bool *is_complete)
250 {
251         const char *conffile, *pathprefix, *host, *bootfile;
252         char *p, *basedir, *url_str;
253         struct pb_url *url;
254
255         conffile = event_get_param(event, "pxeconffile");
256         pathprefix = event_get_param(event, "pxepathprefix");
257         bootfile = event_get_param(event, "bootfile");
258
259         /* If we're given a conf file, we're able to generate a complete URL to
260          * the configuration file, and the parser doesn't need to do any
261          * further autodiscovery */
262         *is_complete = !!conffile;
263
264         /* if conffile is a URL, that's all we need */
265         if (conffile && is_url(conffile)) {
266                 url = pb_url_parse(ctx, conffile);
267                 return url;
268         }
269
270         /* If we can create a URL from pathprefix (optionally with
271          * conffile appended to create a complete URL), use that */
272         if (pathprefix && is_url(pathprefix)) {
273                 if (conffile) {
274                         url_str = talloc_asprintf(ctx, "%s%s",
275                                         pathprefix, conffile);
276                         url = pb_url_parse(ctx, url_str);
277                         talloc_free(url_str);
278                 } else {
279                         url = pb_url_parse(ctx, pathprefix);
280                 }
281
282                 return url;
283         }
284
285         host = parse_host_addr(event);
286         if (!host) {
287                 pb_log("%s: host address not found\n", __func__);
288                 return NULL;
289         }
290
291         url_str = talloc_asprintf(ctx, "tftp://%s/", host);
292
293         /* if we have a pathprefix, use that directly.. */
294         if (pathprefix) {
295                 /* strip leading slashes */
296                 while (pathprefix[0] == '/')
297                         pathprefix++;
298                 url_str = talloc_asprintf_append(url_str, "%s", pathprefix);
299
300         /* ... otherwise, add a path based on the bootfile name, but only
301          * if conffile isn't an absolute path itself */
302         } else if (bootfile && !(conffile && conffile[0] == '/')) {
303
304                 basedir = talloc_strdup(ctx, bootfile);
305
306                 /* strip filename from the bootfile path, leaving only a
307                  * directory */
308                 p = strrchr(basedir, '/');
309                 if (!p)
310                         p = basedir;
311                 *p = '\0';
312
313                 if (strlen(basedir))
314                         url_str = talloc_asprintf_append(url_str, "%s/",
315                                         basedir);
316
317                 talloc_free(basedir);
318         }
319
320         /* finally, append conffile */
321         if (conffile)
322                 url_str = talloc_asprintf_append(url_str, "%s", conffile);
323
324         url = pb_url_parse(ctx, url_str);
325
326         talloc_free(url_str);
327
328         return url;
329 }
330
331 char **user_event_parse_conf_filenames(
332                 struct discover_context *ctx, struct event *event)
333 {
334         char *mac_addr, *ip_hex;
335         const char *mac, *ip;
336         char **filenames;
337         int index, len;
338
339         mac = event_get_param(event, "mac");
340         if (mac)
341                 mac_addr = parse_mac_addr(ctx, mac);
342         else
343                 mac_addr = NULL;
344
345         ip = event_get_param(event, "ip");
346         if (ip) {
347                 ip_hex = parse_ip_addr(ctx, ip);
348                 len = strlen(ip_hex);
349         } else {
350                 ip_hex = NULL;
351                 len = 0;
352         }
353
354         if (!mac_addr && !ip_hex) {
355                 pb_log("%s: neither mac nor ip parameter found\n", __func__);
356                 return NULL;
357         }
358
359         /* Filenames as fallback IP's + mac + default */
360         filenames = talloc_array(ctx, char *, len + 3);
361
362         index = 0;
363         if (mac_addr)
364                 filenames[index++] = talloc_strdup(filenames, mac_addr);
365
366         while (len) {
367                 filenames[index++] = talloc_strdup(filenames, ip_hex);
368                 ip_hex[--len] = '\0';
369         }
370
371         filenames[index++] = talloc_strdup(filenames, "default");
372         filenames[index++] = NULL;
373
374         if (mac_addr)
375                 talloc_free(mac_addr);
376
377         if (ip_hex)
378                 talloc_free(ip_hex);
379
380         return filenames;
381 }
382
383 static int user_event_dhcp(struct user_event *uev, struct event *event)
384 {
385         struct device_handler *handler = uev->handler;
386         struct discover_device *dev;
387
388         dev = discover_device_create(handler, event->device);
389
390         device_handler_dhcp(handler, dev, event);
391
392         return 0;
393 }
394
395 static int user_event_conf(struct user_event *uev, struct event *event)
396 {
397         struct device_handler *handler = uev->handler;
398         struct discover_device *dev;
399         struct pb_url *url;
400         const char *val;
401
402         val = event_get_param(event, "url");
403         if (!val)
404                 return 0;
405
406         url = pb_url_parse(event, val);
407         if (!url)
408                 return 0;
409
410         dev = discover_device_create(handler, event->device);
411
412         device_handler_conf(handler, dev, url);
413
414         return 0;
415 }
416
417 static int user_event_add(struct user_event *uev, struct event *event)
418 {
419         struct device_handler *handler = uev->handler;
420         struct discover_context *ctx;
421         struct discover_device *dev;
422
423         dev = discover_device_create(handler, event->device);
424         ctx = device_handler_discover_context_create(handler, dev);
425
426         parse_user_event(ctx, event);
427
428         device_handler_discover_context_commit(handler, ctx);
429
430         talloc_free(ctx);
431
432         return 0;
433 }
434
435 static int user_event_remove(struct user_event *uev, struct event *event)
436 {
437         struct device_handler *handler = uev->handler;
438         struct discover_device *dev;
439
440         dev = device_lookup_by_id(handler, event->device);
441         if (!dev)
442                 return 0;
443
444         device_handler_remove(handler, dev);
445
446         return 0;
447 }
448
449 static int user_event_url(struct user_event *uev, struct event *event)
450 {
451         struct device_handler *handler = uev->handler;
452         const char *url;
453
454         url = event_get_param(event, "url");
455         if (url)
456                 device_handler_process_url(handler, url, NULL, NULL);
457
458         return 0;
459 }
460
461 static int user_event_boot(struct user_event *uev, struct event *event)
462 {
463         struct device_handler *handler = uev->handler;
464         struct boot_command *cmd = talloc(handler, struct boot_command);
465
466         cmd->option_id = talloc_strdup(cmd, event_get_param(event, "id"));
467         cmd->boot_image_file = talloc_strdup(cmd, event_get_param(event, "image"));
468         cmd->initrd_file = talloc_strdup(cmd, event_get_param(event, "initrd"));
469         cmd->dtb_file = talloc_strdup(cmd, event_get_param(event, "dtb"));
470         cmd->boot_args = talloc_strdup(cmd, event_get_param(event, "args"));
471
472         device_handler_boot(handler, cmd);
473
474         talloc_free(cmd);
475
476         return 0;
477 }
478
479 static int user_event_sync(struct user_event *uev, struct event *event)
480 {
481         struct device_handler *handler = uev->handler;
482
483         if (strncasecmp(event->device, "all", strlen("all")) != 0)
484                 device_sync_snapshots(handler, event->device);
485         else
486                 device_sync_snapshots(handler, NULL);
487
488         return 0;
489 }
490
491 static void user_event_handle_message(struct user_event *uev, char *buf,
492         int len)
493 {
494         int result;
495         struct event *event;
496
497         event = talloc(uev, struct event);
498         event->type = EVENT_TYPE_USER;
499
500         result = event_parse_ad_message(event, buf, len);
501
502         if (result)
503                 return;
504
505         user_event_print_event(event);
506
507         switch (event->action) {
508         case EVENT_ACTION_ADD:
509                 result = user_event_add(uev, event);
510                 break;
511         case EVENT_ACTION_REMOVE:
512                 result = user_event_remove(uev, event);
513                 break;
514         case EVENT_ACTION_URL:
515                 result = user_event_url(uev, event);
516                 goto out;
517         case EVENT_ACTION_CONF:
518                 result = user_event_conf(uev, event);
519                 break;
520         case EVENT_ACTION_DHCP:
521                 result = user_event_dhcp(uev, event);
522                 goto out;
523         case EVENT_ACTION_BOOT:
524                 result = user_event_boot(uev, event);
525                 break;
526         case EVENT_ACTION_SYNC:
527                 result = user_event_sync(uev, event);
528                 break;
529         default:
530                 break;
531         }
532
533         /* user_event_url() and user_event_dhcp() will steal the event context,
534          * but all others still need to free */
535         talloc_free(event);
536 out:
537         return;
538 }
539
540 static int user_event_process(void *arg)
541 {
542         struct user_event *uev = arg;
543         char buf[PBOOT_USER_EVENT_SIZE + 1];
544         int len;
545
546         len = recvfrom(uev->socket, buf, PBOOT_USER_EVENT_SIZE, 0, NULL, NULL);
547
548         if (len < 0) {
549                 pb_log("%s: socket read failed: %s", __func__, strerror(errno));
550                 return 0;
551         }
552
553         if (len == 0) {
554                 pb_log("%s: empty", __func__);
555                 return 0;
556         }
557
558         buf[len] = '\0';
559
560         pb_debug("%s: %u bytes\n", __func__, len);
561
562         user_event_handle_message(uev, buf, len);
563
564         return 0;
565 }
566
567 static int user_event_destructor(void *arg)
568 {
569         struct user_event *uev = arg;
570
571         pb_debug("%s\n", __func__);
572
573         if (uev->socket >= 0)
574                 close(uev->socket);
575
576         return 0;
577 }
578
579 struct user_event *user_event_init(struct device_handler *handler,
580                 struct waitset *waitset)
581 {
582         struct sockaddr_un addr;
583         struct user_event *uev;
584
585         unlink(PBOOT_USER_EVENT_SOCKET);
586
587         uev = talloc(handler, struct user_event);
588
589         uev->handler = handler;
590
591         uev->socket = socket(AF_UNIX, SOCK_DGRAM, 0);
592         if (uev->socket < 0) {
593                 pb_log("%s: Error creating event handler socket: %s\n",
594                         __func__, strerror(errno));
595                 goto out_err;
596         }
597
598         talloc_set_destructor(uev, user_event_destructor);
599
600         memset(&addr, 0, sizeof(addr));
601         addr.sun_family = AF_UNIX;
602         strcpy(addr.sun_path, PBOOT_USER_EVENT_SOCKET);
603
604         if (bind(uev->socket, (struct sockaddr *)&addr, sizeof(addr))) {
605                 pb_log("Error binding event handler socket: %s\n",
606                         strerror(errno));
607         }
608
609         waiter_register_io(waitset, uev->socket, WAIT_IN,
610                         user_event_process, uev);
611
612         pb_debug("%s: waiting on %s\n", __func__, PBOOT_USER_EVENT_SOCKET);
613
614         return uev;
615
616 out_err:
617         talloc_free(uev);
618         return NULL;
619 }