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