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