7350b6c3c4ef558927097c53066e81759d0a0a1f
[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_conf(struct user_event *uev, struct event *event)
386 {
387         struct device_handler *handler = uev->handler;
388         struct discover_device *dev;
389         struct pb_url *url;
390         const char *val;
391
392         val = event_get_param(event, "url");
393         if (!val)
394                 return 0;
395
396         url = pb_url_parse(event, val);
397         if (!url)
398                 return 0;
399
400         dev = discover_device_create(handler, event->device);
401
402         device_handler_conf(handler, dev, url);
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         dev = discover_device_create(handler, event->device);
414         ctx = device_handler_discover_context_create(handler, dev);
415
416         parse_user_event(ctx, event);
417
418         device_handler_discover_context_commit(handler, ctx);
419
420         talloc_free(ctx);
421
422         return 0;
423 }
424
425 static int user_event_remove(struct user_event *uev, struct event *event)
426 {
427         struct device_handler *handler = uev->handler;
428         struct discover_device *dev;
429
430         dev = device_lookup_by_id(handler, event->device);
431         if (!dev)
432                 return 0;
433
434         device_handler_remove(handler, dev);
435
436         return 0;
437 }
438
439 static int user_event_url(struct user_event *uev, struct event *event)
440 {
441         struct device_handler *handler = uev->handler;
442         const char *url;
443
444         url = event_get_param(event, "url");
445         if (url)
446                 device_handler_process_url(handler, url, NULL, NULL);
447
448         return 0;
449 }
450
451 static int user_event_boot(struct user_event *uev, struct event *event)
452 {
453         struct device_handler *handler = uev->handler;
454         struct boot_command *cmd = talloc(handler, struct boot_command);
455
456         cmd->option_id = talloc_strdup(cmd, event_get_param(event, "id"));
457         cmd->boot_image_file = talloc_strdup(cmd, event_get_param(event, "image"));
458         cmd->initrd_file = talloc_strdup(cmd, event_get_param(event, "initrd"));
459         cmd->dtb_file = talloc_strdup(cmd, event_get_param(event, "dtb"));
460         cmd->boot_args = talloc_strdup(cmd, event_get_param(event, "args"));
461
462         device_handler_boot(handler, cmd);
463
464         talloc_free(cmd);
465
466         return 0;
467 }
468
469 static int user_event_sync(struct user_event *uev, struct event *event)
470 {
471         struct device_handler *handler = uev->handler;
472
473         if (strncasecmp(event->device, "all", strlen("all")) != 0)
474                 device_sync_snapshots(handler, event->device);
475         else
476                 device_sync_snapshots(handler, NULL);
477
478         return 0;
479 }
480
481 static void user_event_handle_message(struct user_event *uev, char *buf,
482         int len)
483 {
484         int result;
485         struct event *event;
486
487         event = talloc(uev, struct event);
488         event->type = EVENT_TYPE_USER;
489
490         result = event_parse_ad_message(event, buf, len);
491
492         if (result)
493                 return;
494
495         user_event_print_event(event);
496
497         switch (event->action) {
498         case EVENT_ACTION_ADD:
499                 result = user_event_add(uev, event);
500                 break;
501         case EVENT_ACTION_REMOVE:
502                 result = user_event_remove(uev, event);
503                 break;
504         case EVENT_ACTION_URL:
505                 result = user_event_url(uev, event);
506                 break;
507         case EVENT_ACTION_CONF:
508                 result = user_event_conf(uev, event);
509                 break;
510         case EVENT_ACTION_DHCP:
511                 result = user_event_dhcp(uev, event);
512                 break;
513         case EVENT_ACTION_BOOT:
514                 result = user_event_boot(uev, event);
515                 break;
516         case EVENT_ACTION_SYNC:
517                 result = user_event_sync(uev, event);
518                 break;
519         default:
520                 break;
521         }
522
523         talloc_free(event);
524
525         return;
526 }
527
528 static int user_event_process(void *arg)
529 {
530         struct user_event *uev = arg;
531         char buf[PBOOT_USER_EVENT_SIZE + 1];
532         int len;
533
534         len = recvfrom(uev->socket, buf, PBOOT_USER_EVENT_SIZE, 0, NULL, NULL);
535
536         if (len < 0) {
537                 pb_log("%s: socket read failed: %s", __func__, strerror(errno));
538                 return 0;
539         }
540
541         if (len == 0) {
542                 pb_log("%s: empty", __func__);
543                 return 0;
544         }
545
546         buf[len] = '\0';
547
548         pb_debug("%s: %u bytes\n", __func__, len);
549
550         user_event_handle_message(uev, buf, len);
551
552         return 0;
553 }
554
555 static int user_event_destructor(void *arg)
556 {
557         struct user_event *uev = arg;
558
559         pb_debug("%s\n", __func__);
560
561         if (uev->socket >= 0)
562                 close(uev->socket);
563
564         return 0;
565 }
566
567 struct user_event *user_event_init(struct device_handler *handler,
568                 struct waitset *waitset)
569 {
570         struct sockaddr_un addr;
571         struct user_event *uev;
572
573         unlink(PBOOT_USER_EVENT_SOCKET);
574
575         uev = talloc(handler, struct user_event);
576
577         uev->handler = handler;
578
579         uev->socket = socket(AF_UNIX, SOCK_DGRAM, 0);
580         if (uev->socket < 0) {
581                 pb_log("%s: Error creating event handler socket: %s\n",
582                         __func__, strerror(errno));
583                 goto out_err;
584         }
585
586         talloc_set_destructor(uev, user_event_destructor);
587
588         memset(&addr, 0, sizeof(addr));
589         addr.sun_family = AF_UNIX;
590         strcpy(addr.sun_path, PBOOT_USER_EVENT_SOCKET);
591
592         if (bind(uev->socket, (struct sockaddr *)&addr, sizeof(addr))) {
593                 pb_log("Error binding event handler socket: %s\n",
594                         strerror(errno));
595         }
596
597         waiter_register_io(waitset, uev->socket, WAIT_IN,
598                         user_event_process, uev);
599
600         pb_debug("%s: waiting on %s\n", __func__, PBOOT_USER_EVENT_SOCKET);
601
602         return uev;
603
604 out_err:
605         talloc_free(uev);
606         return NULL;
607 }