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