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