discover/user-event: Use bootfile_url if available
[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         case EVENT_ACTION_PLUGIN:
67                 return "plugin";
68         default:
69                 break;
70         }
71
72         return "unknown";
73 }
74
75 static void user_event_print_event(struct event __attribute__((unused)) *event)
76 {
77         int i;
78
79         pb_debug("user_event %s event:\n", event_action_name(event->action));
80         pb_debug("\tdevice: %s\n", event->device);
81
82         for (i = 0; i < event->n_params; i++)
83                 pb_debug("\t%-12s => %s\n",
84                         event->params[i].name, event->params[i].value);
85 }
86
87 static struct resource *user_event_resource(struct discover_boot_option *opt,
88                 struct event *event, bool gen_boot_args_sigfile)
89 {
90         const char *siaddr, *boot_file;
91         struct resource *res;
92         struct pb_url *url;
93         char *url_str;
94
95         siaddr = event_get_param(event, "siaddr");
96         if (!siaddr) {
97                 pb_log("%s: next server option not found\n", __func__);
98                 return NULL;
99         }
100
101         boot_file = event_get_param(event, "bootfile");
102         if (!boot_file) {
103                 pb_log("%s: bootfile not found\n", __func__);
104                 return NULL;
105         }
106
107         if (gen_boot_args_sigfile) {
108                 char* args_sigfile_default = talloc_asprintf(opt,
109                         "%s.cmdline.sig", boot_file);
110                 url_str = talloc_asprintf(opt, "%s%s/%s", "tftp://", siaddr,
111                         args_sigfile_default);
112                 talloc_free(args_sigfile_default);
113         }
114         else
115                 url_str = talloc_asprintf(opt, "%s%s/%s", "tftp://", siaddr,
116                         boot_file);
117         url = pb_url_parse(opt, url_str);
118         talloc_free(url_str);
119
120         if (!url)
121                 return NULL;
122
123         res = create_url_resource(opt, url);
124         if (!res) {
125                 talloc_free(url);
126                 return NULL;
127         }
128
129         return res;
130 }
131
132 static int parse_user_event(struct discover_context *ctx, struct event *event)
133 {
134         struct discover_boot_option *d_opt;
135         char *server_ip, *root_dir, *p;
136         struct boot_option *opt;
137         struct device *dev;
138         const char *val;
139
140         dev = ctx->device->device;
141
142         d_opt = discover_boot_option_create(ctx, ctx->device);
143         if (!d_opt)
144                 goto fail;
145
146         opt = d_opt->option;
147
148         val = event_get_param(event, "name");
149
150         if (!val) {
151                 pb_log("%s: no name found\n", __func__);
152                 goto fail_opt;
153         }
154
155         opt->id = talloc_asprintf(opt, "%s#%s", dev->id, val);
156         opt->name = talloc_strdup(opt, val);
157
158         d_opt->boot_image = user_event_resource(d_opt, event, false);
159         if (!d_opt->boot_image) {
160                 pb_log("%s: no boot image found for %s!\n", __func__,
161                                 opt->name);
162                 goto fail_opt;
163         }
164         d_opt->args_sig_file = user_event_resource(d_opt, event, true);
165
166         val = event_get_param(event, "rootpath");
167         if (val) {
168                 server_ip = talloc_strdup(opt, val);
169                 p = strchr(server_ip, ':');
170                 if (p) {
171                         root_dir = talloc_strdup(opt, p + 1);
172                         *p = '\0';
173                         opt->boot_args = talloc_asprintf(opt, "%s%s:%s",
174                                         "root=/dev/nfs ip=any nfsroot=",
175                                         server_ip, root_dir);
176
177                         talloc_free(root_dir);
178                 } else {
179                         opt->boot_args = talloc_asprintf(opt, "%s",
180                                         "root=/dev/nfs ip=any nfsroot=");
181                 }
182
183                 talloc_free(server_ip);
184         }
185
186         opt->description = talloc_asprintf(opt, "%s %s", opt->boot_image_file,
187                 opt->boot_args ? : "");
188
189         if (event_get_param(event, "default"))
190                 opt->is_default = true;
191
192         discover_context_add_boot_option(ctx, d_opt);
193
194         return 0;
195
196 fail_opt:
197         talloc_free(d_opt);
198 fail:
199         return -1;
200 }
201
202 static const char *parse_host_addr(struct event *event)
203 {
204         const char *val;
205
206         val = event_get_param(event, "tftp");
207         if (val)
208                 return val;
209
210         val = event_get_param(event, "siaddr");
211         if (val)
212                 return val;
213
214         val = event_get_param(event, "serverid");
215         if (val)
216                 return val;
217
218         return NULL;
219 }
220
221 static char *parse_mac_addr(struct discover_context *ctx, const char *mac)
222 {
223         unsigned int mac_addr_arr[MAC_ADDR_SIZE];
224         char *mac_addr;
225
226         sscanf(mac, "%X:%X:%X:%X:%X:%X", mac_addr_arr, mac_addr_arr + 1,
227                         mac_addr_arr + 2, mac_addr_arr + 3, mac_addr_arr + 4,
228                         mac_addr_arr + 5);
229
230         mac_addr = talloc_asprintf(ctx, "01-%02x-%02x-%02x-%02x-%02x-%02x",
231                         mac_addr_arr[0], mac_addr_arr[1], mac_addr_arr[2],
232                         mac_addr_arr[3], mac_addr_arr[4], mac_addr_arr[5]);
233
234         return mac_addr;
235 }
236
237 static char *parse_ip_addr(struct discover_context *ctx, const char *ip)
238 {
239         unsigned int ip_addr_arr[IP_ADDR_SIZE];
240         char *ip_hex;
241
242         sscanf(ip, "%u.%u.%u.%u", ip_addr_arr, ip_addr_arr + 1,
243                         ip_addr_arr + 2, ip_addr_arr + 3);
244
245         ip_hex = talloc_asprintf(ctx, "%02X%02X%02X%02X", ip_addr_arr[0],
246                         ip_addr_arr[1], ip_addr_arr[2], ip_addr_arr[3]);
247
248         return ip_hex;
249 }
250
251 struct pb_url *user_event_parse_conf_url(struct discover_context *ctx,
252                 struct event *event, bool *is_complete)
253 {
254         const char *conffile, *pathprefix, *host, *bootfile, *bootfile_url;
255         char *p, *basedir, *url_str;
256         struct pb_url *url;
257
258         conffile = event_get_param(event, "pxeconffile");
259         pathprefix = event_get_param(event, "pxepathprefix");
260         bootfile = event_get_param(event, "bootfile");
261         bootfile_url = event_get_param(event, "bootfile_url");
262
263         /* If we're given a conf file, we're able to generate a complete URL to
264          * the configuration file, and the parser doesn't need to do any
265          * further autodiscovery */
266         *is_complete = !!conffile;
267
268         /* if conffile is a URL, that's all we need */
269         if (conffile && is_url(conffile)) {
270                 url = pb_url_parse(ctx, conffile);
271                 return url;
272         }
273
274         /* If we can create a URL from pathprefix (optionally with
275          * conffile appended to create a complete URL), use that */
276         if (pathprefix && is_url(pathprefix)) {
277                 if (conffile) {
278                         url_str = talloc_asprintf(ctx, "%s%s",
279                                         pathprefix, conffile);
280                         url = pb_url_parse(ctx, url_str);
281                         talloc_free(url_str);
282                 } else {
283                         url = pb_url_parse(ctx, pathprefix);
284                 }
285
286                 return url;
287         }
288
289         host = parse_host_addr(event);
290         if (!host) {
291                 pb_log("%s: host address not found\n", __func__);
292
293                 /* No full URLs and no host address? Check for DHCPv6 options */
294                 if (bootfile_url && is_url(bootfile_url)) {
295                         *is_complete = true;
296                         return pb_url_parse(ctx, bootfile_url);
297                 }
298                 return NULL;
299         }
300
301         url_str = talloc_asprintf(ctx, "tftp://%s/", host);
302
303         /* if we have a pathprefix, use that directly.. */
304         if (pathprefix) {
305                 /* strip leading slashes */
306                 while (pathprefix[0] == '/')
307                         pathprefix++;
308                 url_str = talloc_asprintf_append(url_str, "%s", pathprefix);
309
310         /* ... otherwise, add a path based on the bootfile name, but only
311          * if conffile isn't an absolute path itself */
312         } else if (bootfile && !(conffile && conffile[0] == '/')) {
313
314                 basedir = talloc_strdup(ctx, bootfile);
315
316                 /* strip filename from the bootfile path, leaving only a
317                  * directory */
318                 p = strrchr(basedir, '/');
319                 if (!p)
320                         p = basedir;
321                 *p = '\0';
322
323                 if (strlen(basedir))
324                         url_str = talloc_asprintf_append(url_str, "%s/",
325                                         basedir);
326
327                 talloc_free(basedir);
328         }
329
330         /* finally, append conffile */
331         if (conffile)
332                 url_str = talloc_asprintf_append(url_str, "%s", conffile);
333
334         url = pb_url_parse(ctx, url_str);
335
336         talloc_free(url_str);
337
338         return url;
339 }
340
341 char **user_event_parse_conf_filenames(
342                 struct discover_context *ctx, struct event *event)
343 {
344         char *mac_addr, *ip_hex;
345         const char *mac, *ip;
346         char **filenames;
347         int index, len;
348
349         mac = event_get_param(event, "mac");
350         if (mac)
351                 mac_addr = parse_mac_addr(ctx, mac);
352         else
353                 mac_addr = NULL;
354
355         ip = event_get_param(event, "ip");
356         if (ip) {
357                 ip_hex = parse_ip_addr(ctx, ip);
358                 len = strlen(ip_hex);
359         } else {
360                 ip_hex = NULL;
361                 len = 0;
362         }
363
364         if (!mac_addr && !ip_hex) {
365                 pb_log("%s: neither mac nor ip parameter found\n", __func__);
366                 return NULL;
367         }
368
369         /* Filenames as fallback IP's + mac + default */
370         filenames = talloc_array(ctx, char *, len + 3);
371
372         index = 0;
373         if (mac_addr)
374                 filenames[index++] = talloc_strdup(filenames, mac_addr);
375
376         while (len) {
377                 filenames[index++] = talloc_strdup(filenames, ip_hex);
378                 ip_hex[--len] = '\0';
379         }
380
381         filenames[index++] = talloc_strdup(filenames, "default");
382         filenames[index++] = NULL;
383
384         if (mac_addr)
385                 talloc_free(mac_addr);
386
387         if (ip_hex)
388                 talloc_free(ip_hex);
389
390         return filenames;
391 }
392
393 static int user_event_dhcp(struct user_event *uev, struct event *event)
394 {
395         struct device_handler *handler = uev->handler;
396         struct discover_device *dev;
397
398         uint8_t hwaddr[MAC_ADDR_SIZE];
399
400         if (!event_get_param(event, "mac"))
401                 return -1;
402         if (!event_get_param(event, "ip") && !event_get_param(event, "ipv6"))
403                 return -1;
404
405         sscanf(event_get_param(event, "mac"),
406                "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
407                hwaddr, hwaddr + 1, hwaddr + 2,
408                hwaddr + 3, hwaddr + 4, hwaddr + 5);
409
410         if (event_get_param(event, "ipv6"))
411                 system_info_set_interface_address(sizeof(hwaddr), hwaddr,
412                                                   event_get_param(event, "ipv6"));
413         else
414                 system_info_set_interface_address(sizeof(hwaddr), hwaddr,
415                                                   event_get_param(event, "ip"));
416
417         dev = discover_device_create(handler, event_get_param(event, "mac"),
418                                         event->device);
419
420         device_handler_dhcp(handler, dev, event);
421
422         return 0;
423 }
424
425 static int user_event_add(struct user_event *uev, struct event *event)
426 {
427         struct device_handler *handler = uev->handler;
428         struct discover_context *ctx;
429         struct discover_device *dev;
430         int rc;
431
432         /* In case this is a network interface, try to refer to it by UUID */
433         dev = discover_device_create(handler, event_get_param(event, "mac"),
434                                         event->device);
435         dev->device->id = talloc_strdup(dev, event->device);
436         ctx = device_handler_discover_context_create(handler, dev);
437
438         rc = parse_user_event(ctx, event);
439         if (rc) {
440                 pb_log("parse_user_event returned %d\n", rc);
441                 return rc;
442         }
443
444         device_handler_discover_context_commit(handler, ctx);
445
446         talloc_free(ctx);
447
448         return 0;
449 }
450
451 static int user_event_remove(struct user_event *uev, struct event *event)
452 {
453         struct device_handler *handler = uev->handler;
454         struct discover_device *dev;
455         const char *mac = event_get_param(event, "mac");
456
457         if (mac)
458                 dev = device_lookup_by_uuid(handler, event_get_param(event, "mac"));
459         else
460                 dev = device_lookup_by_id(handler, event->device);
461
462         if (!dev)
463                 return 0;
464
465         device_handler_remove(handler, dev);
466
467         return 0;
468 }
469
470 static int user_event_url(struct user_event *uev, struct event *event)
471 {
472         struct device_handler *handler = uev->handler;
473         const char *url;
474
475         url = event_get_param(event, "url");
476         if (url)
477                 device_handler_process_url(handler, url, NULL, NULL);
478
479         return 0;
480 }
481
482 static int user_event_boot(struct user_event *uev, struct event *event)
483 {
484         struct device_handler *handler = uev->handler;
485         struct boot_command *cmd = talloc(handler, struct boot_command);
486
487         cmd->option_id = talloc_strdup(cmd, event_get_param(event, "id"));
488         cmd->boot_image_file = talloc_strdup(cmd, event_get_param(event, "image"));
489         cmd->initrd_file = talloc_strdup(cmd, event_get_param(event, "initrd"));
490         cmd->dtb_file = talloc_strdup(cmd, event_get_param(event, "dtb"));
491         cmd->boot_args = talloc_strdup(cmd, event_get_param(event, "args"));
492
493         device_handler_boot(handler, cmd);
494
495         talloc_free(cmd);
496
497         return 0;
498 }
499
500 static int user_event_sync(struct user_event *uev, struct event *event)
501 {
502         struct device_handler *handler = uev->handler;
503
504         if (strncasecmp(event->device, "all", strlen("all")) != 0)
505                 device_sync_snapshots(handler, event->device);
506         else
507                 device_sync_snapshots(handler, NULL);
508
509         return 0;
510 }
511
512 static int process_uninstalled_plugin(struct user_event *uev,
513                 struct event *event)
514 {
515         struct device_handler *handler = uev->handler;
516         struct discover_boot_option *file_opt;
517         struct discover_device *device;
518         struct discover_context *ctx;
519         const char *path;
520         struct resource *res;
521
522         if (!event_get_param(event, "path")) {
523                 pb_log("Uninstalled pb-plugin event missing path param\n");
524                 return -1;
525         }
526
527         device = device_lookup_by_name(handler, event->device);
528         if (!device) {
529                 pb_log("Couldn't find device matching %s for plugin\n",
530                                 event->device);
531                 return -1;
532         }
533
534         ctx = device_handler_discover_context_create(handler, device);
535         file_opt = discover_boot_option_create(ctx, device);
536         file_opt->option->name = talloc_strdup(file_opt,
537                         event_get_param(event, "name"));
538         file_opt->option->id = talloc_asprintf(file_opt, "%s@%p",
539                         device->device->id, file_opt);
540         file_opt->option->type = DISCOVER_PLUGIN_OPTION;
541
542
543         path = event_get_param(event, "path");
544         /* path may be relative to root */
545         if (strncmp(device->mount_path, path, strlen(device->mount_path)) == 0) {
546                 path += strlen(device->mount_path) + 1;
547         }
548
549         res = talloc(file_opt, struct resource);
550         resolve_resource_against_device(res, device, path);
551         file_opt->boot_image = res;
552
553         discover_context_add_boot_option(ctx, file_opt);
554         device_handler_discover_context_commit(handler, ctx);
555
556         return 0;
557 }
558
559 /*
560  * Notification of a plugin event. This can either be for an uninstalled plugin
561  * that pb-plugin has scanned, or the result of a plugin that pb-plugin has
562  * installed.
563  */
564 static int user_event_plugin(struct user_event *uev, struct event *event)
565 {
566         struct device_handler *handler = uev->handler;
567         char *executable, *executables, *saveptr;
568         struct plugin_option *opt;
569         const char *installed;
570
571         installed = event_get_param(event, "installed");
572         if (!installed || strncmp(installed, "no", strlen("no")) == 0)
573                 return process_uninstalled_plugin(uev, event);
574
575         opt = talloc_zero(handler, struct plugin_option);
576         if (!opt)
577                 return -1;
578         opt->name = talloc_strdup(opt, event_get_param(event, "name"));
579         opt->id = talloc_strdup(opt, event_get_param(event, "id"));
580         opt->version = talloc_strdup(opt, event_get_param(event, "version"));
581         opt->vendor = talloc_strdup(opt, event_get_param(event, "vendor"));
582         opt->vendor_id = talloc_strdup(opt, event_get_param(event, "vendor_id"));
583         opt->date = talloc_strdup(opt, event_get_param(event, "date"));
584         opt->plugin_file = talloc_strdup(opt,
585                         event_get_param(event, "source_file"));
586
587         executables = talloc_strdup(opt, event_get_param(event, "executables"));
588         if (!executables) {
589                 talloc_free(opt);
590                 return -1;
591         }
592
593         /*
594          * The 'executables' parameter is a space-delimited list of installed
595          * executables
596          */
597         executable = strtok_r(executables, " ", &saveptr);
598         while (executable) {
599                 opt->executables = talloc_realloc(opt, opt->executables,
600                                                   char *, opt->n_executables + 1);
601                 if (!opt->executables) {
602                         talloc_free(opt);
603                         return -1;
604                 }
605                 opt->executables[opt->n_executables++] = talloc_strdup(opt,
606                                                                 executable);
607                 executable = strtok_r(NULL, " ", &saveptr);
608         }
609
610         device_handler_add_plugin_option(handler, opt);
611
612         talloc_free(executables);
613
614         return 0;
615 }
616
617 static void user_event_handle_message(struct user_event *uev, char *buf,
618         int len)
619 {
620         int result;
621         struct event *event;
622
623         event = talloc(uev, struct event);
624         event->type = EVENT_TYPE_USER;
625
626         result = event_parse_ad_message(event, buf, len);
627
628         if (result)
629                 return;
630
631         user_event_print_event(event);
632
633         switch (event->action) {
634         case EVENT_ACTION_ADD:
635                 result = user_event_add(uev, event);
636                 break;
637         case EVENT_ACTION_REMOVE:
638                 result = user_event_remove(uev, event);
639                 break;
640         case EVENT_ACTION_URL:
641                 result = user_event_url(uev, event);
642                 goto out;
643         case EVENT_ACTION_DHCP:
644                 result = user_event_dhcp(uev, event);
645                 goto out;
646         case EVENT_ACTION_BOOT:
647                 result = user_event_boot(uev, event);
648                 break;
649         case EVENT_ACTION_SYNC:
650                 result = user_event_sync(uev, event);
651                 break;
652         case EVENT_ACTION_PLUGIN:
653                 result = user_event_plugin(uev, event);
654                 break;
655         default:
656                 break;
657         }
658
659         /* user_event_url() and user_event_dhcp() will steal the event context,
660          * but all others still need to free */
661         talloc_free(event);
662 out:
663         return;
664 }
665
666 static int user_event_process(void *arg)
667 {
668         struct user_event *uev = arg;
669         char buf[PBOOT_USER_EVENT_SIZE + 1];
670         int len;
671
672         len = recvfrom(uev->socket, buf, PBOOT_USER_EVENT_SIZE, 0, NULL, NULL);
673
674         if (len < 0) {
675                 pb_log("%s: socket read failed: %s", __func__, strerror(errno));
676                 return 0;
677         }
678
679         if (len == 0) {
680                 pb_log("%s: empty", __func__);
681                 return 0;
682         }
683
684         buf[len] = '\0';
685
686         pb_debug("%s: %u bytes\n", __func__, len);
687
688         user_event_handle_message(uev, buf, len);
689
690         return 0;
691 }
692
693 static int user_event_destructor(void *arg)
694 {
695         struct user_event *uev = arg;
696
697         pb_debug("%s\n", __func__);
698
699         if (uev->socket >= 0)
700                 close(uev->socket);
701
702         return 0;
703 }
704
705 struct user_event *user_event_init(struct device_handler *handler,
706                 struct waitset *waitset)
707 {
708         struct sockaddr_un addr;
709         struct user_event *uev;
710
711         unlink(PBOOT_USER_EVENT_SOCKET);
712
713         uev = talloc(handler, struct user_event);
714
715         uev->handler = handler;
716
717         uev->socket = socket(AF_UNIX, SOCK_DGRAM, 0);
718         if (uev->socket < 0) {
719                 pb_log("%s: Error creating event handler socket: %s\n",
720                         __func__, strerror(errno));
721                 goto out_err;
722         }
723
724         talloc_set_destructor(uev, user_event_destructor);
725
726         memset(&addr, 0, sizeof(addr));
727         addr.sun_family = AF_UNIX;
728         strcpy(addr.sun_path, PBOOT_USER_EVENT_SOCKET);
729
730         if (bind(uev->socket, (struct sockaddr *)&addr, sizeof(addr))) {
731                 pb_log("Error binding event handler socket: %s\n",
732                         strerror(errno));
733         }
734
735         waiter_register_io(waitset, uev->socket, WAIT_IN,
736                         user_event_process, uev);
737
738         pb_debug("%s: waiting on %s\n", __func__, PBOOT_USER_EVENT_SOCKET);
739
740         return uev;
741
742 out_err:
743         talloc_free(uev);
744         return NULL;
745 }