discover: Move generic config routines to platform
[petitboot] / discover / platform.c
1
2 #define _GNU_SOURCE
3
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <locale.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 #include <log/log.h>
11 #include <file/file.h>
12 #include <types/types.h>
13 #include <talloc/talloc.h>
14 #include <url/url.h>
15
16 #include "platform.h"
17
18 void                    *platform_ctx;
19 static struct platform  *platform;
20 static struct config    *config;
21
22 static const char *kernel_cmdline_debug = "petitboot.debug";
23
24 static void dump_config(struct config *config)
25 {
26         unsigned int i;
27
28         pb_log("configuration:\n");
29
30         if (config->autoboot_enabled)
31                 pb_log(" autoboot: enabled, %d sec\n",
32                                 config->autoboot_timeout_sec);
33         else
34                 pb_log(" autoboot: disabled\n");
35
36         if (config->network.n_interfaces || config->network.n_dns_servers)
37                 pb_log(" network configuration:\n");
38
39         if (config->safe_mode)
40                 pb_log(" safe mode: active\n");
41
42         if (config->disable_snapshots)
43                 pb_log(" dm-snapshots disabled\n");
44
45         for (i = 0; i < config->network.n_interfaces; i++) {
46                 struct interface_config *ifconf =
47                         config->network.interfaces[i];
48
49                 pb_log("  interface %02x:%02x:%02x:%02x:%02x:%02x\n",
50                                 ifconf->hwaddr[0], ifconf->hwaddr[1],
51                                 ifconf->hwaddr[2], ifconf->hwaddr[3],
52                                 ifconf->hwaddr[4], ifconf->hwaddr[5]);
53
54                 if (ifconf->ignore) {
55                         pb_log("   ignore\n");
56                         continue;
57                 }
58
59                 if (ifconf->method == CONFIG_METHOD_DHCP) {
60                         pb_log("   dhcp\n");
61
62                 } else if (ifconf->method == CONFIG_METHOD_STATIC) {
63                         pb_log("   static:\n");
64                         pb_log("    ip:  %s\n", ifconf->static_config.address);
65                         pb_log("    gw:  %s\n", ifconf->static_config.gateway);
66                         pb_log("    url:  %s\n", ifconf->static_config.url);
67
68                 }
69         }
70         for (i = 0; i < config->network.n_dns_servers; i++)
71                 pb_log("  dns server %s\n", config->network.dns_servers[i]);
72
73         for (i = 0; i < config->n_autoboot_opts; i++) {
74                 if (config->autoboot_opts[i].boot_type == BOOT_DEVICE_TYPE)
75                         pb_log("  boot device %d: %s\n", i,
76                                device_type_name(config->autoboot_opts[i].type));
77                 else
78                         pb_log("  boot device %d: uuid: %s\n",
79                                i, config->autoboot_opts[i].uuid);
80         }
81
82         pb_log("  IPMI boot device 0x%02x%s\n", config->ipmi_bootdev,
83                         config->ipmi_bootdev_persistent ? " (persistent)" : "");
84
85         pb_log("  Modifications allowed to disks: %s\n",
86                         config->allow_writes ? "yes" : "no");
87
88         pb_log("  Default UI to boot on: %s\n",
89                 config->boot_console ?: "none set");
90         if (config->manual_console)
91                 pb_log("    (Manually set)\n");
92
93         if (config->http_proxy)
94                 pb_log("  HTTP Proxy: %s\n", config->http_proxy);
95         if (config->https_proxy)
96                 pb_log("  HTTPS Proxy: %s\n", config->https_proxy);
97
98
99         pb_log(" language: %s\n", config->lang ?: "");
100 }
101
102 static bool config_debug_on_cmdline(void)
103 {
104         char buf[600];
105         int rc, fd;
106
107         fd = open("/proc/cmdline", O_RDONLY);
108         if (fd < 0)
109                 return false;
110
111         rc = read(fd, buf, sizeof(buf));
112         close(fd);
113
114         if (rc <= 0)
115                 return false;
116
117         return memmem(buf, rc, kernel_cmdline_debug,
118                         strlen(kernel_cmdline_debug)) != NULL;
119 }
120
121 void config_set_defaults(struct config *config)
122 {
123         const char *lang;
124
125         config->autoboot_enabled = true;
126         config->autoboot_timeout_sec = 10;
127         config->autoboot_enabled = true;
128         config->network.interfaces = NULL;
129         config->network.n_interfaces = 0;
130         config->network.dns_servers = NULL;
131         config->network.n_dns_servers = 0;
132         config->http_proxy = NULL;
133         config->https_proxy = NULL;
134         config->safe_mode = false;
135         config->allow_writes = true;
136         config->disable_snapshots = false;
137
138         config->n_consoles = 0;
139         config->consoles = NULL;
140         config->boot_console = NULL;
141
142         config->n_autoboot_opts = 2;
143         config->autoboot_opts = talloc_array(config, struct autoboot_option,
144                                                 config->n_autoboot_opts);
145         config->autoboot_opts[0].boot_type = BOOT_DEVICE_TYPE;
146         config->autoboot_opts[0].type = DEVICE_TYPE_NETWORK;
147         config->autoboot_opts[1].boot_type = BOOT_DEVICE_TYPE;
148         config->autoboot_opts[1].type = DEVICE_TYPE_ANY;
149
150         config->ipmi_bootdev = 0;
151         config->ipmi_bootdev_persistent = false;
152
153         config->debug = config_debug_on_cmdline();
154
155         lang = setlocale(LC_ALL, NULL);
156         pb_log("lang: %s\n", lang);
157         if (lang && strlen(lang))
158                 config->lang = talloc_strdup(config, lang);
159         else
160                 config->lang = NULL;
161
162 }
163
164 int platform_init(void *ctx)
165 {
166         extern struct platform *__start_platforms,  *__stop_platforms;
167         struct platform **p;
168
169         platform_ctx = talloc_new(ctx);
170
171         for (p = &__start_platforms; p < &__stop_platforms; p++) {
172                 pb_debug("%s: Try platform %s\n", __func__, (*p)->name);
173                 if (!(*p)->probe(*p, platform_ctx))
174                         continue;
175                 platform = *p;
176                 break;
177         }
178
179         config = talloc(platform_ctx, struct config);
180         config_set_defaults(config);
181
182         if (platform) {
183                 pb_log("Detected platform type: %s\n", platform->name);
184                 if (platform->load_config)
185                         platform->load_config(platform, config);
186         } else {
187                 pb_log("No platform type detected, some platform-specific "
188                                 "functionality will be disabled\n");
189         }
190
191         dump_config(config);
192
193         return 0;
194 }
195
196 const struct platform *platform_get(void)
197 {
198         return platform;
199 }
200
201 void platform_pre_boot(void)
202 {
203         const struct config *config = config_get();
204
205         if (platform && config && platform->pre_boot)
206                 platform->pre_boot(platform, config);
207 }
208
209 int platform_get_sysinfo(struct system_info *info)
210 {
211         if (platform && platform->get_sysinfo)
212                 return platform->get_sysinfo(platform, info);
213         return -1;
214 }
215
216 int config_set(struct config *newconfig)
217 {
218         int rc;
219
220         if (!platform || !platform->save_config)
221                 return -1;
222
223         if (newconfig == config)
224                 return 0;
225
226         pb_log("new configuration data received\n");
227         dump_config(newconfig);
228
229         rc = platform->save_config(platform, newconfig);
230
231         if (!rc)
232                 config = talloc_steal(platform_ctx, newconfig);
233         else
234                 pb_log("error saving new configuration; changes lost\n");
235
236         return rc;
237 }
238
239 /* A non-exported function to allow the test infrastructure to initialise
240  * (and change) the configuration variables */
241 struct parser_test;
242 struct config __attribute__((unused)) *test_config_init(
243                 struct parser_test *test);
244 struct config *test_config_init(struct parser_test *test)
245 {
246         config = talloc(test, struct config);
247         config_set_defaults(config);
248         return config;
249 }
250
251 const struct config *config_get(void)
252 {
253         return config;
254 }
255
256 void config_set_autoboot(bool autoboot_enabled)
257 {
258         config->autoboot_enabled = autoboot_enabled;
259
260         pb_log("set autoboot: %s\n",
261                         config->autoboot_enabled ? "enabled" : "disabled");
262 }
263
264 int platform_fini(void)
265 {
266         talloc_free(platform_ctx);
267         return 0;
268 }
269
270 static int parse_hwaddr(struct interface_config *ifconf, const char *str)
271 {
272         int i;
273
274         if (strlen(str) != strlen("00:00:00:00:00:00"))
275                 return -1;
276
277         for (i = 0; i < HWADDR_SIZE; i++) {
278                 char byte[3], *endp;
279                 unsigned long x;
280
281                 byte[0] = str[i * 3 + 0];
282                 byte[1] = str[i * 3 + 1];
283                 byte[2] = '\0';
284
285                 x = strtoul(byte, &endp, 16);
286                 if (endp != byte + 2)
287                         return -1;
288
289                 ifconf->hwaddr[i] = x & 0xff;
290         }
291
292         return 0;
293 }
294
295 static int config_parse_one_interface(struct config *config, char *confstr)
296 {
297         struct interface_config *ifconf;
298         char *tok, *tok_gw, *tok_url, *saveptr;
299
300         ifconf = talloc_zero(config, struct interface_config);
301
302         if (!confstr || !strlen(confstr))
303                 goto out_err;
304
305         /* first token should be the mac address */
306         tok = strtok_r(confstr, ",", &saveptr);
307         if (!tok)
308                 goto out_err;
309
310         if (parse_hwaddr(ifconf, tok))
311                 goto out_err;
312
313         /* second token is the method */
314         tok = strtok_r(NULL, ",", &saveptr);
315         if (!tok || !strlen(tok) || !strcmp(tok, "ignore")) {
316                 ifconf->ignore = true;
317
318         } else if (!strcmp(tok, "dhcp")) {
319                 ifconf->method = CONFIG_METHOD_DHCP;
320
321         } else if (!strcmp(tok, "static")) {
322                 ifconf->method = CONFIG_METHOD_STATIC;
323
324                 /* ip/mask, [optional] gateway, [optional] url */
325                 tok = strtok_r(NULL, ",", &saveptr);
326                 if (!tok)
327                         goto out_err;
328                 ifconf->static_config.address =
329                         talloc_strdup(ifconf, tok);
330
331                 /*
332                  * If a url is set but not a gateway, we can accidentally
333                  * interpret the url as the gateway. To avoid changing the
334                  * parameter format check if the "gateway" is actually a
335                  * pb-url if it's the last token.
336                  */
337                 tok_gw = strtok_r(NULL, ",", &saveptr);
338                 tok_url = strtok_r(NULL, ",", &saveptr);
339
340                 if (tok_gw) {
341                         if (tok_url || !is_url(tok_gw))
342                                 ifconf->static_config.gateway =
343                                         talloc_strdup(ifconf, tok_gw);
344                         else
345                                         tok_url = tok_gw;
346                 }
347
348                 if (tok_url)
349                         ifconf->static_config.url =
350                                 talloc_strdup(ifconf, tok_url);
351         } else {
352                 pb_log("Unknown network configuration method %s\n", tok);
353                 goto out_err;
354         }
355
356         config->network.interfaces = talloc_realloc(config,
357                         config->network.interfaces,
358                         struct interface_config *,
359                         ++config->network.n_interfaces);
360
361         config->network.interfaces[config->network.n_interfaces - 1] = ifconf;
362
363         return 0;
364 out_err:
365         talloc_free(ifconf);
366         return -1;
367 }
368
369 static int config_parse_one_dns(struct config *config, char *confstr)
370 {
371         char *tok, *saveptr = NULL;
372
373         for (tok = strtok_r(confstr, ",", &saveptr); tok;
374                         tok = strtok_r(NULL, ",", &saveptr)) {
375
376                 char *server = talloc_strdup(config, tok);
377
378                 config->network.dns_servers = talloc_realloc(config,
379                                 config->network.dns_servers, const char *,
380                                 ++config->network.n_dns_servers);
381
382                 config->network.dns_servers[config->network.n_dns_servers - 1]
383                                 = server;
384         }
385
386         return 0;
387 }
388
389 static void config_populate_network(struct config *config, const char *cval)
390 {
391         char *val, *saveptr = NULL;
392         int i;
393
394         if (!cval || !strlen(cval))
395                 return;
396
397         val = talloc_strdup(config, cval);
398
399         for (i = 0; ; i++) {
400                 char *tok;
401
402                 tok = strtok_r(i == 0 ? val : NULL, " ", &saveptr);
403                 if (!tok)
404                         break;
405
406                 if (!strncasecmp(tok, "dns,", strlen("dns,")))
407                         config_parse_one_dns(config, tok + strlen("dns,"));
408                 else
409                         config_parse_one_interface(config, tok);
410
411         }
412
413         talloc_free(val);
414 }
415
416 static int read_bootdev(void *ctx, char **pos, struct autoboot_option *opt)
417 {
418         char *delim = strchr(*pos, ' ');
419         int len, prefix = 0, rc = -1;
420         enum device_type type;
421
422         if (!strncmp(*pos, "uuid:", strlen("uuid:"))) {
423                 prefix = strlen("uuid:");
424                 opt->boot_type = BOOT_DEVICE_UUID;
425         } else if (!strncmp(*pos, "mac:", strlen("mac:"))) {
426                 prefix = strlen("mac:");
427                 opt->boot_type = BOOT_DEVICE_UUID;
428         } else {
429                 type = find_device_type(*pos);
430                 if (type != DEVICE_TYPE_UNKNOWN) {
431                         opt->type = type;
432                         opt->boot_type = BOOT_DEVICE_TYPE;
433                         rc = 0;
434                 }
435         }
436
437         if (opt->boot_type == BOOT_DEVICE_UUID) {
438                 if (delim)
439                         len = (int)(delim - *pos) - prefix;
440                 else
441                         len = strlen(*pos) - prefix;
442
443                 if (len) {
444                         opt->uuid = talloc_strndup(ctx, *pos + prefix, len);
445                         rc = 0;
446                 }
447         }
448
449         /* Always advance pointer to next option or end */
450         if (delim)
451                 *pos = delim + 1;
452         else
453                 *pos += strlen(*pos);
454
455         return rc;
456 }
457
458 static void config_populate_bootdev(struct config *config,
459         const struct param_list *pl)
460 {
461         struct autoboot_option *opt, *new = NULL;
462         char *pos, *end;
463         unsigned int n_new = 0;
464         const char *val;
465
466         /* Check for ordered bootdevs */
467         val = param_list_get_value(pl, "petitboot,bootdevs");
468         if (!val || !strlen(val)) {
469                 pos = end = NULL;
470         } else {
471                 pos = talloc_strdup(config, val);
472                 end = strchr(pos, '\0');
473         }
474
475         while (pos && pos < end) {
476                 opt = talloc(config, struct autoboot_option);
477
478                 if (read_bootdev(config, &pos, opt)) {
479                         pb_log("bootdev config is in an unknown format "
480                                "(expected uuid:... or mac:...)\n");
481                         talloc_free(opt);
482                         continue;
483                 }
484
485                 new = talloc_realloc(config, new, struct autoboot_option,
486                                      n_new + 1);
487                 new[n_new] = *opt;
488                 n_new++;
489                 talloc_free(opt);
490
491         }
492
493         if (!n_new) {
494                 /* If autoboot has been disabled, clear the default options */
495                 if (!config->autoboot_enabled) {
496                         talloc_free(config->autoboot_opts);
497                         config->n_autoboot_opts = 0;
498                 }
499                 return;
500         }
501
502         talloc_free(config->autoboot_opts);
503         config->autoboot_opts = new;
504         config->n_autoboot_opts = n_new;
505 }
506
507 void config_populate_all(struct config *config, const struct param_list *pl)
508 {
509         const char *val;
510         char *end;
511         unsigned long timeout;
512
513         /* if the "auto-boot?' property is present and "false", disable auto
514          * boot */
515         val = param_list_get_value(pl, "auto-boot?");
516         config->autoboot_enabled = !val || strcmp(val, "false");
517
518         val = param_list_get_value(pl, "petitboot,timeout");
519         if (val) {
520                 timeout = strtoul(val, &end, 10);
521                 if (end != val) {
522                         if (timeout >= INT_MAX)
523                                 timeout = INT_MAX;
524                         config->autoboot_timeout_sec = (int)timeout;
525                 }
526         }
527
528         val = param_list_get_value(pl, "petitboot,language");
529         config->lang = val ? talloc_strdup(config, val) : NULL;
530
531         val = param_list_get_value(pl, "petitboot,network");
532         config_populate_network(config, val);
533
534         config_populate_bootdev(config, pl);
535
536         if (!config->debug) {
537                 val = param_list_get_value(pl, "petitboot,debug?");
538                 config->debug = val && !strcmp(val, "true");
539         }
540
541         val = param_list_get_value(pl, "petitboot,write?");
542         if (val)
543                 config->allow_writes = !strcmp(val, "true");
544
545         val = param_list_get_value(pl, "petitboot,snapshots?");
546         if (val)
547                 config->disable_snapshots = !strcmp(val, "false");
548
549         val = param_list_get_value(pl, "petitboot,console");
550         if (val)
551                 config->boot_console = talloc_strdup(config, val);
552         /* If a full path is already set we don't want to override it */
553         config->manual_console = config->boot_console &&
554                                         !strchr(config->boot_console, '[');
555
556         val = param_list_get_value(pl, "petitboot,http_proxy");
557         if (val)
558                 config->http_proxy = talloc_strdup(config, val);
559         val = param_list_get_value(pl, "petitboot,https_proxy");
560         if (val)
561                 config->https_proxy = talloc_strdup(config, val);
562 }